hierarchy viewer makes height changes appear - android

it's my first question.
I have built a custom component: a RelativeLayout with a TextView on the bottom and two ImageView above that, acting as a 2-columns clickable element of an histogram. To set the height of a bar, i get the "available height" in onLayout(), as container's height minus label's one:
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
mAvailHeight = getHeight()-findViewById(R.id.label).getHeight(); // it works
and then assign it (multiplied by a 0.-1. value) as a layout parameter to the ImageView:
View bar = findViewById(R.id.bigBar);
RelativeLayout.LayoutParams rlp = (RelativeLayout.LayoutParams) bar.getLayoutParams();
rlp.height = Math.round((float)mAvailHeight * mBigBarHeight);
}
The mBigBarHeight variable (0.-1.) can be set via this function:
public void setBigBarHeight(float value, float max) {
mBigBarHeight = value / max;
requestLayout(); //
invalidate(); // do these help? i find no difference
}
Now. When i add one of these "HistogramBar" in onCreate() and set the heights and label everything works as I expect. If i try to modify them later, say onClickSomething:
bar.setBigBarHeight(25, 100);
bar.setSmallBarHeight(50, 100);
bar.setLabel("jjk");
only the label changes. I checked with Hierarchy Viewer and actually the LayoutParams did change. If i click again changes appear.
The funny thing is that even if i do "Load View Hierarchy" from the tool changes get displayed (on the emulator)!! What happens? Is it strange? I want to do that in my code so that it works!
I couldn't find any similar question. Thanks.

When you load a hierarchy from the tool, a relayout/redraw happens to measure performance. You are probably not calling requestLayout() when you should.

Related

When do View dimensions become available to MainActivity?

Let's say we have a main_activity.xml layout that defines all dimensions in a relative manner -- constraints, percentages, and guidelines (that are percentages)... no "static" dp.
But in MainActivity.java, we programatically create some subviews, and we want to define their height/width dimensions as relative to existing views.
We do not know the dimensions or density of the device so, so nor do we know the (actual integer) dimensions of any view before run-time...
But we can say something like:
int heightDimensionForNewView = (int) (someAlreadyInflatedView.getHeight() / 7f)
But what if, under certain circumstances, these "new" views need to be displayed immediately at app start-time?
So, the question:
In the Android Activity life-cycle, when is the earliest point at which you can (somehow) safely query (something) for actual/finalized/guaranteed layout dimensions? And what is that something and somehow?
I haven't been able to find an override method such as "onContentViewInflated()" and there is no onCreateView() method like there is in Fragments.
I've also tried Logging from inside onStart() and onResume() but the dimension results are always "0," presumably because they haven't been inflated yet.
I know that any given View can get its own dimensions in onMeasure(), but then you would have make a static variable in MainActivity in order to assign it and use it from there... or some way of sending that information from the View back to the Activity.
What am I missing? I just want to be able to get the number somehow from inside MainActivity itself.
My suggestions are:
view.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
#Override
public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
int oldWidth = oldRight - oldLeft; // right exclusive, left inclusive
if( v.getWidth() != oldWidth ) {
// width has changed
}
}
});
and
view.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
// View has laid out
// Remove the layout observer if you don't need it anymore
view.getViewTreeObserver().removeGlobalOnLayoutListener(this);
}
});

Scroll items in RecyclerView so last item is always the same when soft keyboard is shown

I'm working on a messaging app, nothing fancy about the layout - a Toolbar at the top of the layout, an EditText at the bottom and a RecyclerView for the list of messages, filling the space in-between.
My issue is that the position of the last visible item in the RecyclerView is not maintained when the soft keyboard is shown by clicking/pressing the EditText. i.e. if the list has 20 items and the last visible when the soft keyboard isn't shown is item 12, when the soft keyboard is shown I'd still like the last visible to be item 12.
I already tried a combination of adjustPan and adjustResize in the manifest and in code but doesn't solve the problem.
Any ideas?
I think your best bet is to set adjustResize in the manifest then create a custom RecyclerView subclass that overrides onLayout():
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
int height = b - t;
if (height != mLastHeight) {
int last = getAdapter.getItemCount() - 1;
scrollToPosition(last);
}
mLastHeight = height;
}

Android: Notify Scrollview that it's child's size has changed: how?

