I have a fragment and I need to measure location/width/height of its views on screen and pass to some other class.
So what I have is a function which does it, something like this :
private void measureTest(){
v = ourView.findViewById(R.id.someTextField);
v.getLocationOnScreen(loc);
int w = v.getWidth();
...
SomeClass.passLocation(loc,w);
...
The problem is that the location/width/height of views is not ready within fragment lifecycle.
So if I run that function within these lifecycle methods :
onCreateView
onViewCreated
onStart
onResume
I either get wrong location and width/height measurments or 0 values.
The only solution I found is to add a GlobalLayoutListener like this to mainView
mainView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
public void onGlobalLayout() {
if(alreadyMeasured)
mainView.getViewTreeObserver().removeGlobalOnLayoutListener(this);
else
measureTest();
}
});
this gets the job done.. but its just Yack! IMO.
Is there a better way of doing this? seems like such a basic thing to do
inside onActivityCreated of your fragment retrieve the currentView (with getView()) and post a runnable to its queue. Inside the runnable invoke measureTest()
There is no better way. That code isn't that bad! It's fired as soon as the view is layed out (my terminology might be a bit weird there) which happens right after measuring. That is how it is done in the BitmapFun sample (see ImageGridFragment, line 120) in Google's Android docs. There is a comment on that particular piece of code stating:
// This listener is used to get the final width of the GridView and then calculate the
// number of columns and the width of each column. The width of each column is variable
// as the GridView has stretchMode=columnWidth. The column width is used to set the height
// of each view so we get nice square thumbnails.
Related
I have a headache in current Android project. I want to detect the changing of the current page. For example, there is a TextView to display device time, which is updated per second. How to detect this change? I searched a lot on SO (thanks SO), but none works for me.
More information: I don't use standard Activity to create page. My way is:
All widgets are created into a View object which is then used to create a container object. After that, I just handle this container to draw on a canvas with a VSYNC callback Choreographer.FrameCallback periodically.
Indeed, it works to draw. All are ok. Except: I want to draw canvas only when the page's content changed. So back to my beginning question, how to detect this "changing" event? I am sure there is some kind of callback to handle this problem. I used the following solution, but onGlobalLayout is not called when textview's text changed.
CanvasAppViewContainer container;//CanvasAppViewContainer extends AbsoluteLayout
LayoutInflater li =(LayoutInflater)getService().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View view = li.inflate(resourceId, null);//passed a correct the layout id
container = new CanvasAppViewContainer(getService(), view, getWidth(), getHeight(), getSurface());
rootView = (LinearLayout) findViewById(R.id.rootView); //root element of layout
rootView.getViewTreeObserver().addOnGlobalLayoutListener(
new ViewTreeObserver.OnGlobalLayoutListener() {
#SuppressLint("NewApi")
#Override
public void onGlobalLayout() {
Log.d("test", "onGlobalLayout");
}
});
BTW: Even if I register the view tree observer for the textview, onGlobalLayout is still not called.
Thanks
onGlobalLayout() is only called when the layout changes. When you want to detect a change in the text use TextView.addTextChangedListener()
I am trying to do calculate view's x,y positions after completion of loading of activity. What I did is view.postDelayed(runnable, 2000) which is working fine. code reviewer is not happy with this and suggested to use OnGlobalLayoutListener to know about the completion of activity loading. Somehow I don't like OnGlobalLayoutListener because it is associated with entire view tree which is not required for my solution. I am trying to understand pros and cons of these approaches. Thanks!
If all you are trying to do is read the view's x and y coordinates, I recommend using view.post(Runnable) with no delay (unless there is a good reason to include a delay). This will add the Runnable to a message queue to in the UI thread. The Runnable will wait to execute until after your View is inflated and attached to the window. Since View position property values depend on the view's layout context, posting a Runnable will give you the timing that you are looking for.
As you mentioned in your question description, an OnGlobalLayoutListener will apply to the entire View's layout as the class name suggests. An OnGlobalLayoutListener should only be considered if you are concerned with the layout state or visibility of any or all views within the view tree. I.e. anything that causes the view tree to be re-laid out.
Code reviewer is not happy because you wait 2s and guess that the loading of the activity is finished by then. This may be the case with your emulator or device but on older and slower devices the activity may not have finished loading. To be 100% safe that the activity has finished loading you should use the listener to inform the 'listener' when loading is completed.
I think that the correct way of doing this is by adding an onPreDrawListener to the view. This is the listener that is called when the view is about to be drawn, and where you already have all the information about it's size (width and height) and position (X and Y).
Example:
final View v = new View(getContext());
v.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
#Override
public boolean onPreDraw() {
v.getViewTreeObserver().removeOnPreDrawListener(this);
float myX = v.getX();
float myY = v.getY();
return true;
}
});
Don't forget to make the method onPreDraw return true, I don't know why but Android Studio makes it return false when you create the listener.
Also don't forget to remove the listener from the view, otherwise it might be uselessly called again.
I am facing following problem:
I got 4 Buttons over the screen, which can be dragged and move back to their original position when dropping the button.
To do so I want to read out the specific position of the button (on the specific device) by using the getX() and getY() function and writing the results into an array.
buttons[0][0] = button.getX();
buttons[0][1] = button.getY();
As I cannot use these functions in the onCreate or in the onStart (the position is always (0,0) cause the view is not instantiated yet I am thinking about how to read out the original position without any extra coding.
Thanks a lot
In your onCreate—though onResume may make more sense—get a reference to a view that is an ancestor of all the buttons (or just the root of the activity, which may be easiest) and then do this:
rootView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
rootView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
// Read values here.
buttons[0][0] = button.getX();
buttons[0][1] = button.getY();
}
});
not sure what you mean by "extra coding" but you could use an OnTouchListener (or modify the one you're already using, if that's the case) to capture the position as soon as they touch the button, but before they drag it anywhere (I think that would be ACTION_DOWN).
This has the advantage of only being set when you need it to, skipping it when they don't actually drag the buttons, and not calling it for every layout pass, etc.
I know there are a lot of similar questions, but suggested solutions don't work in my case.
What is my problem.
I have fragment nested inside activity.
Fragment layout includes ViewPager.
What is my idea ? As far as my application won't support landscape (this can cause some additional changes), my ViewPager is loading images from the network.Images can be quietly large and heavy, so I have written server side API which helps to convert image to the desired size on the server and return resized image url.
This will help to get rid of OutOfMemory errors.
But the problem is that I need to send ViewPager's height and width in my request.
I am finding it by id in onViewCreated
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
mBannerSlider = (AutoScrollViewPager) view.findViewById(R.id.banner_slider);
mBannerSlider.getViewTreeObserver().addOnGlobalLayoutListener(
new ViewTreeObserver.OnGlobalLayoutListener(){
#Override
public void onGlobalLayout() {
mSliderHeight = mBannerSlider.getHeight();
mSliderWidth = mBannerSlider.getWidth();
Toast.makeText(getActivity(),"View " + mSliderHeight + " " + mSliderWidth,Toast.LENGTH_SHORT).show();
}
requestBannerImages(mSliderHeight........);
});
The problem here is that this method is called on every page change.
Of course I can use some helper variable to determine if it is first time or not.
Also there is way to send callback from activity to the fragment using this activitiy's method.
#Override
public void onWindowFocusChanged(boolean hasFocus) {
// TODO Auto-generated method stub
super.onWindowFocusChanged(hasFocus);
//Here you can get the size!
}
What is the best solution in this way, maybe I can do this better in another way.
Thanks in advance.
EDIT
I have a plenty of view where I need to get dimensions, so I need to add listener to every view and that after first measurement remove it ?
I would do it with the tree observer. To avoid repeated calls I would remove the listeners once i get the value. This assures you that you will get the dimensions and it wont get call anymore.
if (Build.VERSION.SDK_INT < 16) {
v.getViewTreeObserver().removeGlobalOnLayoutListener(listener);
} else {
v.getViewTreeObserver().removeOnGlobalLayoutListener(listener);
}
I took the code from: https://stackoverflow.com/a/16190337/2051397
I understand your dilemma, mainly because I have implemented a multimedia app. However setting dimensions in every view or many UI elements related to icons/images is time consuming. If not so, then you end up with lots of code doing the same stuff.
In my opinion, you don't have to set dimensions for every view, for the sake of looks. You may set max height of a view/UI or/and set setAdjustViewBound to true, let the UI framework handle the images. There is a cost of performance though if the image is large, in your case probably not.
You may refer to Google notes on setMaxHeight of ImageView. Let me know of your view on this, interested to know.
In my application I have an infinite loop on one of my View's onMeasure() overrides. Debugging the source code starting from a break point in my onMeasure, I am able to trace myself all the way up the stack trace up to the PhoneWindow$DecorView's measure() (top most class in my View Hierarchy), which gets called by ViewRoot.performTraversals(). Now from here if I keep stepping over, I eventually get the PhoneWindow$DecorView's measure() called again by a message in the Looper.loop() class. I'm guessing something has queued up a message that it needs to remeasure, like an invalidate.
My question is, what triggers that a measure call needs to occur on a View?
From my understanding of the layout/measure/draw process, this will only occur when the invalidate() method is called on a specific view, and that will trickle down and perform a layout/measure/draw pass for that view invalidated and all of its children. I would assume that somehow my top most View in my View Hierarchy is getting invalidated.
However, I've explicitly put a break point on every single invalidate call I have, and am not calling invalidate in some infinite manner. So I do not think that is the case. Is there another way to trigger a measure pass? Could something internally be triggering this? I'm kind of out of ideas after seeing that nothing is infinity invalidating.
In order to trigger a measure pass for a custom View you must call the requestLayout() method.
For example, if you are implementing a custom view that extends View and it will behave like a TextView, you could write a setText method like this:
/**
* Sets the string value of the view.
* #param text the string to write
*/
public void setText(String text) {
this.text = text;
//calculates the new text width
textWidth = mTextPaint.measureText(text);
//force re-calculating the layout dimension and the redraw of the view
requestLayout();
invalidate();
}
Well, If you are changing a View's content, it will eventually have to call invalidate(). For example, you have a TextView with a text called "Text 1". Now, you change the text of the same TextView to "Text 2". Here aslo, invalidate will be called.
So basically, when something changes on the view, more often than not, you would expect the invalidate method to be called, and a corresponding call to the measure().
Look at the source code for TextView, for example.
http://www.google.com/codesearch#uX1GffpyOZk/core/java/android/widget/TextView.java&q=TextView%20package:android&type=cs
Count the number of invalidate calls. There are quite a few.