[Android] Horizontal scroll list in vertically scrolling list, and how to handle “ItemClick” event fired by each item
4/30/2015, 9:07:59 AM
One thing I would have liked to implement in the all new Peatix Android app was this idea of “Home” screen. It shows what are relevant to you regardless of what type of objects they are. Very much like Google Now, which shows many different types of “cards” in one list.
In the list, I want to show as many “stuff” as possible in a small screen of a phone. The app could list things like tickets you have got, list of events the app recommends to you, tweets and FB posts related to an event you are going, etc etc. Lining up all of these items vertically in a scrolling view is an okay-idea; but it’s pretty obvious that the latter an item is listed, the latter it’s going to be actually seen (or to impress the user). As of this writing, Facebook and Twitter both put relevant items in vertically scrolling list, while some items (mostly Ads, some photos if there are many) occupies only a height of an item while showing many child items in horizontally scrolling list. I would have liked that in my app too.
You can see the guts of what I implemented in one of my GitHub repositories. Here in this commit I implemented very basic vertically scrolling list by using RecyclerView and its friends. Then I added “ItemClick” event handler in the next commit. Simple, so far.
Now I want to modify some of those items have its own child items that are horizontally scrolling. I added another layout XML that has a RecyclerView. So, the root RecyclerView has list of CardViews. Each CardView renders either a text or another RecyclerView. Basically, many RecyclerViews are nested inside a RecyclerView. Each child RecyclerView has as many items its adapter gives it, and renders each item using (happens to be) the same layout as is used in the parent RecyclerView to render a simple text. So far so good.
The last but the hardest part to implement in a way that makes sense to me is to handle “ItemClick” event fired by each child items in a horizontally scrolling item. The very first idea that comes to my mind is to define OnItemClickListener in the ViewHolder. If you think about it, each of the parent items in the vertically scrolling list fires the event to the handler that MainActivity defines; the same structure should work, so it’s natural for each ViewHolder to define OnItemClickListener for each of its own children.
But I dumped the idea because it didn’t make sense to me. ViewHolder should be a reusable utility class for a type of View/Layout rendered in a RecyclerView. It must not be tied to a particular item. But in order to implement OnItemClickListener, an instance of ViewHolder must know about either the list of items that its parent RecyclerView is rendering, or each item that is rendered. The latter makes the ViewHolder un-reusable. The former idea, well, having a reference in something that could be discarded at any point (RecyclerView aggressively recycles views) make hard the fighting NullPointerException.
These are what I have thought through, to conclude my implementation of having both ItemClickListener (children and grandchildren) in MainActivity.
It first detects if if’s a “touch-up” event. It then finds an item that must be under where the touch event occurs. If an item is a type of String, it fires the direct child’s OnItemClick (well, virtually said. It does what the event handler does inline in the example). If an item is not a type of String, it must be an array of String in this case, which means the item is a horizontally scrolling CardView. It calculates offset to the CardView and pass the calculated coordinates to the ViewHolder’s method to get a grand child item. If the position of a grand child item is found, it looks for the grand child and fires OnChildItemClick (again, it writes what it does in line). Woot. Mission accomplished.
I am not saying that this is the best way to handle these stuff though. I have a part of myself who doesn’t like this complicated implementation. Implementing ViewHolder pattern correctly is hard.
Please let me know what I am doing wrong if you see it. Thanks for reading thus far.