When I enlarge the size of the content of a scrollview, the scrollview takes a while to get to "know" this size change of it's child. How can I order the ScrollView to check it's child immediately?
I have an ImageView in a LinearLayout in a ScrollView.
In my ScaleListener.onScale, I change the size of my LinearLayout. I then try to order a scroll on the scrollview. In the ScaleListener.onScale:
LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) imageView.getLayoutParams();
params.width = (int) (startX * scaleFactor);
params.height = (int) (startY * scaleFactor);
imageView.setLayoutParams(params);
(...)
scrollView.scrollBy(scrollX, scrollY);
However, no scrolling occurs when in the situation before the scaling scrolling was not possible because the view was too small to scroll. After the setLayoutParams, the view should be larger, but no scrolling occurs because the scrollview thinks the child is still small.
When a fes ms later the onScroll is called again, it does scroll fine, it somehow found out that the child is larger and scrollable.
How can I notify the scrollview immediately, that the child's size has changed? So that scrollBy will work right after setLayoutParams on it's child?
I found a solution after trying just about every onXXX() method. onLayout can be used. You can plan the scroll and do it later in onLayout().
Extend your scrollview, and add:
private int onLayoutScrollByX = 0;
private int onLayoutScrollByY = 0;
public void planScrollBy(int x, int y) {
onLayoutScrollByX += x;
onLayoutScrollByY += y;
}
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
doPlannedScroll();
}
public void doPlannedScroll() {
if (onLayoutScrollByX != 0 || onLayoutScrollByY != 0) {
scrollBy(onLayoutScrollByX, onLayoutScrollByY);
onLayoutScrollByX = 0;
onLayoutScrollByY = 0;
}
}
Now, to use this in your code, instead of scrollBy(x,y) use planScrollBy(x,y). It will do the scroll at a time when the new size of the child is "known", but not displayed on screen yet.
When you use a horizontal or vertical scrollview, of course you can only scroll one way, so you will have to change this code it a bit (or not, but it will ignore the scroll on the other axis). I used a TwoDScrollView, you can find it on the web.
You can call:
scrollView.updateViewLayout(childView, childLayout)

How do I force a ViewGroup to draw off screen?

I have several LinearLayouts that get filled with downloaded images or text within a ScrollView. The LinearLayouts have a LayoutAnimation applied to them, so each one "slides" into place when drawn. Is there a way to force the offscreen LinearLayouts to draw so that by the time the user scrolls to them, the animation has already completed? I've tried measuring each view like so: (container is the ViewGroup)
int measuredWidth = View.MeasureSpec.makeMeasureSpec(LayoutParams.FILL_PARENT, View.MeasureSpec.AT_MOST);
int measuredHeight = View.MeasureSpec.makeMeasureSpec(LayoutParams.WRAP_CONTENT, View.MeasureSpec.UNSPECIFIED);
container.measure(measuredWidth, measuredHeight);
container.layout(0, 0, container.getMeasuredWidth(), container.getMeasuredHeight());
container.requestLayout();
But they still won't draw until they appear on screen during scrolling (which normally is fine but the animation makes it.. er, not fine)
If you don't want to run the animation why don't you simply remove the animation? The framework will apply the animation because you tells it to.
Also note that none of your code causes a redraw. To draw you need to call invalidate() or draw().
For any future readers, here's what I ended up doing: I subclassed LinearLayout and overrode onLayout to only apply animation if the layout is currently on screen at the moment it is populated:
#Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom)
{
super.onLayout(changed, left, top, right, bottom);
// only animate if viewgroup is currently on screen
int[] xy = new int[2];
this.getLocationOnScreen(xy);
int yPos = xy[1];
if (yPos < availableScreenHeight && bottom > 200)
{
Animation slide_down = AnimationUtils.loadAnimation(getContext(), R.anim.container_slide_down);
LayoutAnimationController controller = new LayoutAnimationController(slide_down, 0.25f);
this.setLayoutAnimation(controller);
}
}
This actually saves some cycles since I'm not applying animation across the board then removing it from views that don't need it. (BTW "availableScreenHeight" is just that, and "200" is simply a threshold that I know a populated view will never be smaller than. Your case may vary.)

How to retrieve the dimensions of a view?

