For the development of my app, I realized I needed a complicated view (let's call it foo), it contains three ImageButtons, a progress bar, and three TextViews, all of which are dynamically changed by interacting with the same view's elements. To make this work, I extended foo from RelativeLayout, dynamically created the sub-views then added them to foo (this.addView(...)).
What I planned to do next was add them dynamically to a ScrollView. I did this and put three foos for testing. The result was extreme lag. I'd press an ImageView (which should change its image on press), and it would take 2 seconds to do so.
My final aim would be to support 50 of these foos at a time and have them work smoothly, with the user having the option of loading more (without overwriting the previous ones) if he/she so chooses. All interactions will use the internet (I dunno if that's relevant), but the testing was done with all the network tasks commented out.
My questions are thus:
Is the strategy I was using (ScrollView & add foos to them) viable, and the lag is from some other issue (the specific code in question, in which case I'll provide some code)? Or is it really a bad idea to do that?
What would be the best way to reach my goal here (assuming 1 is bad)?
What I already know:
I've researched my problem a bit, and most online sources recommend using a ListView. I didn't read much into it but from what I got:
I'd have to redo the design using xml rather than dynamically
The different components and their values will be stored each on it's own array which is extremely unacceptable in my situation (changing the sub-view's values should be done very simply and should not appear in the main activity)
I can't (or it's difficult to) set OnClickListener's for the different sub-views (as only the main foo view will get one)
I also tried this method (ScrollView and add to Views) with another View and had 20 of them run at the same time seamlessly, but that one had been extended from View and only used canvas to draw text with no sub-views.
Thanks in advance.
Related
I have a rather complicated List with nested RecyclerViews. I get it that nested RecyclerViews aren't the best solution, but in my case it is one of few solutions that create structured code and meet the requirements. I have attached an image of the structure. You can take telegram as an example to improve your understanding of the structure. Basically I have an outer RecyclerView RV-1 with Items RV-1-Item and an inner RecyclerView RV-2 with Items RV-2-Item. So far so good, my problem is that the outer RecyclerView recycles views as intended, but if one of the RV-1-Items comes into view, all ViewHolders of RV-2 are created (That means that sometimes more than 100 ViewHolders are created). To sum it all up my question is how to force the inner RecyclerView RV-2 to recycle ViewHolders as well.
I know that the inner RecyclerView RV-2 has to have a hight of wrap_content because it depends in the count of the inner items, also i cannot set setHasFixedHeigth(true) (and I don't know if it would help) because during runtime new RV-2-Items can be added into RV-2. I also tried to set setNestedScrollingEnabled(false) on RV-2 because I read a lot about it online but it didn't help me either.
So basically this is how I configure
RV-1
layoutManager = LinearLayoutManager(context)
isNestedScrollingEnabled = false
RV-2
setHasFixedSize(true)
layoutManager = LinearLayoutManager(context).apply {
reverseLayout = true
}
In addition to that I have some ItemDecorators but they only create the space between the items, so they shouldn't have to do anything with the problem.
To sum it all up:
The outer RV-1 recycles it ViewHolders as intended but the inner RV-2 creates all ViewHolders at once, even if they are not on screen. I assume that this is the case because RV-2 has a height of wrap_content and when the layout_height need to be measured it needs to create all views. THE QUESTION: Is there a way to force RV-2 to recycle its views?
EDIT:
Also I am using a shared RecycledViewPool between all RV-2 RecyclerViews but that isn't really related to the problem, because even if the ViewHolders are shared between the RecyclerViews, an RV-2 RecyclerView shouldn't create ViewHolders that aren't visible when it is initialised.
EDIT 2:
A lot of comments and related questions say that two vertical nested RecyclerViews isn't a possible thing in android, in case all visitors of this question think the same my question is: How would you implement such a structure. It is obvious that I could make a single view which has a IM (Round Image View) and RV-2-Item and just make the IM invisible when it isn't needed. In my opinion this somehow makes the structure more complicated. Furthermore a requirement is that the IM on the left side of RV-1-Item must have the ability to move up and down in RV-1-Item, which is obviously easier with my current structure.
EDIT 3: (My last one I promise)
The Problem I have shown can be solved by using the approche I explain in my EDIT 2, even if it isn't the best solution it would work. But the issue is that I have an even more complex screen where this approche wouldn't work anymore because I have three nested RecyclerViews. I could get that number down to two with the approche of EDIT 2 but I would still be left with two nested RecyclerViews and I cannot think of a workaround that could solve the problem of the remaining two nested RecyclerViews. I attached an image of the even more complex screen which contains a the interface of the app with marked sections to help you to understand the structure.
(Not quite an answer to your specific question in solving "how to not get the RecyclerView to create all items at once", but something that most likely will fix your specific problem by not using nested recyclerviews at all)
I would suggest (in a quite similar way as already suggested in this answer), to flatten your feed into one recyclerview
(No matter how much you tweak your nested recyclerview architecture, imho it will never be as performant than having just one recyclerview, and as you don't need nested scrolling (I guess), just one recycler view should be your best option).
I would propose to not think of your feed in the way your data is structured, but in a way you want to show it and how it can be split into smaller items which are "look alikes" / consist of the same things.
From your screenshot I would see for example the following items / view types for each chat item:
Chat header (the thing with the icon and the text "New Group")
the user badge (the picture with the text "Jürgen")
a message item (one bubble of text, so e.g. in your screenshot at the bottom there would actually be 3 of those items, one for each message)
The section with the date and the action/reply items.
Those items are way smaller than a whole chat item, and therefore can be faster created / recycled.
For each of those items, create a view-type and a view-holder, and treat them as seperate recycler-view items.
The recyclerview will, when the getItemViewType method is correctly used, create / prepare the correct type of view for the position you need.
For this to work, the adapter needs to add some logic, as your data most likely will be structured something like
a list of chats, and each chat has a name and some messages to display
and we need it as
the first 6 elements are for the first chat, where the first position
is the header, the second the user badge, the next 3 items are message
items and then we need an action item.
So you basically need to calculate how many recyclerview items you will need to show each single chat-item, which could be a calculation along the lines:
1 chat header item + 1 user badge item + 3 message items + 1 action/reply item = 6
This calculation needs to be performed for each chat item of your data list separately.
So if you only have this single chat item in your list of data to display, you actually need to tell the adapter to create 6 items (by returning in this case 6 at getItemViewCount()).
Then, you need to tell the adapter using the getItemViewType(position: Int) function, at which position of the recyclerview which type of view the adapter needs to prepare.
So there you again need some logic to say that e.g. on position 0 the chat header for the first chat item should be, at position 1 the user badge for the first chat item, at position 2-4 message items should be, on position 5 the action item and at position 6 the chat header for the second chat should be and so on
(again, the logic then needs to be in place for all chat items, and it can get really messy / complicated, as to calculate each chat items view types for a position, e.g. all prior chat element view counts need to be recalculated, too (in order to know at which recycler-view position your current chat item starts)).
As this tends to blow your adapter up, I would suggest (if you don't already do so), to get some manager / delegate architecture in there.
So e.g. have a delegate for each view type, and a manager which calculates the number of recyclerview items / view types needed for each chat item.
Just for reference:
Some time ago we had a situation similar to yours
(a recycler-view with a design similar to a social media feed, which should show the first n comments in the feed and we displayed the comments for each feed item (which was a recyclerview item) with another recyclerview in the item) and also after some troubles with performance which we could not manage to resolve just flattened the recyclerview, and never had performance troubles again.
A lot of comments and related questions say that two vertical nested RecyclerViews isn't a possible thing in android
This is not true; whoever says this is not a thing has not done it and thinks it's not possible. It is possible, albeit with complications, side-effects, and most likely, the annoyance of your users when they tap around trying to scroll up/down and the wrong touch interceptor wins.
Why is this a problem?
On iOS, when you try to do something that the platform devs didn't think it was good, most people and other devs scream at you: don't fight the framework!!!.
On Android, we see the craziest Java (and now Kotlin) implementations of things that makes you wonder what are we -developers- learning at school and what are we teaching?! and yet nobody says anything (for the most part) :p
The truth is, you're trying to design a complicated user interaction and data transformation, and yet, your attempt is biased by trying to use the data "as you have it" (which implies dealing with these two different RV/Adapters), as opposed to do what one should do: transform the data for presentation.
This leads me to the next question:
How would you implement such a structure.
Well, for starters, I don't know how your data looks like, nor where it's coming from; I don't know what your users can do with your data, outside of the obvious scrolling.
I also don't know how your data wants to be presented, aside from your mock up.
But I do know the situation very well. A list of things, which also contain their own list of things.
Case: The List of List
It is doable; you can have a list and inside said list, have another list. I've done it. I've seen it done by others. I've used it. I also never liked the idea of having this "small" scrollable thing, fighting to see who scrolls first when I tap "the wrong place".
I would not do this. If the inner-list is big (say more than 3 items per outer item), I would not present it as scrollable content.
What I would do (considering the things I do not know about your problem) is to have a single list displaying all the content properly flattened.
This has a issue with your content:
What if the inner-lists are super long, wouldn't this cause them all to be displayed? YES, and that's why I wouldn't do it this way if the data (as you described) can have 100 items. An options is to display the 3 first items with a "more" link to now open the inner-list "full screen"; this is 10 times better than the nested list from a user's PoV and from the technical aspect of it.
Another alternative, is to keep this single long list (RV-1) and let users "expand" the list to launch another full-screen list depicting the contents of RV-2, in a separate window. This is even better.
The time you'll spend implementing this and getting rid of the mess of code you probably have right now, will make you wonder why didn't you suggest this in the first place.
If this is something you absolutely cannot do, then I cannot offer you much more advice, for now you're tied to unknown to me business/product rules. Ultimately, the price will be paid by the users of your app, when they have to scroll that nightmare :)
Take a Step Back
Let me be clear, I am not criticizing you or your solution; I'm merely pointing out that, in my experience, this "pattern" you have here is not a good user experience.
Format your data for presentation, not the other way around. Your data should be properly shaped so it can be properly presented with the tools you have.
You're fighting against the tools Android is giving you; you're giving a RecyclerView (and its adapter) a lot of new problems to deal with when it already has a lot going on.
Think about it: RecyclerViews have to do a lot of things; Adapters must also conform to a few interfaces, ensure things are dispatched as soon as possible, calculate Diffs (if using a ListAdapter<T,V>), etc. Activities/Fragments? They have a lot on their plates dealing with ... well "Android"; now you're asking all these components to also handle a complicated scenario of scrolling content, touch recognition, event handling, view inflation, etc.
All this, while expecting each view to take 16ms or less (to stay above 60 FPS scrolling speed, your view/viewHolder should not take more than 16ms to do all it needs.
Instead, I'm asking you to take a step back, grab the data you have, compose it, transform it, map it, and create the data structure that can better serve the components you have (a RV + Adapter + a simple View).
Good luck :)
I have an activity with a scrollable view. I also have containers that all have the same structure - consist of imageButton and also several textViews that serve as a description. The whole thing looks like that:
The problem is that there are around 30 elements with the same structure and when each one of them has so many textViews I get the warning -
Layout has more than 80 views, bad for performance.
The reason why I have so many views is because of the different formatting of the different words. How would it be possible to reduce the views and still get the same formatting?
P.S. I already read that how to resolve "Layout has more than 80 views, bad for performance"? . But this doesn't help me, since all the information that I have, should be static and always visible.
You will want to set up your layout using a RecyclerView.
If your app needs to display a scrolling list of elements based on large data sets (or data that frequently changes), you should use RecyclerView.
Android Developer Guide has some pretty good documentation on how to set this up. Also, here is a video tutorial on RecyclerView by SlideNerd. He has a pretty good series for android developers.
In addition to what J. Jefferson said about RecyclerView. As you mentioned in your initial post:
The reason why I have so many views is because of the different
formatting of the different words. How would it be possible to reduce
the views and still get the same formatting?
You can do different text formatting without creating a separate textview. Have a look at the following https://stackoverflow.com/a/41953808/8312634
This is not a code problem, I interpret the guidelines as that being OK.
I've been researching a way of building an infinitely scrolling calendar-like view in Android, but I've reached an impasse.
Right now my dilemma is that most of the similar views available have their children placed relative each other in a recurring style. With this I mean:
item 4 comes after item 3, which comes after item 2, and there is constant padding/margin between all items.
What I need is a way to produce an infinitely long scrollable view that may, or may not, contain items. The items should be placed at variable positions within the view. The best way I can describe a similar looking view is a one-day calendar-like view that is infinitely scrollable.
So far my best two bets are using the new RecyclerView with a custom LayoutManager (this seems very complex and still not perfectly documented by Google though). I like this approach because, among other things, it is optimized for displaying large sets in a limited view.
My other solution would be to build a completely custom View. However, with that solution I loose the adapter unless I build a container view (which is probably more complex than building a layout manager).
How would you go about solving such a problem? Tips are appreciated, I don't need code examples, just ideas which path is the best to solve this problem.
Thanks.
Apologies if I've misunderstood the guidelines
Edit: How I resolved this problem
My first solution to use RecyclerView with a special Decorator seemed promising, but it remained a "hack" so we decided not to go for that solution since we were afraid of the complications that it would create down the line.
To solve the problem I went with a SurfaceView instead of an Adapter, this means having to rewrite all the adapter-functionality for my SurfaceView but it seemed to be the best way of solving this issue of very custom drawing and layout managing for my use-case.
It still would be nice to build a custom Viewgroup that can handle this kind of layout problems.
ListView and ListAdapter are based on a fixed list, so the current infinite-scrollers just keep adding more and more data to the end of the list.
But what you want is scroller similar to Google's Calendar app which has a bi-directional infinite scroller. The problem with using ListView and ListAdapter in this case is that if you add data to the front of the list, the index of any one item changes so that the list jumps.
If you really start thinking about this from the MVC perspective, you realize that ListAdapter does not provide a model that fits this need.
Instead of having absolute indexing (i.e. 1, 2, 3, 4, etc), what you really want is relative indexing, so instead of saying "Give me the item at index 42" you want to say "here's an item, give me the five items before it". Or you have something like a calendar date which is absolute; yet — unlike your device's memory — it has effectively no beginning or end, so what you really want here is a "window" into a section of that data.
A better data model for this would be a kind of double-ended queue that is partly a LRU cache. You place a limit on the number of items in the structure. Then as prior items are loaded (user is scrolling up) the items at back end are pushed off, and when subsequent items are added (user is scrolling down), items at the front are pushed off.
Also, you would have a threshold where if you got within a few items of of one edge of the structure, a "loadNext" or "loadPrevious" event would fire and invoke a callback that you set up to push more data onto the edge of the structure.
So once you've figured out that your model is completely different, you realize that even RecyclerView isn't going to help you here because it's tied to the absolute indexing model. You need some sort of custom ViewGroup subclass that recycles item views like a ListView, but can adapt to the double-ended queue. And when you search code repos for something like this, there's nothing out there.
Sounds like fun. I'll post a link when I get a project started. (Sadly, it won't be done in any timely manner to help you right now, sorry.)
Something that might help you a little sooner: look at Google's Calendar implementation and see how they did it: Google Calendar Git repo
What you may be searching for is a FragmentStatePagerAdapter , where you can implement a swiped view, meaning when the user (for example)swipes to the right, a completely new view is displayed.
Using a FragmentStatePagerAdapter , you can handle a huge amount of views without overflowing the memory, because this specific PagerAdapter only keeps the views' states and is explicitly meant to handle large sets of views.
Keeping your example of a calendar, you can implement swiped navigation between for example weeks and generate the week views on demand while only keeping for example the year and the week's number as identifiers.
There are plenty of online tutorials for Android, maybe you have a look at this one
I am having trouble using an ExpandableListView because the type of childItems in each group varies greatly. In one section there are contacts from the phone box, another has a view with a progressBar in and the last one has a TextView in.
I have thought about using 3 individual ExpandableListViews, each with their own adapter and handlers, but this seems a bit crazy too, particularly when some of the 'lists' only have one item in and it never changes. Essentially I am using ExpandableListViews to create expandable sections in my UI, and it doesn't really feel right.
Is there a better UI element to use?
If they are completely distinct like it sounds like they are, you may want to look into using Fragments. It's a lot of new stuff you learn if you're not familiar with them, but you can then easily reproduce the collapsing mechanic using Animations coupled with hiding the Fragments. This also decouples the 3 UI elements (contacts, progress bar, text view) from each other, so you can reuse the Fragments pretty much anywhere you want.
I need some valuable advice from you guys...
I have a UI where I have to drag objects from one view to another.
I have a rough sketch of this UI below
I think I have to create a custom view for this. I have to drag a square and a ball from bag 1 and bag2 [both bags are scrollable, can contain 10 to 60 items each] and drop it to the closet one by one. and later I have to find the number of items in the closet. The items in the closet must be arranged in a well maintained fashion [may be ...like 6 in a row].
Where should I start?
How many custom views should I use?
Is there a simple and effective UI solution for this?
Happy coding..!
I would suggest a design with three different GridView objects laid out inside a ViewGroup that supports dragging objects from one GridView to another. The objects would be custom ImageView subclasses so you'd have a place in code to support a drag-and-drop protocol of some sort and because you'd likely want to associate some data that is specific to your application with the objects being dragged.
As for the drag-and-drop protocol, you could consider an adaptation of the Android Launcher drag-and-drop framework or the current drag-drop classes described on the Android developers' website. I don't have much to say about the current drag-drop classes. I have not tried them yet, but understanding them is on my to-do list. However, I have done an adaptation of the Launcher code, and I have written it up on my blog and posted demo apps and source code there. See Drag-Drop for an Android GridView.
With the framework that originated in the Android Launcher, you have a good set of classes and interfaces to work with. Some of the objects include: DragLayer, DragSource, DropTarget, DragController, DragView. The DragLayer is a custom ViewGroup within which all drag-drop operations occur. It delegates handling of all the touch events to a DragController, which is the object that does the actual moving of objects around on the screen. As it does so, it interacts with DropTarget objects to give the user visual feedback that something is being dragged and that a place to drop something is available. A DropTarget is an object where something can be dropped. A DragSource is the interface for objects that can be dragged within the DragLayer. The Launcher framework is a good one because it gives you a way to think about dragging and dropping and how you want to divide up the responsibilities defined by the framework.
The reason I suggest GridViews for your problem is it sounds like you are thinking that way already. The closet has "maybe 6 in a row" so that could be a GridView with one or more rows depending on how screen space you have. A GridView would also work for the container that holds the squares and circles. If that is a good fit, you could study the code in my drag-drop tutorial and see if that makes it easy for you to move objects from one GridView to another.
In my demo program, I ended up with a custom subclass of ImageView that I called an ImageCell. These are views that are on the grid. An ImageCell allows objects to be dragged from them and onto them. For awhile as I worked on the tutorial, I had a custom GridView class too but ended up with the standard GridView. It sounds like you'd want a custom GridView because it sounds like being in the closet is different than being in the other sections. Having it gives you a convenient place for the methods you have not thought of yet.
I hope some of these suggestions prove useful.
Depends on the Android version you are targeting. For 11 and above you can use the built in drag and drop functionality, otherwise you are pretty much on your own. I would normally advice you to implement some sort of a long press action that might even allow you to select multiple items and move them in batches, which would be a really simple thing to implement.
If you really need DnD you should check out this example, it should give you some idea on how to make your own implementation.
I think that bag1, bag2 and closet can be three instance of the same configurable custom view (let's name it CustomBagView).
CustomBagViews should be responsible for displaying items (using a gridview of imageview for example).
I think those will not need to handle drag & drop directly but they should support
removing and adding elements
provide a setOnItemTouched(Interface_class) callback setter.
Then you will need to code a Container custom view (let's name it BagContainerView) that will contain the three bags and handle the dragging & dropping from the bags.
You will provide a handleDrag callback to each bag using CustomBagView.setOnItemTouched, then track the finger motion in it.
When the dragging finishes, you must find where it ends, locate the right 'customBagView' and ask it to add the item to its list.