Why getHeight() of a Viewgroup does not change - android

I have a LinearLayout (ViewGroup). And in my code, I have added a child view (another linear layout) to the LinearLayout. My question is why the LinearLayout 's getHeight() return the SAME value? On the phone, I do see my child linear layout is visible and is displayed correctly. But why the getHeight() does not return me the right value?
And I have looked at the source code of add() in ViewGroup. It does call 'requestLayout()' so I expect the getHeight() of the ViewGroup does get update correctly. Am I correct?

getHeight() is not updated immediately, requestLayout() requests a layout pass that happens later (it's asynchronous). You must post an event in the UI events queue to retrieve the height after the layotu pass:
requestLayout();
post(new Runnable() {
public void run() {
// getHeight
}
});

Related

getTop for custom view always returning zero

I have built a custom view, where when the user taps on the view it expands. Before it expands I call getTop() to get the current Y coordinate, however it returns zero always. Im calling getTop() from within the custom view class. Is calling getTop() from the class that implements the view not allowed?
public void expand(float targetHeight) { //This custom view extends FrameLayout
int top = getTop(); // returns zero
rootLayout.setLayoutParams(new FrameLayout.LayoutParams(widthPixels, dpToPx((int) targetHeight)));
webView.setLayoutParams(new FrameLayout.LayoutParams(widthPixels, dpToPx((int) targetHeight)));
}
The method getTop() returns the Top position of the view relative to its parent. So maybe is in the position 0 of his parent.
Can you please share your XML so I can have a better understanding of the layout?

Android - Adding an item to a scroll view and scroll programmatically

I have an empty scrollview. I create with inflater a text view, add it to the ScrollView programmatically and then do:
rsView.smoothScrollBy((viewLeft + viewWidth / 2) - center, 0);
BUT viewLeft and viewWidth are 0 as the view is not measured yet. Any suggestions on how to make it scroll the view properly?
In this post: getWidth() and getHeight() of View returns 0 you have a lot of good options to solve this problem. Particularly I use this one:
Listen to Draw/Layout Events: ViewTreeObserver
A ViewTreeObserver gets fired for different drawing events. Usually the OnGlobalLayoutListener is what you want for getting the measurement, so the code in the listener will be called after the layout phase, so the measurments are ready:
view.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
rsView.smoothScrollBy((viewLeft + viewWidth / 2) - center, 0);
}
});
Move the smoothScrollBy() call to a Runnable and post that to the new TextView's handler after it's been added, which will cause it to run after it's been laid out. For example:
viewGroup.addView(childView);
childView.post(new Runnable() {
#Override
public void run() {
rsView.smoothScrollBy((childView.getLeft() + childView.getWidth() / 2) - center, 0);
}
}
);
I'm not sure how you're calculating center, but if it depends on the new TextView's dimensions, you'll need to move the calculation to the Runnable, as well.

When is the correct moment to call getWidth() / getHeight() on a View?

EDIT --> Please, this is NOT a question WHY getWidth() / getHeight() return zero.
I have a Fragment inside an Activity.
I am dynamically adding SubViews (red) to a LinearLayout (RowView, blue) in horizontal orientation that is a child of the Fragment (green). How many of those childviews (SubViews) are added to the LinearLayout is determined at runtime.
The SubViews must have a fixed width of 200dp. Therefore, when dynamically adding the SubViews to the Linearlayout, I want to check if there is enough space for another SubView, or if a new Row needs to be started. The width of the RowView should be variable, and is NOT necessarily equal to the screen size.
In order to check if there is enough space, I simply see if the combined width of all SubViews is smaller than the width of the Linearlayout - the width of one SubView. Inside my custom LinearLayout (RowView), this looks as follows:
public void addSubView(SubView v) {
int childcount = getChildCount();
int totalChildWidth = 0;
for(int i = 0; i < childcount; i++) {
totalChildWidth += getChildAt(i).getWidth();
}
if(totalChildWidth < getWidth() - 200) { // there is space left in this row
addView(v);
}
}
The problem simply is, that getWidth() of the LinearLayout, and getWidth() of already added SubViews return 0, and therefore, the check if there is enough space goes wrong.
The reason for that is that I am calling getWi´dtdh() when the Views (the UI) have not yet been rendered on the screen, so my question is when is the right time to call my addSubView() method? Currently, I am calling it when creating the Fragment, which is obviously wrong:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment, container, false);
RowView row = (RowView) v.findViewById(R.id.rowView);
ArrayList<SubView> subViews = DBManager.getSubViewList(); // connects to a database and returns all available subviews
for(int i = 0; i < subViews.size(); i++) {
row.addSubView(subViews.get(i));
}
return v;
}
So where to call my addSubView(...) method where it is ensured that getWidth() inside it will not return 0? And in general, when is the correct moment (which callback method, according to Activity lifecycle) for getWidth() or getHeight() of a View to be called, where it is ensured that they will not return 0?
What I have tried so far:
Call addSubView(...) of my RowView in Fragments onActivityCreated(...) --> doesn't work
Connect to the database inside the RowViwes onSizeChanged(...) method, and all addSubView(...) there --> doesn't work
Do it as described in the code above, but with a Handler with 500ms delay --> works, because UI is rendered, but is not a proper solution for me
What you try to do is not working as the layouting isn't finished at the moment you want to know the width of the element. There are two solutions. The first one is to determine the actual width of your layout, which is ease in your case, as it is the actual screen width:
int width = getActivity().getResources().getDisplayMetrics().widthPixels
Use this value to determine how many of your subviews you can add in one row by converting the 200dp into pixel:
int viewWidth = (int) (200 * (getActivity().getResources().getDisplayMetrics().densityDpi / 160f));
Then you can calculate the maximal number of views for a row:
int maxViewsToAdd = (int) width/viewWidth;
The other solution is a globalLayoutListener, you can find a description here. But this seems not to work in all cases.

Why view width and height 0 in post() runnable?

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.

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