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).
Related
So we are supposed to use the recycler view when we want to show a large list of elements.
The benefit of that is that views are re-used so we don't inflate each view in the list and keep them in memory and just keep in memory those that are displayed in the screen recycling the rest.
But what if the recycled view is itself a "mini" list?
I.e. for each item recycled we change its structure by removing all child views and adding new child views?
Is that defeating the whole idea of using a recycled list?
Example in question (itemInRecycler is a vertical LinearLayout that is passed to the recycler view holder):
itemInRecycler.removeAllViews();
for(element: elements) {
CustomView view = inflate();
view.setDisplayData(element);
itemInRecycler.addView(view);
}
Is that defeating the whole idea of using a recycled list?
It depends on how much of the whole itemView the sub-list is. If the sub-list is the only thing you're displaying, then yes, you're defeating a large portion of the performance gains. You're still inflating views every time you bind the ViewHolder, and that's one of the things you try to avoid when using RecyclerView.
It also depends on how large the sub-list is. If it's only three elements at most, then the cost is diminished. If the sublist is hundreds of items, then the cost is large.
One option is to use a sub-RecyclerView instead of a LinearLayout, and to connect each sub-RecyclerView to the same shared RecycledViewPool. This way you get all the benefits of RecyclerView vs a scrollable LinearLayout, but even better because each sub-RecyclerView can take ViewHolders from the others.
If that's a little too heavy-handed for you, you could potentially just optimize the code you've already written. Instead of clearing the list each time and then re-inflating the correct number of views, you could re-use existing views (and only create new ones if there aren't enough) and remove the extra views if there are too many.
int i = 0;
// reuse existing views
for (; i < elements.size() && i < itemInRecycler.getChildCount(); i++) {
Element element = elements.get(i);
CustomView view = (CustomView) itemInRecycler.getChildAt(i);
view.setDisplayData(element);
}
// create new views if there weren't enough to reuse
for (; i < elements.size(); i++) {
Element element = elements.get(i);
CustomView view = inflate();
view.setDisplayData(element);
itemInRecycler.addView(view);
}
// remove any extras after we've reused everything
int viewsToRemove = itemInRecycler.getChildCount() - i;
if (viewsToRemove > 0) {
itemInRecycler.removeViews(i, viewsToRemove);
}
In a world where the elements collection always has between five and seven elements, say, this approach will make sure that you're always re-using the first five CustomViews, and then only inflating or removing 0-2 extra CustomViews.
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.
I'm dynamically adding Views to my items in a RecyclerView. These added Views should only be related to the item which they're added to, but I'm having a problem when I scroll. It seems the View is recycled and a new item is loaded, but those previously added views are still there, only now on the wrong item.
I'm assuming that it's just because the ViewHolder is being reused, so the added items show up again with a new item, when loaded.
How would one go about solving this?
This was an old question of mine. A bounty was placed on it, hence the surge of popularity and the multiple new and irrelevant answers.
As stated in both my comment to this answer and #CQM's comment below my original question, the answer is to override the onViewRecycled() method and perform any needed operations there. This method is called when a view is recycled, and any cleanup operations can be done here.
Documentation on this method can be found here.
In my case, it was a matter of deleting the invisible TextView's attached to the view. The text itself had been deleted, but the view remained. If many invisible TextView's accumulate on the view and aren't properly recycled when scrolling, the scroll will begin to lag.
You need to track what views have been added based on the backing data. I would probably add any necessary extra views in onBindViewHolder(), and remove any that might be present in onViewRecycled(). Then when you want to make one appear dynamically, change whatever variable you have tracking whether it should be visible, and call notifyItemChanged().
Based on this:
but those previously added Views are still there, but now on the wrong item.
Basically, as per the RecyclerView documentation, You have to reset the views everytime inside the onBindViewHolder() method,
so let say, you have a method that sets a view param if its your profile, so the code for the same goes as follows,
if (list.get(position).getId()==PreferenceManager.getUserID())
{
// do some view change here
setViewParam(true);
}else
{
// reset the view change here
setViewParam(false);
}
So what you're doing here is giving recycled ViewHolder a chance to reset.
Do comment if you need help!
You can use this! setItemViewCacheSize(int size)
Check here RecyclerViewDocumentation.
The offscreen view cache stays aware of changes in the attached adapter, allowing a LayoutManager to reuse those views unmodified without needing to return to the adapter to rebind them.
First of all, can you share some more code please?
Second, why would you want to dynamically add new views on fly? Why don't you use different VIEWTYPE or just have those view already on your layout and just make them visible/invisible or visible/gone? (I believe it will be more efficient this way).
Let me remind you something about RecyclerView, yes when user is scrolling viewHolder are being reused (few of them can be created, even more than it needs to fill the screen). So if it happened that you added some views on "item A" and user scroll to "item Z", that viewHolder can be reused for that "item Z", hence the show up of the previously added views.
How can you solve that?
Well always check on every items if you need to add new views, if yes add them if not already added, else always remove those views (if present) to return to default viewHolder state (or whatever you call it).
Hope this will help you.
Save Information by tags for items with new child each time the Add newView operation occur. (In shared preference for example)
Tag: create with item position onBindViewHolder.
...
SharedPreference sharedPref = getSharedPreference("text" + position, context);
SharedPreference.Editor editor = sharedPref.edit();
editor.putString("view", "ImageView");
...
when load Adapter get this value and put default as null.
I am not sure about its efficiency but i will work.
...
String viewType = sharedPref.getString("view", null);
//it will return ImageView
if you know some possible viewTypes for example always going to be ImageView & TextView so with some if statement it will be ok.
if(viewType.equals("ImageVIew")){
item(position).addView(new ImageVIew(context));
}
Good Luck
In your adapter class of your recyclerView,
in the onBindViewHolder method,
create another adapter and do the same methods for your new adapter.
The hierarchy will be,
mainRecyclerView -> item1(->childRecyclerView1) , item2(->childRecyclerView2), item3(->childRecyclerView3)
This way you can achieve what you want without wrong values to be viewed on wrong items.
You should take any Empty Layout like Linearlayout in your child item layout XML and then add views into that LinearLayout of your particular item in this way when you scroll List all of you child views which you have added to LinearLayout also scroll with that item .
My problem is similar to ListView getChildAt returning null for visible children, but despite searching I cannot find a solution.
I have a ListView with a Scroll. The ListView has 10 items, 7 of which are visible and 3 are hidden by scroll. I also have an external method (out of adapter) that must get all of the children from this ListView (e.g. using getChildAt()).
I need all 10 of the items, but the last 3 are null objects. I've tried code like the following:
getListView().smoothScrollToPosition();
But this doesn't work.
I think that I don't need to post the rest of my code, as the description says everything?
As you have already seen you can't get all the child row views from a ListView simply because a ListView holds only the views for the visible rows(plus some recycled rows but you can't reach those). The correct way to do what you want is to store whatever data in the adapter's data and retrieve it from there.
But the ListView doesn't keep the current values from RadioGroup in
running time.
I've seen that you have some problems with this so I've adapted some old code to build a basic example, code that you can find here.
I don't think so you need to add scroll view for a listView. Scroll automatically works on ListView. Try your application without adding scroll view and I'm sure it'll work as you needed.
The reason those children are null it's because they really do not exist and they will never exist, if only 7 children are on the screen at one time, the system will only create 7 and re-use by passing the convertView back to the adapter getView() method.
If you want to grab information regarding your whole dataset you should search on the dataset itself, instead of the views on the screen. E.g. if it's an ArrayAdapter, loop the array; if it's a CursorAdapter, loop the cursor; etc.
The non-visible children of a listView don't actually exist. When they become visible, one of the redundant views is recycled or a new view is generated. So you can't actually access all the views. Why do you want to? Whatever changes you want to make should be made to the data that populates the views rather than the views themselves.
There are a few point that you need to take care of:
1. List view provides inbuilt scroll functionality, So don't use Scroll view. It will only mess up things.
2. List view doesn't contain ALL the children. When you scroll it, it creates only visible items on run time.
3. If you want to get all the children altogether, Better keep an ArrayList of the child objects that your list has. You can add or remove children to this ArrayList as per requirement.
my viewpager currently only re-renders views that are two views away from what is visually seen.
example (shown respectively)
ViewA, ViewB, CurrentView, ViewD, ViewE
not rendered, rendered, rendered, rendered, not rendered`
how would I force it to reload a particular view X many views away from the currentView, there are some use case scenarios where I want it to, and other cases when I dont want it to. But currently I only know how to reload the entire adapter - where it perhaps pulls from an arraylist
I'm really not sure how to control the Viewpager - very few examples out there. All I know how to do is reset the entire list on "notifyDataSetChanged"
insight appreciated
use this method mViewPager.setOffscreenPageLimit(2) and you can set the limit of offscreen pages
I think your going too need to decouple your view and data logic (array list in this case?). If you don't want the view to change, don't change the data layer. When you call notifyDataSetChanged everything will mimic your data layer. If you want a view to change, change the data layer and call notifyDataSetChanged.
To put it simpler, the view just draws what the data tells it to, and expect that the view could update at any time. It is very similar to how listview works.