Does View gets redrawn when property programatically changed to same value? - android

Let's have some View in fragment (e.g. LinearLayout or ImageView). If I change property of this View programatically (e.g. change backgroundColor) to same property that is already set, is view redrawn/rerendered?
Example of double set:
binding.someLinearLayout.setBackgroundColor(redColor)
binding.someLinearLayout.setBackgroundColor(grayColor);
binding.someLinearLayout.setBackgroundColor(grayColor);
binding.someLinearLayout.setBackgroundColor(grayColor);
LinearLayout will be redrawn twice or 4 times? If 4 times, should I implement something like this to prevent it?
fun setBackgroundColor(ll: LinearLayout, color: Int){
val current = binding.progressBarChunk4.background as ColorDrawable
if(current.getColor() != color)
ll.setBackgroundColor(color)
}
setBackgroundColor(binding.someLinearLayout, redColor); // Color set
setBackgroundColor(binding.someLinearLayout, grayColor); // Color set
setBackgroundColor(binding.someLinearLayout, grayColor); // ignored
setBackgroundColor(binding.someLinearLayout, grayColor); // ignored
So basically I am asking if multiple set of same property affects performance, because view gets redrawn everytime.

If you call setBackgroundColor four times in a row like that, it doesn't cause four redraws. The view is only redrawn one time on the next loop of the main thread, after your current function has returned.
Changing these properties usually does invalidate the view, so your first call to setBackgroundColor will trigger a redraw on the next loop of the main thread, even if it matches the existing color. I can't guarantee that's true of every existing property in the core Views, because I haven't checked them all. It is definitely true for setBackgroundColor and setText.
Your defensive check of the current color could potentially prevent an unnecessary redraw, but only if you ultimately don't mutate any views in this iteration of your main thread. I can't think of a case where it would be worth worrying about. If this is in onCreate() or onViewCreated(), the view hasn't been drawn yet anyway. If this is in a click listener, your view is getting redrawn anyway because your button's visual state is changing.
It might be worth doing defensive checks for changes that affect the size of the view, because then you could be preventing a re-layout. But probably only if this happening during some animation, because otherwise the savings would not be noticeable.

Related

Does Android batch UI operations or re-draw every single command?

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!

How to iterate across views & remove views based on condition

Currently I want to do something like this
FrameLayout parent = ...
for (childView in parent)
{
if (satisfyPredicate(childView))
{
removeFromParent(childView);
}
}
Unfortunately, I can't seem to figure out how to do this in android (since I am mutating the list as I go)
Requirements:
Cannot remove all views and reinflate all views (views contains pictures and such an algorithm would cause the UI to flicker oddly)
First off- if you want to walk a list and mutate it, you can easily. Just use a do, while, or old style for loop from i=0 to parent.getChildCount()
instead of a foreach loop or an iterator.
Secondly- you probably don't want to remove the views, especially if you may see them later. You probably want to set their visibility to GONE (GONE views are not drawn to the screen and do not take up space, but will remain in the view hierarchy).

What is the purpose of FlatViewManager in react native android?

As far as my understanding, FlatViewManager would flat all its children and itself as a single view called FlatViewGroup. But then I found RCTTextManager return RCTText as its shadow nodes and RCTText is not a virtual node. So RCTTextManager will return FlatViewGroup as its viewInstance? And will there be view hierarchy in FlatViewGroup? I am not sure what the meaning of flat is.
Your understanding is correct, but there is something you are missing.
FlatViewGroup doesn't do or draw anything by itself. You can notice that every View is actually a FlatViewGroup (Text, Image, View - everything maps to a FlatViewGroup).
But how can a generic FlatViewGroup draw images, text, borders and more? Thing is, it doesn't know about any of that.
The way it works, View, Image and Text prepare content in background thread, optimize it and try to use as little resources (Android Views) as possible. In many cases, it can fit hundreds of elements into a single FlatViewGroup.
But in some cases, more Views is better. For example, it there is an element that you mutate a lot, it is best if that element is moved out into its own View so that when that element changes we can only redraw that element only.
There is flag in every FlatShadowNode calles mMountsToView that controls whether we can flatten the element into a parent.
So when a FlatShadowNode does have mMountsToView flag set to true, which View should we use? Well, this is where the ViewManager.createViewInstance() kicks in.
Now I can answer your question. Yes, RCTViewManager will return FlatViewGroup for an RCTText, but not for every RCTText. Most texts will have mMountsToView set to false and thus get flattened into a parent.
The thing where you got confused is the virtual node vs mounts to a View.
Virtual node is a Flexbox thing, it means that the node is not an independent one, and cannot be measured. This has a side effect that it is always merged with a parent non-virtual node. This is regardless of Flat implementation.
Flat UI Manager takes node flattening to a whole new level. It adds this mMountsToView flag that controls if the node will be flatten further. Set it to true if the node mutates a lot and causes its siblings to redraw for no reason.
To help you understand a little bit more, imagine that you want to optimize RCTText. You noticed that it sometimes mounts to a FlatViewGroup, but since it cannot contain any children, it doesn't have to be a ViewGroup. You could add a FlatView (or even something more specialized, like FlatTextView), copy the contents of FlatViewGroup into it, remove all children management logic, modify RCTTextManager to return your View and it will work. If you rerun your React, you will notice that some texts are now using your new Views, but only those that have mMountsToView flag set to true (you can trigger it by e.g. setting text opacity to 0.99). The rest will be flattened into parent which is likely still using a FlatViewGroup.
Hope that helps.

