I want to implement a layout similar to this.
The catch is that the content needs to be a view pager (that contains scrollable content).
My current plan is to implement this with one of the following
Make the main view a linear layout with the top section holding the header taking up fixed space (the viewpager fills whatever is left). Listen for touch events on the linear layout to expand or shrink the header, once the header has reached the minimum size, propagate the scroll events through to the viewpager (how?).
Same as 1., but wrapped in a ScrollView
Same as 1., but listen for scrolling in the viewpager and propagate that back up the view hierarchy (onScroll Listeners of some kind).
Same as 3, but wrapped in a ScrollView
I have a better idea on how to implement 3/4, but it seems like it introduces a lot of coupling, and "feels gross". I have less of an idea on how to do 1/2, but it feels (slightly) less gross. In either case, using the LinearLayout, I would have to override onTouch, rather than onScroll. Not sure which of those is preferable.
Ideally I would like to find a more elegant solution (possibly from Android L), but I welcome insights/pros/cons to my proposed solutions as well.
Related
I have nested ViewPagers and RecyclerViews as depicted in the image:
Requirements:
The first level ViewPager1 swipes from left to right. It uses FragmentStatePagerAdapter.
The first level RecyclerView1 scrolls vertically.
The second level ViewPager2 does not swipe - the swipe motion is controlled by a TabLayout. It uses a custom PagerAdapter that returns a View as a page.
The second level RecyclerView does not scroll - it simply wraps a list of dynamic items
What I have working so far:
The first level ViewPager1 and RecyclerView1 works as intended.
The ViewPager2 does not show because its height is defined as "wrap_content"
The ViewPager2/RecyclerView2 prevents RecyclerView1 to scroll up/down.
What I have tried:
Setting RecyclerView1.setNestedScrollingEnabled(false) stops it from passing the onTouch event to its children, but because the ViewPager2/RecyclerView2 wraps its content, it does not know what the size it needs to scroll.
Setting the ViewPager2 to a fixed height solves the scrolling problem. But because it is a fixed height, the content of RecyclerView2 is cut off.
Overriding OnMeasure as described here makes ViewPager2's content wrap, perfectly, but the scrolling no longer work again. I assume it is because OnMeasure is called "after" the View has already been attached?
So basically I need help on how to get the content to wrap but in such a way that RecyclerView 1 knows what the height is so that it can scroll.
EDIT
It turns out I was totally off base with point 3. The OnMeasure workaround DOES work as intended and the scrolling problem is NOT caused by recyclerView not knowing the height. It in fact does. The reason why it doesn't scroll is due to multiple nested scrollable view groups. I found this out by putting Log.i on onTouchEvent() and onInterceptTouchEvent() on all the scrollable view groups. Some surface of the views work, but if the surface has another scrollable child, it starts to cause problems.
Setting RecyclerView2.setNestedScrollingEnabled(false) fixed the vertical scrolling. However, now, the ViewPager2's touch behaviour is interfering with ViewPager1's
On closer inspection, ViewPager1 intercepts touch event when hitting non-scrollable surface, causing the ViewPager1 to call its onTouchEvent() to scroll left and right. However, if I start the touch event over a the ViewPager2's surface, ViewPager1 never intercept and it never handles the swipe left to right.
Unlike a RecyclerView, there is no simple method to disable nestedScrolling. So I tried disabling ViewPager2, but that didn't work and caused the inside views such as buttons not clickable.
I tried to return false in ViewPager2's OnTouchEvent so that it bubbles up the chain, but still, the ViewPager1's OnTouchEvent is never fired.
So I'm stuck again, how do I pass the touch event to the parent when the parent did not intercept the event when it should have. Again, I'm assuming, and again I might be off-base, that ViewPager1 might not intercept because ViewPager2 has requested a disallowInterceptTouchEvent() somewhere in its code? I just don't know where or how to begin to fix this problem.
Is it possible to animate RecyclerView height, or otherwise change it programmatically? For example, if I have a large header view that is sometimes visible, and other times not - I would want to animate the RecyclerView height to fill the screen when the header is animated out.
Changing LayoutParams.height does not seem to work. LinearLayout animateLayoutChanges causes a crash.
<LinearLayout>
<RelativeLayout (header)>
<RecyclerView>
</LinearLayout>
I want to make the RelativeLayout animate out the top (translationY) and then at the same time make the recyclerview animate to be taller to fit.
There are possible options to tackle this:
Follow suggestion from #Ari to start animation and on every animation tick update layout params. This will make an effect of recyclerview changing its size. However, this is a horrible idea from performance stand point. Layout and Measure process is quite expensive, so generally you want to minimize calls which trigger layout. Call to setLayoutParams will trigger layout & measure process for RecyclerView + all its children which means that on every single frame you will do really expensive work which most likely will lead to framedrop and bad user experience
There is another way though. It might not work in all cases - it all depends on your final layout, but still that's what I would recommend doing. The idea - is to make your recyclerView taller before you start animation. It requires some advanced Android skills though. Basically you need to override onMeasure & layout methods in your RecyclerView (you actually need to extend RecyclerView class to do that).
You can introduce some flag to your recyclerView to measure itself a bit taller than normal (how much taller - the exact height of your header view)
when you need to animate header out - set your flag to true and request new layout. This will re-layout recyclerView with some invisible part at the bottom.
Now you can just animate y translations of both RecyclerView & Header so header moves out of the screen and recyclerview goes higher. This will make user feel like recyclerview "expands"
Once animation is done - set your custom flag to false and change visibility of your header to GONE since it is off screen now
Here is some information about implementing custom onMeasure logic:
https://medium.com/android-news/perfmatters-introduction-to-custom-viewgroups-to-improve-performance-part-2-f14fbcd47c
I'm developing an app that has an UI pretty similar to Play Store. It is organized as a multiple panels one above another. First it has a panel containing a photo. Under that it has another panel containing some text and a custom background color. Under that it has another photo and finally it has a Linear Layout with vertical orientation containing a pretty long list of little views filled dynamically at runtime. I have all this inside a Scroll View, naturally.
The problem? That dynamic fill of the linear layout takes a long processor time and makes my app unresponsive during those inner views inflation. So I thought to replace the linear layout by a Recycler View. And the performance is awesome!
So? Well... Not everything is so awesome. I can't scroll the Recycler View because it's inside the Scroll View. And if I remove the Scroll View then I can't scroll the entire view (some things doesn't fit on the screen).
What's the best approach for fixing this?
It's not recommended to use a RecyclerView or ListView inside of a ScrollView precisely due to the double scrolling issues. RecyclerView is very robust and is prepared to receive headers, footers, etc. I see no reason why the entire layout could not be inside of a RecyclerView instead of a ScrollView
The ViewHolder implementation can include logic to inflate different layouts depending on what section should be next.
Pseudocode:
i.e.
if(currentAdapterItem == sectionA){
useLayoutA();
} else{
useLayoutB();
}
Just use a NestedScrollView instead of a normal ScrollView. It handles the nested scrolling quite well.
I want to learn how to solve this problem. I want to have a Horizontal scrollview with the scroll blocked (the user should not be able to scroll it) and inside that horizontal scrollview i want to have another horizontal scroll view, and this scrollview must be able to be scrolled by the user (it haves more content that the width of the screen).
Is it possible to do it?
I tried using this on the parent horizontal scroll view:
((HorizontalScrollView) view).setHorizontalScrollBarEnabled(false);
((HorizontalScrollView) view).setEnabled(false);
((HorizontalScrollView) view).setFocusable(false);
((HorizontalScrollView) view).setFocusableInTouchMode(false);
and this on the child horizontal scroll view:
((HorizontalScrollView) view).requestFocus();
It is not working, the child appears to have a scroll bar, but it cannot be scrolled.
How can this be solved?
PD: I know that this is not a good practice, but I want to learn how to achieve this goal.
You should never use a
HorizontalScrollView with a ListView,
since ListView takes care of its own
scrolling. Most importantly, doing
this defeats all of the important
optimizations in ListView for dealing
with large lists, since it effectively
forces the ListView to display its
entire list of items to fill up the
infinite container supplied by
HorizontalScrollView.
http://developer.android.com/reference/android/widget/HorizontalScrollView.html
UPDATE:
Since you may be forced to use a two dimensional scrollview, you may consider using this:
http://blog.gorges.us/2010/06/android-two-dimensional-scrollview/
I haven't used this but it may be a reasonable approach.
you can do it. But you have to handle child layouts in scrollview i.e ScrollView can host only one direct child.
I'm researching creating a view for displaying EPG data. That is the view should:
have a fixed header column that shows the services,
have a fixed header row that shows the time and
a content area that has equal-height TextViews of flexible width for showing the actual EPG entries
Most importantly, scolling vertically must scroll header column as well, scrolling horizontally must scroll header row.
Apart from drawing the whole view myself (inside a scrollable?) I'm totally stumped if there is any good approach I could use involving linear layouts or such.
Any hints are greatly appreciated- please note that I'm and android beginner.
Approach 1: Everything (that is all views) are pre-generated.
Have top and left one-way ScrollViews together with a bidirectional ScrollView and have the scolling be synchronized ( Android: Synchronized scrolling of two different views ). Synchronized scrolling isn't to difficult to do if you've subclassing the ScrollViews and add your own ScrollManager to handle notifications.
Drawback: ScrollViews inside ScrollViews for the main content area do seem to be the desired option. The whole thing will become highly resource intensive as all items need to be created upfront to be available for scrolling.
Approach 1 takes care of view-synchronization for the scrolling, but is a huge resource hog (imagine an EPG with 30+ channels and 100+ events per channel).
Approach 2
One approach for this I could imagine would be- for the main content area- a ListView with a custom ArrayAdapter. Custom adapter would probably return a LinearLayout or similar holding the individual events. That way, scrolling would still work bidirectionally if the ListView is put into horizontal ScrollView and at least the LinearLayouts themselves could be recycled by the ListView.
Are there better approaches?