In my Activity, I have a method that makes the font size bigger or smaller, depending on the width of a TextView. I do a setText on the TextView to enter a new value (which will most likely increase or decrease the width of the ViewText) and then go right into my method. My problem is that when I enter my method, the size of the TextView has not changed yet, so I'm guessing the UI thread is taking a bit more time to accomplish the resize of the TextView and my method does not work because of that. So how do I wait after the UI thread so that I can correctly execute the code of my method?
This is the code of my main activity. The method texteAffichage is just setting what is in my TextView and gestionPolice is resizing the font if it needs to. When I do a sysout in gestionPolice, I never get the current width of the TextView, because it seems like it did not have time to do that.
affichage.setText(texteAffichage((Button) v));
gestionPolice();
You could delay your code using a Handler:
Handler h = new Handler();
h.postDelayed(new Runnable() {
#Override
public void run() {
// DO DELAYED STUFF
}
}, your_variable_amount_of_time); // e.g. 3000 milliseconds
Side note: I do not think that your problem occurs because the setText(...) method takes too much time. Post your code so that others can have a look at it.
My suggestion would be something like:
tv.setText("someText");
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
int width = tv.getWidth();
// your code reacting to the change in width
}
}, 50);
50 miliseconds should be enough for the change in UI to complete but fast enough for the user not to notice.
As Simon correctly points out this is not necessarily the best solution. To give you an example of how you might use GlobalLayoutListener:
final ViewTreeObserver vto = tv.getViewTreeObserver();
vto.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
width = tv.getWidth();
// your code reacting to the change in width
}
});
Related
I have a layout with some Views that I need to change size depending on some parameters, that depend on width of the View. The width is set to "match_parent". So when I try to get the width it returns 0. Here is what I have tried so far:
mask.getWidth();
mask.getLayoutParams().width
ViewTreeObserver vto = mask.getViewTreeObserver();
vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
mask.getViewTreeObserver().removeGlobalOnLayoutListener(this);
int width = mask.getMeasuredWidth();
}
});
So my question is how do I get the width in this situation?
Thank you.
After digging around I found a solution that answers my question but has a major problem that I am yet to resolve. We can use method post() that every View has and adds a Runnable to the message queue. So as I understand what happens is after View is fully measured in method post() we can get dimensions that we need like this:
holder.button.post(new Runnable() {
#Override
public void run() {
width = holder.button.getWidth();
}
});
However the problem here is that RecyclerView won't wait until this view is measured as and will go to next row and rows won't be updated according to measures that you expect.
If anyway has a way to go about this problem please leave a comment. Thank you.
I'm working on an Android TV app and need to make a progress bar to indicate how much content has been played already. I feel like what I've written should be working, but it isn't.
I've got a LinearLayout with android:orientation="horizontal" and android:weightSum=100 as a container for the progress bar, and the actual progress bar is a FrameLayout inside the container, with android:layout_width="0dp" and android:layout_weight="0" to make sure the progress bar isn't drawn if there isn't any progress.
In the activity code I'm attempting to change the weight of the progress bar like this:
ViewGroup.LayoutParams lp = progressView.getLayoutParams();
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(lp.width, lp.height, progress);
progressView.setLayoutParams(params);
(progressView, naturally, is the progress bar object I'm trying to manipulate, and progress is the intended new weight)
I was initially running this code in the activity's onCreate() method, until I remembered you can't do anything about a view's dimensions until it has been laid out properly. Since then I've tried running this in onResume() with postDelayed like this:
#Override
public void onResume() {
super.onResume();
progressView.postDelayed(new Runnable() {
#Override
public void run() {
setProgress();
}
}, 1000);
}
and with a ViewTreeObserver like this during onCreate():
progressView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
if (!progressBarRefreshed) {
setProgress();
}
}
});
and I've even tried running the code as an onClick event attached to a button in the same activity (in both cases setProgress() is the function that performs the weight change using the code posted above). I've verified (with log messages I removed from the code to make it more readable) that the initial width is 0, height is -1 (match_parent) and weight is 0, and that the new LayoutParams object has width 0, height -1 and weight equal to whatever I set it to. But after the setLayoutParams() call the view still has weight 0 and there is no change on the screen either. What blatantly trivial thing am I missing here?
Try calling progressView.requestLayout() after setting the layout params.
I want to make a gridView (or ListView) scrolling automatically (without user interaction) repeatly.
I want it on the background, the user has not the possibility to scroll the gridView, he has only one button in foreground to start an activity. It is just a "presentation" activity
How can I make it possible? I have no idea what to use to do it, if there is somes simple android api to make it.
Should I use animation thread or can it be done only with smoothToScroll?
Use duration
mListView.smoothScrollToPositionFromTop(position, duration);
You have the smoothScrollToPosition method that will scroll to a position on the listview/gridview
http://developer.android.com/reference/android/widget/AbsListView.html#smoothScrollToPosition(int)
As for the possibility to scroll on the gridview/listview just implement a touchListener and return true
For example if you want to to this slowly you can't just create a for. It will scroll too fast. You can use handlers for that. Create a recursive function scrollTo.
public void scrollTo(final ListView myView, final int position) {
final Handler myHandler = new Handler();
myHandler.postDelayed(new Runnable() {
#Override
public void run() {
myView.smoothScrollToPosition(position);
if(position<myView.getAdapter().getCount())
scrollTo(myView, position + 1);
}
}, 2000);
}
And next call it once like scrollTo(myListview,0);. The function will do the rest. Change 2000 for the number of seconds you want to wait *1000.
I'm looking for a good way to measure the dimensions of the actual content area for an activity in Android.
Getting display always works. Simply go like this:
Display display = getWindowManager().getDefaultDisplay();
And you can get the pixel count for the entire screen. Of course this does not take into consideration the ActionBar, status bar, or any other views which will reduce the available size of the activity itself.
Once the activity is running, you can do this:
View content = getWindow().findViewById(Window.ID_ANDROID_CONTENT);
To get the activity content only. But doing this in onCreate() will result in a view with width and height of 0, 0.
Is there a way to get these dimensions during onCreate? I imagine there ought to be a way to get the measurements of any status bars and just subtract that from the total display size, but I'm unable to find a way to do that. I think this would be the only way, because the content window method will always return a view with no width/height before it is drawn.
Thanks!
You can use a layout or pre-draw listener for this, depending on your goals. For example, in onCreate():
final View content = findViewById(android.R.id.content);
content.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
//Remove it here unless you want to get this callback for EVERY
//layout pass, which can get you into infinite loops if you ever
//modify the layout from within this method.
content.getViewTreeObserver().removeGlobalOnLayoutListener(this);
//Now you can get the width and height from content
}
});
Update
as of API 16 removeGlobalOnLayoutListener is deprecated.
Change to:
content.getViewTreeObserver().removeOnGlobalLayoutListener(this)
(copied from my answer to a related question)
I use the following technique - post a runnable from onCreate() that will be executed when the view has been created:
contentView = findViewById(android.R.id.content);
contentView.post(new Runnable()
{
public void run()
{
contentHeight = contentView.getHeight();
}
});
This code will run on the main UI thread, after onCreate() has finished.
Answer with post is incorrect, because the size might not be recalculated.
Another important thing is that the view and all it ancestors must be visible. For that I use a property View.isShown.
Here is my kotlin function, that can be placed somewhere in utils:
fun View.onInitialized(onInit: () -> Unit) {
viewTreeObserver.addOnGlobalLayoutListener(object : OnGlobalLayoutListener {
override fun onGlobalLayout() {
if (isShown) {
viewTreeObserver.removeOnGlobalLayoutListener(this)
onInit()
}
}
})
}
And the usage is:
myView.onInitialized {
Log.d(TAG, "width is: " + myView.width)
}
This appears to be a duplicate question. There is an elegant answer within this SO question:
getWidth() and getHeight() of View returns 0
Which (shamelessly copied) is to override onWindowFocusChanged(), which seems to fire just after onCreate(), and where the sizes are rendered:
#Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
//Here you can get the size!
}
If you want lo load a Bitmap into a ImageView within OnCreate(), you can use this example to do it:
public static void setImageLater(#NonNull final ImageView imageView,final Bitmap bitmap){
final ViewTreeObserver observer = imageView.getViewTreeObserver();
observer.addOnGlobalLayoutListener(
new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
imageView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
imageView.setImageBitmap(bitmap);
}
});
}
You can do any work wich needs sizes in onResume(using flag like alreadyDone not to repeat it every time an Activity goes foreground). In onCreate views are not displayed, so it's normal that sizes are zeros.
I'm getting height and width of a view, inflated in the getView() method. It's a grid item.
Usually I use post() on the view to get the size, after it was attached to the layout. But it returns 0.
final View convertViewFinal = convertView;
convertView.post(new Runnable() {
#Override
public void run() {
doSomethingWithConvertView(convertViewFinal);
}
});
...
doSomethingWithConvertView(View v) {v.getWidth(); /*returns 0*/};
How do I get the size?
While using a viewTreeObserver definitely works, I have found that calling measurements requirements on inflated views can be done reliably using a runnable from the activity's view. i.e.
someActivityInstance.getWindow().getDecorView().post(new Runnable() {
#override
public void run() {
// someMeasurements
}
});
The thing is that convertview most likely is not drawn on the phone screen yet, so you have to wait until it is ready.
You need to use a ViewTreeObserver, to know exactly when the view has been drawn.
Check this answer for more info:
When Can I First Measure a View?
You probably is calling this at onCreate or onStart or onResume, methods which runs before layout measure.
But there is a lot of work arounds, this is one good option:
ViewTreeObserver vto = rootView.getViewTreeObserver();
vto.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
public void onGlobalLayout() {
v.getWidth();//already measured...
}
});
Where rootView may be any viewGroup in a higher level than the one you want the width.
But be aware that this listner may run more than once.