setVisibility on ProgressBar in Android - java

I have the following progress bar in my Android application:
<ProgressBar
android:id="#+id/progressBar"
android:layout_width="200sp"
android:layout_height="200sp"
android:layout_marginTop="60sp"
android:visibility="gone"
android:indeterminate="true"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
I also have an ImageView with an image in it. What I want to do is while I'm making a call to an API, make the image invisible and show the ProgressBar in its place, then when it ends show the image again and hide the ProgressBar. However, this does not work:
searchBtn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
ProgressBar progressBar = findViewById(R.id.progressBar);
ImageView image = findViewById(R.id.image);
image.setVisibility(View.INVISIBLE);
progressBar.setVisibility(View.VISIBLE);
Thread thread = new Thread(new Runnable() {
#Override
public void run() {
makeRequest();
}
});
thread.start();
try {
thread.join();
progressBar.setVisibility(View.INVISIBLE);
image.setVisibility(View.VISIBLE);
} catch (InterruptedException e) {
}
}
});
I've tried various combinations with View.INVISIBLE and View.GONE, nothing works. I've also searched through similar questions on here but none of them answers my question.
I thought maybe it's because it disappears and reappears so quickly I don't notice it, but even if I do Thread.sleep() the progress bar still doesn't show up.
What am I doing wrong?

Calling thread.join() within OnClickListener blocks the UI thread and hence block any UI updates.
To fix your code, you can remove thread.join() and put progressBar.setVisibility(View.INVISIBLE);image.setVisibility(View.VISIBLE); within run() after makeRequest() but warp with runOnUiThread()
searchBtn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
ProgressBar progressBar = findViewById(R.id.progressBar);
ImageView image = findViewById(R.id.image);
image.setVisibility(View.INVISIBLE);
progressBar.setVisibility(View.VISIBLE);
Thread thread = new Thread(new Runnable() {
#Override
public void run() {
makeRequest();
runOnUiThread(new Runnable() {
#Override
public void run() {
progressBar.setVisibility(View.INVISIBLE);
image.setVisibility(View.VISIBLE);
}
});
}
});
thread.start();
}
});

Related

Why is there no CalledFromWrongThreadException when a new thread operates UI immediately?

I was trying to dynamically add a view in to another view in MainActivity:
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout.xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.example.aronho.testfragment.MainActivity">
<FrameLayout
android:id="#+id/myView"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</android.support.constraint.ConstraintLayout>
view_test.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#FFFF00">
<TextView
android:text="testString"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
I know this code will throw CalledFromWrongThreadException when I start the app:
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
LayoutInflater vi = (LayoutInflater) getApplicationContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
final View view=vi.inflate(R.layout.view_test,null);
final FrameLayout parentView=(FrameLayout) findViewById(R.id.myView);
parentView.addView(view);
new Thread(new Runnable(){
#Override
public void run() {
try{
Thread.sleep(3000);
}catch (Exception e){
}
parentView.removeView(view);
}
}).start();
}
}
but when I remove the Thread.sleep(3000);,eg:
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
LayoutInflater vi = (LayoutInflater) getApplicationContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
final View view=vi.inflate(R.layout.view_test,null);
final FrameLayout parentView=(FrameLayout) findViewById(R.id.myView);
parentView.addView(view);
new Thread(new Runnable(){
#Override
public void run() {
parentView.removeView(view);
}
}).start();
}
}
the app doesn't throw CalledFromWrongThreadException despite I try to remove a view using a new thread. Why would that happen?
The root cause of the crash is the ViewRootImpl.checkThread () method.
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException( "Only the original thread that created a view hierarchy can touch its views.");
}
However, the invalidate () method is not called when the view is not measured , the invalidate() method will eventually execute to the checkThread().
ok , see :
new Thread(new Runnable() {
#Override
public void run() {
System.err.println("getMeasuredHeight:" + parentView.getMeasuredHeight());
System.err.println("getMeasuredWidth:" + parentView.getMeasuredWidth());
parentView.removeView(view);
}
}).start();
will get that:
W/System.err: getMeasuredWidth:0
W/System.err: getMeasuredHeight:0
can see, the measurement process has not been completed , so we can change the UI without triggering the invalidate() and not trigger exception.
then, keep it sleep 50 ms:
new Thread(new Runnable() {
#Override
public void run() {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.err.println("getMeasuredHeight:" + parentView.getMeasuredHeight());
System.err.println("getMeasuredWidth:" + parentView.getMeasuredWidth());
parentView.removeView(view);
}
}).start();
will get that:
W/System.err: getMeasuredHeight:360
W/System.err: getMeasuredWidth:1080
can see, the measurement process has been completed, then , when we change the UI the invalidate will be triggering,and then it call the checkThread().
In short, avoid manipulating Android UI elements off the UI Thread; you're going to run into unpredictable behaviour.
You can synchronize your update to the UI by using Activity.runOnUiThread(Runnable r), which should alleviate the problem. i.e.
new Thread(new Runnable(){
#Override
public void run() {
try{
Thread.sleep(3000);
// Synchronize on the UI Thread.
MainActivity.this.runOnUiThread(new Runnable() { #Override public final void run() {
// Remove the View.
parentView.removeView(view);
} });
}
catch (Exception e){
e.printStackTrace();
}
}
}).start();
However, there is a much better construct you should be using; you should never underestimate the computational burden of allocating and running a new Thread!
You can achieve the exact same functionality using:
(new Handler(Looper.getMainLooper())).postDelayed(new Runnable() {
/* Update the UI. */
}, 3000));
This will achieve the same behaviour, without leaving you having to worry about managing synchronization along the UI Thread. Additionally, you won't be launching a whole new Thread whose only job is to sleep!
This should perform a great deal better.
No need to use a thread for a delay, instead :
new Thread(new Runnable(){
#Override
public void run() {
try{
Thread.sleep(3000);
}catch (Exception e){
}
parentView.removeView(view);
}
}).start();
Do :
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
parentView.removeView(view);
}
}, 3000);

