Without resorting to the infamous onGlobalLayoutListener() solution, and without having to implement a custom View, what lifecycle event in a Fragment can I put code into and be sure all of the Fragment's Views have been given a size?
As a corollary, I would also like this lifecycle event to be applicable to Fragments in a ViewPager.
I dont think there would be any Fragment lifecycle event to be sure all the Views have size.
What I would usually do is, to use OnLayoutChangeListener inside onActivityCreated(). Like this,
getView().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) {
getView().removeOnLayoutChangeListener(this);
// Check the size of Views here.
}
});
I am trying to get a screen capture in the Xamarin.Android platform.
public static Android.Content.Context Context { get; private set; }
public override View OnCreateView(View parent, string name, Context context, IAttributeSet attrs)
{
MainActivity.Context = context;
return base.OnCreateView(parent, name, context, attrs);
}
I am trying to find out the why the following rootView.Width and Height returns 0 all the time.
var rootView = ((Activity)MainActivity.Context).Window.DecorView.RootView;
Console.WriteLine ("{0}x{1}", rootView.Width,rootView.Height);
My ultimate goal is to capture the screenshot of the view as an image and generate pdf.
I don't know Xamarin, however it seems to be the same as native Android for this solution.
When onCreateView() is called the views have not yet been measured. To get a view dimensions you should attach a specific listener: onLayoutChangeListener.
Here is an Android native code example:
rootView.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 width = right - left;
int height = bottom - top;
v.removeOnLayoutChangeListener(this); // Remove the listener
}
});
You could find here the listener to use for Xamarin
Hope its help ! :)
In the onCreateView the width & height of objects are not yet defined. They are defined in a later stage of the activity's lifecycle.
You have to use the treeviewobserver for this.
Example with your rootview:
rootView.ViewTreeObserver.GlobalLayout += (object sender, EventArgs e) => {
Console.WriteLine ("{0}x{1}", rootView.Width,rootView.Height);
};
In that method the width & height will be known.
Furthermore, you want to take a picture of the your rootview, the best way to do this is to use this method, this will automatically output a Bitmap of the view to variable b.
rootView.DrawingCacheEnabled = true;
Bitmap b = rootView.GetDrawingCache(true);
Hope this gets you on your way!
In Android, can you create a Listener for catching changes in a View's properties (width / height / margin / position relative to top of the screen)?
I want to trigger an event when layout_marginTop="10dp" is changed to a different value.
What about implementing a OnLayoutChangeListener that gets called when a View is moved due to Layout Change
new View().addOnLayoutChangeListener(new OnLayoutChangeListener() {
#Override
public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
int oldTop, int oldRight, int oldBottom) {
// TODO Auto-generated method stub
}
});
Excerpt from Android API:
Add a
listener that will be called when the bounds of the view change due to
layout processing.
A GridView has BaseAdapter derived class responsible for populating its children. It works fine.
I want to do something when the GridView is refreshed. In other words, I wish there was a method "addOnRefreshListner()".
It needs to accommodate API Level as low as 8 (Froyo).
What if you override BaseAdapter.notifyDataSetChanged() in your custom adapter?
#Override
public void notifyDataSetChanged() {
super.notifyDataSetChanged();
// A change has happened and caused the GridView to refresh
}
notifyDataSetChanged() is the solution to the absence of your addOnRefreshListener().
Try implementing a View.OnLayoutChangeListener() for API 11 or higher.
View myView = findViewById(R.id.my_view);
myView.addOnLayoutChangedListener(new OnLayoutChangeListener() {
#Override
public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
// Do what you need to do with the height/width since they are now set
}
});
Dianne Hackborn mentioned in a couple threads that you can detect when a layout as been resized, for example, when the soft keyboard opens or closes. Such a thread is this one... http://groups.google.com/group/android-developers/browse_thread/thread/d318901586313204/2b2c2c7d4bb04e1b
However, I didn't understand her answer: "By your view hierarchy being resized with all of the corresponding layout traversal and callbacks."
Does anyone have a further description or some examples of how to detect this? Which callbacks can I link into in order to detect this?
Thanks
One way is View.addOnLayoutChangeListener. There's no need to subclass the view in this case. But you do need API level 11. And the correct calculation of size from bounds (undocumented in the API) can sometimes be a pitfall. Here's a correct example:
view.addOnLayoutChangeListener( new View.OnLayoutChangeListener()
{
public void onLayoutChange( View v,
int left, int top, int right, int bottom,
int leftWas, int topWas, int rightWas, int bottomWas )
{
int widthWas = rightWas - leftWas; // Right exclusive, left inclusive
if( v.getWidth() != widthWas )
{
// Width has changed
}
int heightWas = bottomWas - topWas; // Bottom exclusive, top inclusive
if( v.getHeight() != heightWas )
{
// Height has changed
}
}
});
Another way (as dacwe answers) is to subclass your view and override onSizeChanged.
Override onSizeChanged in your View!
With Kotlin extensions:
inline fun View?.onSizeChange(crossinline runnable: () -> Unit) = this?.apply {
addOnLayoutChangeListener { _, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom ->
val rect = Rect(left, top, right, bottom)
val oldRect = Rect(oldLeft, oldTop, oldRight, oldBottom)
if (rect.width() != oldRect.width() || rect.height() != oldRect.height()) {
runnable();
}
}
}
Use thus:
myView.onSizeChange {
// Do your thing...
}
My solution is to add an invisible tiny dumb view at the end of of the layout / fragment (or add it as a background), thus any change on size of the layout will trigger the layout change event for that view which could be catched up by OnLayoutChangeListener:
Example of adding the dumb view to the end of the layout:
<View
android:id="#+id/theDumbViewId"
android:layout_width="1dp"
android:layout_height="1dp"
/>
Listen the event:
View dumbView = mainView.findViewById(R.id.theDumbViewId);
dumbView.addOnLayoutChangeListener(new OnLayoutChangeListener() {
#Override
public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
// Your code about size changed
}
});
Thank to https://stackoverflow.com/users/2402790/michael-allan this is the good and simple way if you don't want to override all your views.
As Api has evolved I would suggest this copy paste instead:
String TAG="toto";
///and last listen for size changed
YourView.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) {
boolean widthChanged = (right-left) != (oldRight-oldLeft);
if( widthChanged )
{
// width has changed
Log.e(TAG,"Width has changed new width is "+(right-left)+"px");
}
boolean heightChanged = (bottom-top) != (oldBottom-oldTop);
if( heightChanged)
{
// height has changed
Log.e(TAG,"height has changed new height is "+(bottom-top)+"px");
}
}
});
View.doOnLayout(crossinline action: (view: View) -> Unit): Unit
See docs.
Performs the given action when this view is laid out. If the view has been laid out and it has not requested a layout, the action will be performed straight away, otherwise the action will be performed after the view is next laid out.
The action will only be invoked once on the next layout and then removed.
Kotlin version base on #Michael Allan answer to detect layout size change
binding.root.addOnLayoutChangeListener { view, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom ->
if(view.height != oldBottom - oldTop) {
// height changed
}
if(view.width != oldRight - oldLeft) {
// width changed
}
}
Other answers are correct, but they didn't remove OnLayoutChangeListener. So, every time this listener was called, it added a new listener and then called it many times.
private fun View.onSizeChange(callback: () -> Unit) {
addOnLayoutChangeListener(object : OnLayoutChangeListener {
override fun onLayoutChange(
view: View?,
left: Int,
top: Int,
right: Int,
bottom: Int,
oldLeft: Int,
oldTop: Int,
oldRight: Int,
oldBottom: Int,
) {
view?.removeOnLayoutChangeListener(this)
if (right - left != oldRight - oldLeft || bottom - top != oldBottom - oldTop) {
callback()
}
}
})
}
If your like me and you came to this page and
addOnLayoutChangeListener gave you functionality you needed but caused an infinite list of "requestLayout() improperly called .... running second layout pass" messages to fill your logcat and ...
Adding removingOnLayoutChangeListener(this) broke the functionality you had in 1) despite fixing the repeated logcat message then this might work.
Its a mix of what Slion and Tony suggested.
Create a ResetDetectorCallback interface with abstract method
resizeAction.
Create a simple custom view ResetDetector with an instance of the ResetDetectorCallback, a setResetDetectorCallback mehtod, and override its onSizeChange() method to invok the resetDectectorCallback.resizeAction().
In the layout place an
instance of the ResetDetectorView with an id, width="match_parent", height="match_parent", and in my case I also constrained it.
Then in the code (mine was a Fragment since I'm adopting the Single Activity methodology) let that class implement the ResetDetectorCallback, set the resetDetectorView from that class's layout to use "this", and implement the resizeAction in that class.
And in my case its finally working the way I want now. On a Desktop emulator I can resize that Window as much as I want and it appears tobe accurately adjusting the layout accordingly to the way I want and the logcat is not being overloaded with "requestLayout() improperly called .... running second layout pass" messages.
There is probably a better way, perhaps with ViewModels and mutableStateObservers, but I'm not familiar enough with those yet. I hope this helps someone somewhere. And thanks to all the contributors above even if your technique didn't work for me.