I have a view made up of TableLayout, TableRow and TextView. I want it to look like a grid. I need to get the height and width of this grid. The methods getHeight() and getWidth() always return 0. This happens when I format the grid dynamically and also when I use an XML version.
How to retrieve the dimensions for a view?
Here is my test program I used in Debug to check the results:
import android.app.Activity;
import android.os.Bundle;
import android.widget.TableLayout;
import android.widget.TextView;
public class appwig extends Activity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.maindemo); //<- includes the grid called "board"
int vh = 0;
int vw = 0;
//Test-1 used the xml layout (which is displayed on the screen):
TableLayout tl = (TableLayout) findViewById(R.id.board);
tl = (TableLayout) findViewById(R.id.board);
vh = tl.getHeight(); //<- getHeight returned 0, Why?
vw = tl.getWidth(); //<- getWidth returned 0, Why?
//Test-2 used a simple dynamically generated view:
TextView tv = new TextView(this);
tv.setHeight(20);
tv.setWidth(20);
vh = tv.getHeight(); //<- getHeight returned 0, Why?
vw = tv.getWidth(); //<- getWidth returned 0, Why?
} //eof method
} //eof class
I believe the OP is long gone, but in case this answer is able to help future searchers, I thought I'd post a solution that I have found. I have added this code into my onCreate() method:
EDITED: 07/05/11 to include code from comments:
final TextView tv = (TextView)findViewById(R.id.image_test);
ViewTreeObserver vto = tv.getViewTreeObserver();
vto.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
LayerDrawable ld = (LayerDrawable)tv.getBackground();
ld.setLayerInset(1, 0, tv.getHeight() / 2, 0, 0);
ViewTreeObserver obs = tv.getViewTreeObserver();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
obs.removeOnGlobalLayoutListener(this);
} else {
obs.removeGlobalOnLayoutListener(this);
}
}
});
First I get a final reference to my TextView (to access in the onGlobalLayout() method). Next, I get the ViewTreeObserver from my TextView, and add an OnGlobalLayoutListener, overriding onGLobalLayout (there does not seem to be a superclass method to invoke here...) and adding my code which requires knowing the measurements of the view into this listener. All works as expected for me, so I hope that this is able to help.
I'll just add an alternative solution, override your activity's onWindowFocusChanged method and you will be able to get the values of getHeight(), getWidth() from there.
#Override
public void onWindowFocusChanged (boolean hasFocus) {
// the height will be set at this point
int height = myEverySoTallView.getMeasuredHeight();
}
You are trying to get width and height of an elements, that weren't drawn yet.
If you use debug and stop at some point, you'll see, that your device screen is still empty, that's because your elements weren't drawn yet, so you can't get width and height of something, that doesn't yet exist.
And, I might be wrong, but setWidth() is not always respected, Layout lays out it's children and decides how to measure them (calling child.measure()), so If you set setWidth(), you are not guaranteed to get this width after element will be drawn.
What you need, is to use getMeasuredWidth() (the most recent measure of your View) somewhere after the view was actually drawn.
Look into Activity lifecycle for finding the best moment.
http://developer.android.com/reference/android/app/Activity.html#ActivityLifecycle
I believe a good practice is to use OnGlobalLayoutListener like this:
yourView.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
if (!mMeasured) {
// Here your view is already layed out and measured for the first time
mMeasured = true; // Some optional flag to mark, that we already got the sizes
}
}
});
You can place this code directly in onCreate(), and it will be invoked when views will be laid out.
Use the View's post method like this
post(new Runnable() {
#Override
public void run() {
Log.d(TAG, "width " + MyView.this.getMeasuredWidth());
}
});
I tried to use onGlobalLayout() to do some custom formatting of a TextView, but as #George Bailey noticed, onGlobalLayout() is indeed called twice: once on the initial layout path, and second time after modifying the text.
View.onSizeChanged() works better for me because if I modify the text there, the method is called only once (during the layout pass). This required sub-classing of TextView, but on API Level 11+ View. addOnLayoutChangeListener() can be used to avoid sub-classing.
One more thing, in order to get correct width of the view in View.onSizeChanged(), the layout_width should be set to match_parent, not wrap_content.
Are you trying to get sizes in a constructor, or any other method that is run BEFORE you get the actual picture?
You won't be getting any dimensions before all components are actually measured (since your xml doesn't know about your display size, parents positions and whatever)
Try getting values after onSizeChanged() (though it can be called with zero), or just simply waiting when you'll get an actual image.
As F.X. mentioned, you can use an OnLayoutChangeListener to the view that you want to track itself
view.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
#Override
public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
// Make changes
}
});
You can remove the listener in the callback if you only want the initial layout.
I guess this is what you need to look at: use onSizeChanged() of your view. Here is an EXTENDED code snippet on how to use onSizeChanged() to get your layout's or view's height and width dynamically http://syedrakibalhasan.blogspot.com/2011/02/how-to-get-width-and-height-dimensions.html
ViewTreeObserver and onWindowFocusChanged() are not so necessary at all.
If you inflate the TextView as layout and/or put some content in it and set LayoutParams then you can use getMeasuredHeight() and getMeasuredWidth().
BUT you have to be careful with LinearLayouts (maybe also other ViewGroups). The issue there is, that you can get the width and height after onWindowFocusChanged() but if you try to add some views in it, then you can't get that information until everything have been drawn. I was trying to add multiple TextViews to LinearLayouts to mimic a FlowLayout (wrapping style) and so couldn't use Listeners. Once the process is started, it should continue synchronously. So in such case, you might want to keep the width in a variable to use it later, as during adding views to layout, you might need it.
Even though the proposed solution works, it might not be the best solution for every case because based on the documentation for ViewTreeObserver.OnGlobalLayoutListener
Interface definition for a callback to be invoked when the global layout state or the visibility of views within the view tree changes.
which means it gets called many times and not always the view is measured (it has its height and width determined)
An alternative is to use ViewTreeObserver.OnPreDrawListener which gets called only when the view is ready to be drawn and has all of its measurements.
final TextView tv = (TextView)findViewById(R.id.image_test);
ViewTreeObserver vto = tv.getViewTreeObserver();
vto.addOnPreDrawListener(new OnPreDrawListener() {
#Override
public void onPreDraw() {
tv.getViewTreeObserver().removeOnPreDrawListener(this);
// Your view will have valid height and width at this point
tv.getHeight();
tv.getWidth();
}
});
Height and width are zero because view has not been created by the time you are requesting it's height and width . One simplest solution is
view.post(new Runnable() {
#Override
public void run() {
view.getHeight(); //height is ready
view.getWidth(); //width is ready
}
});
This method is good as compared to other methods as it is short and crisp.
You should rather look at View lifecycle: http://developer.android.com/reference/android/view/View.html Generally you should not know width and height for sure until your activity comes to onResume state.
You can use a broadcast that is called in OnResume ()
For example:
int vh = 0;
int vw = 0;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.maindemo); //<- includes the grid called "board"
registerReceiver(new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
TableLayout tl = (TableLayout) findViewById(R.id.board);
tl = (TableLayout) findViewById(R.id.board);
vh = tl.getHeight();
vw = tl.getWidth();
}
}, new IntentFilter("Test"));
}
protected void onResume() {
super.onResume();
Intent it = new Intent("Test");
sendBroadcast(it);
}
You can not get the height of a view in OnCreate (), onStart (), or even in onResume () for the reason that kcoppock responded
Simple Response: This worked for me with no Problem.
It seems the key is to ensure that the View has focus before you getHeight etc. Do this by using the hasFocus() method, then using getHeight() method in that order. Just 3 lines of code required.
ImageButton myImageButton1 =(ImageButton)findViewById(R.id.imageButton1);
myImageButton1.hasFocus();
int myButtonHeight = myImageButton1.getHeight();
Log.d("Button Height: ", ""+myButtonHeight );//Not required
Hope it helps.
Use getMeasuredWidth() and getMeasuredHeight() for your view.
Developer guide: View
CORRECTION:
I found out that the above solution is terrible. Especially when your phone is slow.
And here, I found another solution:
calculate out the px value of the element, including the margins and paddings:
dp to px:
https://stackoverflow.com/a/6327095/1982712
or dimens.xml to px:
https://stackoverflow.com/a/16276351/1982712
sp to px:
https://stackoverflow.com/a/9219417/1982712 (reverse the solution)
or dimens to px:
https://stackoverflow.com/a/16276351/1982712
and that's it.

Categories

Resources