In Android documentation, it says the following for addView (and a few other methods):
Note: do not invoke this method from draw(android.graphics.Canvas),
onDraw(android.graphics.Canvas), dispatchDraw(android.graphics.Canvas)
or any related method.
What's the reason for this restriction? Also, what are the "related methods"? For instance, is surfaceChanged such method?
It's because when rendering Views, the ViewGroup containing the Views goes through certain steps when mapping out where each view will appear on the screen. The steps a ViewGroup takes are:
Measure (the ViewGroup measures all of its child views)
Layout (the ViewGroup positions the measured children at their locations on the screen according to their measurements and ViewGroup.LayoutParams)
Draw (the views are drawn on screen).
Since the draw step is the last step when rendering Views on the screen, adding additional Views at this step of the process could potentially change (invalidate) the entire layout. If you take a look at the source code for ViewGroup you'll see that making a call to addView(View v) starts the entire layout process over again:
public void addView(View child, int index, LayoutParams params) {
if (DBG) {
System.out.println(this + " addView");
}
// addViewInner() will call child.requestLayout() when setting the new LayoutParams
// therefore, we call requestLayout() on ourselves before, so that the child's request
// will be blocked at our level
requestLayout();
invalidate(true);
addViewInner(child, index, params, false);
}
Also, when the ViewGroup draws it's child Views on the screen, it normally iterates through all of the Views currently added to the ViewGroup. So, if this type of event were to be dispatched during a draw method, it could potentially cause the ViewGroup to attempt drawing a View that hasn't gone through the necessary steps for rendering yet.
As for related methods, these would essentially be methods that would invalidate the ViewGroup's the current drawing operation.
Edit:
The SurfaceHolder.Callback methods are just Interface methods for tracking the state of your drawing surface or SurfaceView/GLSurfaceView. Since the state of the layout should remain constant until the Draw step has finished, there shouldn't really be a need to call this method at that point in the process. However, since these methods are basically Interface methods that YOU implement (they're empty by default in the source), there shouldn't really be a reason why calling one of these methods should cause an error, even though I'm not sure that it would be appropriate to do so. One case that would case an issue would be if your underlying implementation leads to issues like the one I explained above...
Related
We know ViewTreeObserver is used to register listeners that can be notified of global changes in the view tree. There are two method defined in this class are
addOnGlobalLayoutListener(ViewTreeObserver.OnGlobalLayoutListener listener) and addOnPreDrawListener(ViewTreeObserver.OnPreDrawListener listener). But the the purpose of these methods are not clearly understandable to me which described in the developer site. I tried to understand it and also searched in google but was unable to find clear picture about this topic. Any help is greatly handful for me.
OnPreDrawListener
Gets called just before onDraw() method gets invoked. At this point, all views in the tree have been measured and given a frame. Therefore you can properly manipulate view in this callback
OnGlobalLayoutListener
This listener gets called:
- when visibility state changes. In example when view has been drawn it becomes visible and this gets called.
- when you addView state of view tree changes
I am creating a TutorialManager that handles all the user tutoring for app usage.
I have a ViewGroup that adds views to itself including a GridView that is filled with items after onMeasure() is called.
I am calling TutorialManagers method that's supposed to perform an action on the gridview but it needs to know when the gridview is filled in order to use any of it items
I added a callback method on the ViewGroup that is called right after all the view have been added
The problem is that the TutorialManager needs to know the X and Y coordinate of the views in order to highlight them using ShowCaseView
I've added a workaround using postDelayed(action, 100); which seems to work on my handset but what if on some device the view layout takes more than 100ms? It'll screw up the whole tutoring system.
I can't figure out any other way than creating a custom view that has a callback method which is called after the views position is known.
What's the common way of handling the issue that the laying view on screen is happening asynchronously?
You can try using an OnGlobalLayoutListener which fires whenever there is a layout change (Ie, your GridView and children have been added and measured. So where ever you initialize the GridView do this:
LayoutListener mLayoutListener = new LayoutListener;
mGridView.getViewTreeObserver().addOnGlobalLayoutListener(mLayoutListener);
private class LayoutListener implements OnGlobalLayoutListener {
#Override
public void onGlobalLayout() {
...Do your calculations here...
mGridView.getViewTreeObserver().removeOnGlobalLayoutListener(this); //If you only need this to execute once.
}
}
Note this will be called quite often. I'd recommend removing as soon as you don't need it.
I am building an application where I need to display signal traces in real time (think the kind you see on cardiac monitors in hospitals). For my line animation to appear smoothly I'd like the frame rate to be around 15 frames per second (I did some experimenting to come to the conclusion this was the lowest acceptable frame rate). It probably does not matter in the context of this post but I potentially have numerous such view in a ListView (~20 is common with about 5 being displayed each time).
Until now I've been doing this using a custom view, extending the View class. I have a thread in the containing fragment that calls invalidate() on the view every ~70ms that redraws the view. This is not causing problems per se as I've optimized my onDraw() function to run in under 2ms most of the time.
Now I have added a Spinner to the fragment and while debugging it I noticed that once I opened the Spinner the adapter was constantly hitting getView() calls, even though I was not touching the Spinner (i.e. open but not scrolling) and also lagging a bit. This led me to realize that my whole fragment was being redrawn every ~70ms which to me sounds bad. Now the questions:
Is there a way to trigger onDraw() on a child view without it causing a redraw of the complete hierachy?
Should I just resort to a SurfaceView? (that would not cause a complete view hierarchy redraw, right?)
I've noticed that the SurfaceView is not HW accelerated. Does that matter if I'm only using basic draw functions (drawLine() and drawText())?
Would GLSurfaceView be any better in my case?
I'll try to answer my question so that it might be useful to other people who might encounter the same.
Containing a bunch of SurfaceViews inside a ListView is a fail. It seems to me since the SurfaceView drawing is not synced with the rest of the UI you will get black lines flickering between views when you scroll (which is probably since the SurfaceView is reassigned to new data source via getView() and displayed before it gets a chance to redraw itself). Triggering a redraw inside getView() was not enough, apparently.
Delyan's first comment to my question was valid even though it might not always apply. If I go through each view in the ListView by hand and call invalidate() on it the redraw will not bubble up through the hierarchy (I did not need the invalidate(l,t,r,b) signature, but it's probably a smart idea to try it out if you're having problems with excessive redraws). So, even though Romain Guy mentions in his talk that invalidate() will bubble up to the root it's apparently not always the case. Delyan's comment about different implementation of HW/SW might be the reason - I'm rendering my views in HW.
Anyhow, to work around the issue I created this method inside my adapter:
public void redrawTraces(ListView lv) {
final int viewCount = lv.getChildCount();
for(int i=0; i < viewCount; i++) {
final View stv = lv.getChildAt(i);
if(stv != null) {
stv.invalidate();
}
}
}
Where the views were not SurfaceViews. Finally, to make it clear what I was doing wrong, I was doing this:
adapter.notifyDataSetChanged();
This call did bubble up and caused a redraw through the entire hierarchy. My assumption was that this was roughly the equivalent of calling listView.invalidate() in terms of updating the whole hierarchy; perhaps that's correct I didn't look into it.
I am creating an android app that is essentially a dashboard with many gauges and indicators on it. I have a Main View which is added in the Main Activity's onCreate() method. Each gauge is a custom View object which is added and initialized in the Main View and drawn in the Main View's onDraw() method. The gauge values are constantly updated by individual threads that are launched in the Main Activity's onCreate() method. When a thread detects the need to change a gauge value, it does so, and then sends a message to the Main View (via a message handler) telling it that the particular gauge needs to be updated (redrawn). I am using invalidate() in the Main View's message handler to refresh the screen. The app runs well, but I am wondering if I am handling the display updates as efficiently as I can. My question is:
When I call invalidate() in the Main View, I assume it is redrawing all the gauge Views, even the ones that have not changed. If so, would it be more efficient to just redraw the View of the gauge that has changed? What is the best way to do that? I have tried calling the individual View's invalidate() and postInvalidate() methods but neither works.
As requested, here is the current state of the onDraw method in the Main View. Eventually there will be a lot more gauges added. The Bezels are static and only change if the device switches configuration (landscape/portrait). The various gauges are instantiated from a Gauge class which extends View:
#Override
protected void onDraw(Canvas canvas) {
speedoBezel.draw(canvas);
tachBezel.draw(canvas);
oilPressBezel.draw(canvas);
oilTempBezel.draw(canvas);
speedoGauge.draw(canvas);
tachGauge.draw(canvas);
oilPressGauge.draw(canvas);
oilTempGauge.draw(canvas);
}
Ok, I have read so many ways to animate in Android I started to mix them up. I would like to rephrase the question:
If I am developing an app with many different objects in the main view that changed frequently but at different intervals, is it better performance wise to:
Add the objects to the main view and use the invalidate(rect) in the main view to update them individually as needed, or
Add each object to a Layout and use invalidate on each view when they require updating.
Important to note that none of the objects overlap
I am populating a linear layout dynamically. Depending upon response, I have to clear the past child views and create new views. I have read the document, but still be confused with the couple methods, they all look the same function. Which function I should use.
As Scott Biggs points out, the difference is not a big one. The only difference is that removeAllViews() calls requestLayout() and invalidate() afterwards. The key to why this difference is here is to understand the naming of removeAllViewInLayout(). Confusingly, its meaning isn't "remove all views within this view layout."
If we look at the similar method, removeViewInLayout(), we can understand what it's supposed to mean:
Removes a view during layout. This is useful if in your onLayout() method, you need to remove more views.
So removeAllViewsInLayout() actually means "remove all views, and we're calling this method during a layout pass (i.e. onLayout())". That's why removeAllViewsInLayout() doesn't call through to requestLayout(), as it's assumed that you're already currently inside a layout pass, so requesting another layout pass is unneeded.
If you use removeAllViewsInLayout(), then it's your responsibility to ensure that you're calling this during a layout pass, or to properly call requestLayout() and invalidate() as needed.
removeAllViews() : Call this method to remove all child views from the ViewGroup.
removeAllViewsInLayout() : Called by a ViewGroup subclass to remove child views from itself, when it must first know its size on screen before it can calculate how many child views it will render.
Well, looking at the source, there isn't much difference:
public void removeAllViews() {
removeAllViewsInLayout(); // Details implemented here
requestLayout();
invalidate(true);
}
So unless you want to call invalidate() at a time of your choosing, you might as well use removeAllViews() and save yourself a bit of typing.
EDIT
For a more detailed explanation, see David Lui's answer. To sum it up, use removeAllViews() unless you're in the process of constructing a View--in which case you'd call removeAllViewsInLayout().