Animate a dynamic number of Views and then set them invisible/gone

In an Activity, I am retrieving data from a server. There are a limited number of TableRows containing further Views pre defined in the layout xml file, all set to android:visibility="gone". Further there's an array in the java class with all IDs of these TableRows.
For each entering "data entry", I fill such a TableRow, set it visible and animate it (the animation is a 'push in' from a randomly choosen vertical side). As an further hint, which is important later in this question, every data packet retrieving from the server is related to a month.
Whenever I get data, I do following process
Fill the still invisible TableRows with the information
For every time step 1 is executed, a value in a boolean array is set to true
call showAnimation()
for every entry that is true in the boolean array, get TableRow at index i in the id array, set an Animation and set visibility to visible
This works fine. Even if the visibility is set before starting the Animation, it doesn't matter, since this takes so few time, that the Animation has already begun in our eyes.
Now comes the problem: the reverse way doesn't work. Let me clarify: I want, that the TableRows are pushed out of the window, and THEN set to invisible resp. gone whenever I change the actual month, and the new entries are pushed then in. The problem is, that as soon the Animation starts, the Views are set to invisible / gone. I tried to solve this with an AnimationListener like explained here in the first answer. This leads to two possible ways:
Define such an AnimationListener for each TableRow and set the visibility in the onAnimationEnd(). I find this is very ugly, since it is not dynamical.
Have access to a kind of index in the AnimationListener and set the same Listener to every TableRow's Animation. Here I don't know how possibly solve this. As I discovered earlier, setting Animations on Views set in a loop (a for loop in my case) results in the Animations beeing started when the loop has finished, and not whenever it is set. So even if I would carry a global index variable in that for loop to be used in the onAnimationEnd(), it obviously would came to nothing.
Do someone knows a better way to implement this? Solving it with a Handler.postDelayed() does not do the trick, since it is possible that the new data has already come from the server, and therefor the new entries are already visible (and thus the new ones are set to invisible).
ok I thought a lot about it and honestly I agree with you that it's not the nicest way, but due to the framework limitations the 1st way is still the best one.
It will be nice if we could from the listener call back do:
public void onAnimationEnd (Animation animation){
animation.getView().setVisibility(View.Visible);
}
// but this doesn't exist.
Just make sure every time you're setting an animation to start to do:
Animation anim = row.getAnimation()
if(anim!=null){
anim.setListener(null); // to avoid the listener to be called
anim.cancel(); // to stop it from running
}
// and then go ahead and setup a new animation with new listener
anim = new MyAnimation();
anim.setListener(new SetToInvisibleListener());
row.startAnimation(anim);

Issue with Android's ListAdapter.getView() view reuse behavior

I have a ListView that displays a list of items, each of which has an icon and a few bits of text.
I am making use of the "convertView" parameter of the ListAdapter.getView() method, altering an existing view rather than creating a new one when the parameter is non-null.
I had expected the ListView to recycle old views only after they had scrolled out of the visible viewport, but this appears not to be the case. It appears that the ListView is providing the same object in the "convertView" parameter on each invocation of ListAdapter.getView(). The single view is rendered to the screen, and then sent in again on the next call to getView().
This poses a significant problem for me, as I wish to modify previously rendered views. I have a background thread retrieving the icons for items, which takes "considerable" time and would be an unacceptable user interface burden to place within the ListAdapter.getView() view rendering code.
Is there any means to make the ListView not reuse views which are currently displayed on the screen? I'd like to realize the performance/efficiency gains of view reuse and be able to load the icons in a background thread.
I think you're having a similar problem I had for a while: Old items visible a while, in listview or gridview, when recycling
You have to "reset" the recycled items of the list at the beginning of getView(). Set them back to progress bar, or invisible, whatever the initial state is. Until they fetch the correct data.
I've found the issue is that Android is creating an additional, never-rendered temporary view for use in layout/measurement under certain conditions. My assumption that this view was rendered to the screen was not correct.

Categories

Resources