I currently have this UI structure:
Activity (ACT)
LinearLayout (LL)
HorizontalScrollView (HSV)
RelativeLayout (RL)
When I set the layout parameters of RL, they don't get applied until later, when it is too late.
var TotalWidth = GetNewWidth() // eg returns 1000...
var lp = this.LayoutRoot.LayoutParameters;
lp.Width = TotalWidth;
this.LayoutRoot.LayoutParameters = lp;
// this.LayoutRoot.LayoutParameters.Width == 1000
// this.LayoutRoot.Width == 0
this.LayoutRoot.RequestLayout();
this.LayoutRoot.ForceLayout();
this.RequestLayout();
this.ForceLayout();
// this.LayoutRoot.LayoutParameters.Width == 1000
// this.LayoutRoot.Width == 0
This is not a problem if I wasn't using the width, but I am :)
I want to do this:
this.ScrollTo(500, 0);
This has no effect as the total width of the HSV children is currently 0.
But, if I do this:
this.PostDelayed(() => this.ScrollTo((int) percent, 0), 500);
It works, after a 500ms delay.
The HSV is a derived class that manages its contents itself - just an empty RL that I use to set the width so that I can have a huge area for painting images. Sort of an massive scrolling canvas.
Also, what I am trying to do is in the HSV's OnSizeChanged overridden member. I want to make it so that if the control is re-sized, the control is scrolled to the same position. The width is dependent on the height (keeps aspect ratio)
You'll have to wait till your layout has been recalculated. When you call forceLayout layouting doesn't happen immediately, it just gets added to the queue in the UI thread. onLayout or onSizeChanged will be called for all involved views as soon as they get their new positions and sizes assigned. Nothing really you can do about that.
Related
I have custom view with child items, which are configureable via xml. However they are can be configurable in runtime via something like a Configuration class. After that I just notify parent view about changes and all is ok.BTW. My question in fact touches measurement: I can change child items size in runtime, but for the first launch I want to set to all of them size (width and height) accordingly to layout params defined in xml.
Maybe some code will add more clarification to you.
protected int getItemWidth() {
if (cell != null) {
int width = cell.getWidth();
return width == 0 ? <layout_width_defined_in_xml> : ScreenUtils.convertToDp(context, width);
} else {
return canvasWidth;
}
}
So, I want to know. Is possible to get layout params before measurement? And how to that?
The size defined in XML isn't always the right size the view should have when it's actually laid out. For example, a child of a LinearLayout may have android:layout_width="0", but might have nonzero width because of android:layout_weight. (There are other examples as well with other kinds of layouts.) Additionally, the values match_parent and wrap_content map to negative integer values in java code, which I don't think is helpful to you here.
If your custom view is interested in measuring and positioning child views, you should be overriding onMeasure() and onLayout(). If you aren't doing that or if you don't need to do that, getWidth() and getHeight() will tell you a view's actual size, and getMeasuredWidth() and getMeasuredHeight() will tell you the measured size (which can differ from the actual size). THe only caveat is that you have to wait for the first measure/layout before calling those 4 methods because otherwise those methods all return zero.
If you do want to inspect the layout parameters of a view, you can do
LayoutParams params = view.getLayoutParams();
int width = params.width;
int height = params.height;
As noted, either or both of those may be negative. You can compare them to LayoutParams.MATCH_PARENT and LayoutParams.WRAP_CONTENT to check for these cases, but again I'm not sure this is helpful to you if you aren't implementing onMeasure() and onLayout().
You can do this in callback ViewTreeObserver.OnGlobalLayoutListener. You can get view dimensions here and set size other views with these dimensions.
I have a View (containing an ad) that I need to scale to fit the screen width at a certain point in time (after the ad is loaded). So I have a method setHeight(myView) that calculates the correct height for the given screen width and changes the LayoutParams of the View accordingly. The crucial part of the code is this:
LayoutParams params = myView.getLayoutParams();
int width = myView.getWidth();
if (params != null && width > 0) {
params.height = (int) Math.round(ratio * width);
}
This seems to work for the most part, but sometimes the view is not scaled. It seems to only consistenly work if I add the following line at the bottom:
myView.setLayoutParams(params);
This seems to make sense, too, since Android Views call requestLayout() in their setLayoutParams() method. Conversely, I see no way how a change in a public field (params.height) would trigger a layout change.
On the other hand, I repeatedly find tutorials on the net where the params are simply changed and then not set to the view again.
So, my question is: Is it correct that, to immediately update the layout after changing a property of the LayoutParams, I need to call setLayoutParams again? And that simply changing the property will only lead to a layout change at some later point in time when the layout change is triggered from elsewhere?
A change to the layout params only take effect on the next layout pass.
requestLayout() schedules a layout pass
setLayoutParams() calls requestLayout() as you have observed
Sometimes a layout pass is scheduled by some other means. For example, immediately after inflation the layout params have also been inflated and the measure/layout message has just been posted to the UI thread message queue for later processing.
So, to be safe, call requestLayout() always after touching the layout params. setLayoutParams() works, too, though it's not strictly necessary when modifying the params in-place.
In Kotlin you can use KTX extension to update LayoutParams
view.updateLayoutParams { height = newValue }
I'm currently extending the SlidingDrawer class and want to resize the width of the content and can't quite get it to work right. Right now I'm setting the width of the whole view (handle and content) and it works for resizing purposes but also introduced a visual glitch when I move the handle it jumps to the new size for a split second and then returns to the handles position. I'm thinking that the problem is stemming from the onMeasure() or onLayout() calls that are happening in the base SlidingDrawer that are preventing the content area to be resized but am not completely sure.
I'm using getLayoutParams().width = newWidth; to resize the whole view but would like to use something like mContent.getLayoutParams().width = newWidth;.
The source code for the onMeasure() is here and for the onLayout() here.
Any insight into why the content area can't be resized would be great. Thanks!
So I finally figured it out if anyone else was having an issue with this. Basically when you want to resize the layout you need to measure() the layouts after the size change. Without the offsetLeftAndRight() call the handle will "jump" to the new size for a split second so setting the offset eliminates that "jump".
A simplified version of what I did was essentially:
public void resize() {
int previousPosition = mHandle.getLeft();
//Set the new size of the content area
mContent.getLayoutParams().width = width;
//Measure the newly sized content area and adjust the layout
mContent.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(getBottom() - getTop(), MeasureSpec.EXACTLY));
mContent.layout(handleWidth + mTopOffset, 0, mTopOffset + handleWidth + content.getMeasuredWidth(), content.getMeasuredHeight());
/* Remeasure any other views that were resized also here */
//Not required but helps position the handle correctly
mHandle.offsetLeftAndRight(previousPosition);
}
Is there a way the this problem can be fixed? I tried invalidate() but, it still displays the same problem. What happens is that, after opening the page/ the Activity, the images behaves like the one in Figure A. It only renders to my desired layout (Figure B) after scrolling it back and forth.
What I'm trying to do is set the width and heigth of the image during runtime. So this is also in relation to my previous questions : Images in my HorizontalListView changes it size randomly and ImageView dynamic width and height which received a very little help.
Any tips regarding this matter, please?
EDIT: btw, my classes are:
MyCustomAdapter (extends baseadapter, this calls the displayimage() from ImageLoader ),
MyActivity and
ImageLoader (this is where my image url are loaded, decoded, displayed asynchronously)
Im also confused as to where i will set the height and width of the imageView. For now, i set it at ImageLoader. It was okay. but i dont know if i did the right thing.
If you want to set the width and height manually at runtime, grab a reference to the ImageView's LayoutParams AFTER the View has been measured by the layout system. If you do this too early in the rendering phase, your view's width and height as well as its parent view and so on will be 0.
I have some code in an open source library that might help you. The process is two parts:
Set up an OnPreDrawListener attached to the ViewTreeObserver for your control. My example does this inside of a custom control, but you can do this in your activity as well.
Inside the onPreDraw method, your image and it's parent will now have their width and height values assigned to them. You can make your calculations and then set your width and/or height manually to the LayoutParams object of your view (don't forget to set it back).
Check out this example where I'm applying an aspect ratio to a custom ImageView just before it's rendered to the screen. I don't know if this exactly fits your use case, but this will demonstrate how to add an OnPreDrawListener to a ViewTreeObserver, removing it when you're done, and applying dynamic sizing to a View at runtime
https://github.com/aguynamedrich/beacon-utils/blob/master/Library/src/us/beacondigital/utils/RemoteImageView.java#L78
Here's a modified version that removes my particular resizing logic. It also grabs the ViewTreeObserver from the imageView, which is a more likely scenario if you're not implementing a custom control and you only want to do this in the Activity
private void initResizeLogic() {
final ViewTreeObserver obs = imageView.getViewTreeObserver();
obs.addOnPreDrawListener(new OnPreDrawListener() {
public boolean onPreDraw() {
dynamicResize();
obs.removeOnPreDrawListener(this);
return true;
}
});
}
protected void dynamicResize() {
ViewGroup.LayoutParams lp = imageView.getLayoutParams();
// resize logic goes here...
// imageView.getWidth() and imageView.getHeight() now return
// their initial layout values
lp.height = someCalculatedHeight;
lp.width = someCalculatedWidth;
imageView.setLayoutParams(lp);
}
}
I'm attempting to build a layout programmatically.
This layout is dynamic. Based on the data read from a database, it could look different every time.
I have a LinearLayout with its orientation set to vertical.
I want to fit as many TextViews with text (the data from database) as I can on a "row".
What I'm doing is building LinearLayouts that are the rows. These LinearLayouts are populated with TextViews. I build the TextView, set the text, and then check the width to see if it will fit on this row (by subtracting the sum of all TextView widths from the screen width and see if the new TextView will fit). If not, I create a new LinearLayout and start adding TextViews to that one.
The problem is myTextView.getWidth() and myTextView.getMeasuredWidth() both return 0.
Why? How can I get the width of the TextView?
Well first of all, please post your code. Second the getWidth/getHeight returning zero question gets asked A LOT so you could search SO for more answers.
Assuming your calling these methods during onCreate (which is probably the case), the UI hasn't been drawn yet so the returned value is zero. You can't get the width or height of the view until it has been drawn.
You can use this library to schedule the task of perform calculation on the width to the correct time after the view had been completely drawn
You can call it on onCreate() and from any Thread
https://github.com/Mohamed-Fadel/MainThreadScheduler
Sample of use:
MainThreadScheduler.scheduleWhenIdle(new Runnable() {
#Override
public void run() {
int width = textview.getWidth();
int height = textview.getHeight();
textview.setText( String.valueOf( width +","+ height ));
}
});