What's a "nice" way of stopping a (linear) runnable in case of activity switch?

I have a thread that will do some processing, then has runnable code which will display the results on the screen. The issue is that if the user presses the back arrow while the runnable is between two lines of display code, the next line will crash as the activity no longer exists.
The below code accomplishes the goal, but I hate having if statements before each line. Is there a better way?
#Override
public void onBackPressed() {
super.onBackPressed();
imgThread.interrupt();
}
private void processImage(){
final Handler mHandler = new Handler(Looper.getMainLooper());
progress = new ProgressDialog(this);
progress.setMessage("TEXT");
progress.setIndeterminate(true);
progress.setProgressStyle(ProgressDialog.STYLE_SPINNER);
progress.show();
imgThread = new Thread(){
#Override
public void run() {
//Process the image a bit
mHandler.post(new Runnable() {
#Override
public void run() {
if(!imgThread.isInterrupted())
imageView.setImageBitmap(mDisplayBitmap);
if(!imgThread.isInterrupted())
progress.setMessage("TEXT TEXT");
if(!imgThread.isInterrupted())
progress.show();
}
});
//More processing
mHandler.post(new Runnable() {
#Override
public void run() {
if(!imgThread.isInterrupted())
addScreenListener();
if(!imgThread.isInterrupted())
determineHelpToast("TEXT TEXT TEXT", Toast.LENGTH_LONG);
if(!imgThread.isInterrupted())
progress.dismiss();
}
});
}
}; imgThread.start();
}
The really safe way to do this is to modify your Runnable so that it never references the Android Context (Activity, etc.).
The easiest way to do this is to communicate changes to the Activity via an event bus.

Ripple Effect On ImageView not triggering

I have a RecyclerView with ImageViews in each item.
I set onClickListener for the ImageViews in onBindViewHolder as follows:
holder.starIV.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
// TODO: logic
}
});
The ripple effect worked fine until I added the following logic to onClick. This logic changes the Drawable for the ImageView.
holder.starIV.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (v.getId() == holder.starIV.getId()) {
ListItem clickedItem = mDataset.get(position);
ListItem updatedItem = new ListItem(clickedItem);
if (clickedItem.getStarState() == STAR_ON) {
updatedItem.setStarState(STAR_OFF);
updatedItem.setStarDrawable(
ContextCompat.getDrawable(
v.getContext(),R.drawable.ic_star_border_24px));
}
else if (clickedItem.getStarState() == STAR_OFF) {
updatedItem.setStarState(STAR_ON);
updatedItem.setStarDrawable(
ContextCompat.getDrawable(
v.getContext(),R.drawable.ic_star_24px));
}
mDataset.set(position,updatedItem);
notifyDataSetChanged();
}
}
});
Now, I get no ripple effect at all. Here's the XML for the ImageView:
<ImageView
android:id="#+id/list_item_star"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:paddingLeft="4dp"
android:paddingRight="16dp"
android:src="#drawable/ic_star_border_24px"
android:clickable="true"
android:background="?attr/selectableItemBackgroundBorderless"
android:drawSelectorOnTop="true"
/>
The ripple effect works normally again when i comment out the logic part in onClick.
Have I implemented the above correctly?
What change would you suggest to get the ripple effect working correctly?
EDIT: It appears that changing the Drawable is interfering with the ripple animation. So i moved all the logic to an AsyncTask with a small delay to allow the animation to finish. This seems to work, but I feel this solution is not elegant. Here's the AsyncTask:
class DoLogix extends AsyncTask<Integer, Integer, Void> {
#Override
protected Void doInBackground(Integer... params) {
try{Thread.sleep(125);}catch (Exception e) {}
publishProgress(params[0]);
return null;
}
protected void onProgressUpdate(Integer... val) {
ListItem clickedItem = mDataset.get(val[0]);
ListItem updatedItem = new ListItem(clickedItem);
if (clickedItem.getStarState() == STAR_ON) {
updatedItem.setStarState(STAR_OFF);
updatedItem.setStarDrawable(starBorder);
}
else if (clickedItem.getStarState() == STAR_OFF) {
updatedItem.setStarState(STAR_ON);
updatedItem.setStarDrawable(star);
}
mDataset.set(val[0],updatedItem);
notifyDataSetChanged();
}
}
u can set a ripple drawable as the foreground of ur imageview.
add below code to your parent layout
android:clickable="true"
android:focusable="true"
android:background="?attr/selectableItemBackgroundBorderless"

