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
Related
This question came to my mind when re-binding data to views in RecycleView. Before selectively applying UI changes to the views, I usually reset them all to their default states.
Such as
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
/* Reseting to normal/default state */
holder.title.visibility = View.VISIBLE
holder.poster.visibility = View.VISIBLE
/* Applying data */
if (data.poster.url == null) {
holder.poster.visibility = View.GONE
}
}
Here the poster View has potentially its visibility changed to VISIBLE and GONE again within a very short time interval.
Does Android actually invalidate and request drawing for both visibility changes? For example, if before binding, holder.poster view was GONE, then Android would draw it visible and then gone again?
Or does it batch them and only execute the latest state? For example, if we want the app to run at 60fps, it might batch UI operations in 16ms intervals. I can change Visibility hundreds of times, but it will actually draw the very last state within that 16ms batch.
The 2nd point holds true, but for a much more simple reason in your use-case.
The onBindViewHolder method runs on the main thread. Thus no other operation is possible on that thread until it returns.
Every change to a view property might invalidate the view. Layouting and drawing happens once, after onBindViewHolder has returned.
Yes, ViewHolders are recycled. That's the reason for their existence: so that expensive-to-build ViewHolders can be recycled to display different items. Yes, you need to zero out any changes to the contents of the ViewHolder that may have been made while an item was bound to it.
Rendering is also batched. Any change you make to a View property invalidates rendering or layout (as appropriate), and layout and rendering passes occur later. Property changes on views ultimately make calls to RequestLayout(), and/or RequestRender() to schedule layout and/or rendering passes at a later time. There is virtually zero cost for the second and subsequent changes.
I'm not quite sure when "a later time" is, but there's no delay involved. Right away, for all practical purposes, but batched. Something along the lines of process all queued messages, and when there are none, start a layout pass if it's been requested.
Rendering then takes place in a separate pass, after the layout pass has taken place.
Recycler view actual does a LOT of work figuring out where items were displayed, where items will be displayed and animating between the old and new location. This occurs in the layout pass, after you have notified the RecylerView that items in your adapter have changed. But it's done in a batch. You can toggle away on visibility to your hearts content. Only one render pass, and one layout pass takes place. (That's a sight simplification; atypically for an Android view, internally, RecyclerView, actually executes two layout passes, but that's not something you need to know or are supposed to know. It's a hack. I THINK you're being called during the first layout pass).
With respect to toggling bits and pieces in an onBindVieHolder call... rest assured that layout and rendering for the contents of the ViewHolder have been thoroughly and completely invalidated by the time you get the call. You are about to display something else, somewhere else. So go to it!
According to concept of recycling items mechanism in ListView.
I want to know, actually, how i can detect a moment when a View going off-screen.
I explain why.
In most cases ListView have a custom
Adapter(? extends ArrayAdapter / ? extends BaseAdapter, etc.).
getView(...) method allows to manipulate visibility and content of views (text,bitmaps,drawables,etc.)
And in some cases i need to launch a separate Thread which doing background work, and after that update UI. Actually - using AsyncTask.
When i have many items in ListView each call of getView will be produced start a new Thread. I need to cancel them if View is no more longer present on the screen. How to do this?
You can override onDetachedFromWindow for the view.This will let you know when the view is going off-screen.The docs says:
protected void onDetachedFromWindow ()
Added in API level 1 This is called when the view is detached from a
window. At this point it no longer has a surface for drawing.
See Also
onAttachedToWindow()
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.
Will ever crash an application that changes text, background resource and inflate some views in it in a background thread, if the view's visibility is GONE ? I've done some tests with two buttons, one visible and one gone and if I changed text of the visible one in background thread, it crashed and when I've changed the text of the button with GONE visibility, it worked without a crash. Can someone explain this?
Ok, I have a something for you.
Event Handling and Threading
The basic cycle of a view is as follows:
An event comes in and is dispatched to the appropriate view. The
view handles the event and notifies any listeners.
If in the course of processing the event, the view's bounds may need
to be changed, the view will call requestLayout().
Similarly, if in the course of processing the event the view's
appearance may need to be changed, the view will call invalidate().
If either requestLayout() or invalidate() were called, the framework
will take care of measuring, laying out, and drawing the tree as
appropriate.
Note: The entire view tree is single threaded. You must always be on the UI thread when calling any method on any view. If you are doing work on other threads and want to update the state of a view from that thread, you should use a Handler.
You can find much more here http://developer.android.com/reference/android/view/View.html .
In your case Your View is already GONE so I think its not attached to View Tree.
You have to create an Handler!
In that handler put your code to update your UI and in the thread call the Handler!
You should never update the UI from an thread directly in none of langages! it's a big error in programming!
Example to create your handler:
static Handler updater1 = new Handler() {
#Override
public void handleMessage(Message msg) {
//your code to update the UI
}
};
Example to call your handler from the thread:
updater1.sendEmptyMessage(0);
The behaviors about to change the text from a thread can be different in all langages! but you should never do that it's a big error!Maybe when you tried to update a text on a button with visibility "GONE" it just not worked! but you can't see it!