How to hide and display a view in front of a webview depending on user input

I'm currently working on a project where I use a webview packed in RelativeLayout and I placed a ImageButton in the left-bottom corner:
<RelativeLayout
android:id="#+id/relativeLayout_maincontent"
android:layout_width="fill_parent"
android:layout_height="fill_parent" >
<WebView
android:id="#+id/webview"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_alignParentTop="true"
/>
<ImageButton
android:id="#+id/imageButton_call"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_marginLeft="2dp"
android:src="#android:drawable/ic_menu_call" />
</RelativeLayout>
This works just fine. The next step I was thinking of is that the button can sometimes disturb my users, when they can't see the stuff behind the button... So I tried to hide the button after a few seconds and when the user is scrolling/touching/or whatever (just some user input) I'll display it and after a few seconds the button should disappear again. Like the zoom-in and zoom-out buttons in a webview. The best way would be to use the same functionality as these zoom buttons, but I couldn't find a way to do this. I created a function based on the knowledge of How to show a view for 3 seconds, and then hide it? :
private void startCallButtonHidingThread() {
Thread thread = new Thread() {
#Override
public void run() {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
}
runOnUiThread(new Runnable() {
#Override
public void run() {
callButton.setVisibility(ImageButton.GONE);
}
});
}
};
thread.start();
}
That works great. The next step was to display the button when user input happens, so I tried things like that:
relativeLayout.setOnTouchListener(new OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
if(callButton.getVisibility() == ImageButton.GONE) callButton.setVisibility(ImageButton.VISIBLE);
startCallButtonHidingThread();
return false;
}
});
or that:
relativeLayout.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
if(callButton.getVisibility() == ImageButton.GONE) callButton.setVisibility(ImageButton.VISIBLE);
startCallButtonHidingThread();
}
});
and the same I tried on the webview. Most of the time it works just one time and then there is no reaction at all.
So if you have any idea or a better solution, please let me know.
I really appreciate your help!
I think it's android restriction. Try to use handler:
private Handler handler = new Handler(){
#Override
public void handleMessage(Message msg) {
switch (msg.what){
case 1:{
if(callButton.getVisibility() == ImageButton.GONE) callButton.setVisibility(ImageButton.VISIBLE);
startCallButtonHidingThread();
}
}
};
And your listener:
relativeLayout.setOnTouchListener(new OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
handler.sendEmptyMessage(1);
return false;
}
});

Android: Updating UI with a Button?

So I have some simple code but it seems to not be working.. any suggestions?
I just want an image to show after a button is pressed then become invisible after 2 seconds.
button.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
firstImage.setVisibility(ImageView.VISIBLE);
// delay of some sort
firstImage.setVisibility(ImageView.INVISIBLE);
}
}
The image never shows, it always stays invisible, should I be implementing this in another way? I've tried handlers.. but it didn't work, unless I did it wrong.
Never make your UI thread sleep!
Do this:
final Handler handler = new Handler();
button.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
firstImage.setVisibility(ImageView.VISIBLE);
handler.postDelayed(new Runnable(){
public void run(){
firstImage.setVisibility(ImageView.INVISIBLE);
}
}, DELAY);
}
}
Where you would set DELAY as 2000 (ms).
Well, you will need to add a delay between the two lines. Use a thread or a timer to do this.
Start a thread on click of a button. In the run method, change the ImageView's visibility to VISIBLE, then put the thread to sleep for n secs, and then change then make it invisible.
To call the imageView's setvisibility method, you will need a hanlder here.
Handler handler = new Handler();
handler.post(new Runnable() {
public void run() {
image.setVisibiliy(VISIBLE);
Thread.sleep(200);
image.setVisibility(INVISIBLE);
}
});
I know this question has already been answered, but I thought I would add an answer for people who like me, stumbled across this looking for a similar result where the delay was caused by a process rather than a "sleep"
button.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
firstImage.setVisibility(ImageView.VISIBLE);
// Run the operation on a new thread
new Thread(new Runnable(){
public void run(){
myMethod();
returnVisibility();
}
}).start();
}
}
private void myMethod() {
// Perform the operation you wish to do before restoring visibility
}
private void returnVisibility() {
// Restore visibility to the object being run on the main UI thread.
MainActivity.this.runOnUiThread(new Runnable() {
#Override
public void run() {
firstImage.setVisibility(ImageView.INVISIBLE);
}
});
}

Categories