Android RecyclerView plus ViewPager - android

I have a ViewPager that utilizes a RecyclerView for each page and shares ViewItem rows across pages. Accordingly I share a single RecyclerViewPool between them. However, the ViewPager loads each RecyclerView whether or not it is the page on screen. Is there a way to indicate to the RecyclerView that all of its items are offscreen and force its views to be returned to the Recycler?
My sense is that subclassing LinearLayoutManager and overriding its onLayoutChildren method is the way to go, but I don't have much experience with LayoutManager and would like some guidance.

So here is a subclass of LinearLayoutManager that operates the way I described:
public class PageVisibleLinearLayoutManager extends LinearLayoutManager {
public PageVisibleLinearLayoutManager(Context context) {
super(context);
}
public PageVisibleLinearLayoutManager(Context context, int orientation, boolean reverseLayout) {
super(context, orientation, reverseLayout);
}
public PageVisibleLinearLayoutManager(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
private boolean pageVisible = true;
void setPageVisible(boolean pageVisible) {
boolean change = (this.pageVisible != pageVisible);
this.pageVisible = pageVisible;
if(change) requestLayout();
}
#Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
if(pageVisible) {
super.onLayoutChildren(recycler, state);
} else {
removeAndRecycleAllViews(recycler);
}
}
}
It works nicely and gives up its views if requested. As dsh mentioned, it is important to mark adjacent pages as being on screen (and I really don't know why setOffscreenPageLimit doesn't limit the number of pages loaded as expected). My previous solution was to use ViewStub and inflate a page only when it was on screen or adjacent. The layout manager method is slightly faster upon initial turning to an unloaded page, but ViewStub has the advantage of pages staying in memory once loaded (making subsequent scrolling more smooth), so I decided to stick with that.
Thank you all. Next question...

Related

Shared element transition get weird behavior while something is scrolling

I'm using shared element transition between activities. The first activity contains a LinearLayout and a RecyclerView:
The second activity:
And here's my code, seems very simple:
ActivityOptionsCompat options = ActivityOptionsCompat.makeSceneTransitionAnimation((Activity) getContext(), pairs);
ActivityCompat.startActivity(getContext(), new Intent(Intent.ACTION_VIEW, scheme), options.toBundle());
In common cases, everything seems ok, the transition animation works perfectly. But if I start the next activity while the RecyclerView in the first activity is scrolling, then go back to the first activity, something terrible happens:
I also tested ListView and ViewPager, ListView has same problem while ViewPager not. If I stop the scroll before startActivity, everything will be fine.
However in my product environment, views are complicated and it is ugly to find all ListViews and RecyclerViews and stop their scrolls. Is there anyway to prevent it from happening other than stop scrolling before startActivity?
RecyclerView class don't have such method to disable scrolling. Create CustomRecyclerView yourself like one below.
public class FooRecyclerView extends RecyclerView {
private boolean verticleScrollingEnabled = true;
public void enableVersticleScroll (boolean enabled) {
verticleScrollingEnabled = enabled;
}
public boolean isVerticleScrollingEnabled() {
return verticleScrollingEnabled;
}
#Override
public int computeVerticalScrollRange() {
if (isVerticleScrollingEnabled())
return super.computeVerticalScrollRange();
return 0;
}
#Override
public boolean onInterceptTouchEvent(MotionEvent e) {
if(isVerticleScrollingEnabled())
return super.onInterceptTouchEvent(e);
return false;
}
public FooRecyclerView(Context context) {
super(context);
}
public FooRecyclerView(Context context, #Nullable AttributeSet attrs) {
super(context, attrs);
}
public FooRecyclerView(Context context, #Nullable AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
}
replace RecyclerView to above created customview,
<com.customGoogleViews.FooRecyclerView
android:id="#+id/task_sub_list"
android:layout_width="match_parent"
android:layout_height="match_parent" />
disable scrolling before starting new activity,
recyclerview.enableVersticleScroll(false);
ActivityOptionsCompat options = ActivityOptionsCompat.makeSceneTransitionAnimation((Activity) getContext(), pairs);
ActivityCompat.startActivity(getContext(), new Intent(Intent.ACTION_VIEW, scheme), options.toBundle());
Hope it helps :)

Android - How to create Excel sheet type layout?

I want to create an Excel type layout where there is infinite scroll vertically. There are fixed number of columns horizontally, but they should be scrollable.
I tried the below code
<HorizontalScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true">
<android.support.v7.widget.RecyclerView
android:id="#+id/table_data_recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</HorizontalScrollView>
If I used the above code, then I am able to scroll vertically, but the columns are not scrollable.
Even giving fixed height in the xml for HorizontalScrollView and RecyclerView, the columns do not scroll.
Finally, I found an answer here, where it was advised to extend the RecyclerView and calculate the height dynamically.
public class MySmartRecyclerView extends RecyclerView {
public MySmartRecyclerView(Context context) {
super(context);
}
public MySmartRecyclerView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MySmartRecyclerView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
#Override
public boolean canScrollHorizontally(int direction) {
return false;
}
#Override
public int getMinimumWidth() {
return computedWidth;
}
#Override
protected void onMeasure(int widthSpec, int heightSpec) {
super.onMeasure(widthSpec, heightSpec);
setMeasuredDimension(computedWidth, getMeasuredHeight());
}
#Override
protected int getSuggestedMinimumWidth() {
return computedWidth;
}
}
So is this the only way, this can be achieved? Or is there any other way to achieve this. For RecyclerView inside ScrollView there is NestedScrollView, but no such thing for HorizontalScrollView.
Any pointers will be appreciated. TIA.
There is one alternate solution. You can use this library.
You want to do Ehhhh? Like the guy said, use library. You need and have normal easy to use solutions.
EDIT:
Okay my bad.
You want to infinite scroll vertically, not excel. I'm back from excel topic:
at onScroll listener, you should incriment endlessly the totalItemCount, you do it with double recurssion. From within the function of listener, you call to another listner whereas you call it by a function in the middle, which also increments that number before calling to the same listener again. You simply edit the view and re-attach the listener.

Android Recyclerview preload views

Im wondering how I can avoid the little white 'flashing' of the views in a recyclerview when the user scrolls a little faster.
The flashing can be avoided of course by preloading more views outside of the visible screen
I could not find anything yet how this can be done, although this must be a pretty common task??
I tried this code from a blog:
public class PreCachingLayoutManager extends LinearLayoutManager {
private static final int DEFAULT_EXTRA_LAYOUT_SPACE = 600;
private int extraLayoutSpace = -1;
private Context context;
public PreCachingLayoutManager(Context context) {
super(context);
this.context = context;
}
public PreCachingLayoutManager(Context context, int extraLayoutSpace) {
super(context);
this.context = context;
this.extraLayoutSpace = extraLayoutSpace;
}
public PreCachingLayoutManager(Context context, int orientation, boolean reverseLayout) {
super(context, orientation, reverseLayout);
this.context = context;
}
public void setExtraLayoutSpace(int extraLayoutSpace) {
this.extraLayoutSpace = extraLayoutSpace;
}
#Override
protected int getExtraLayoutSpace(RecyclerView.State state) {
if (extraLayoutSpace > 0) {
return extraLayoutSpace;
}
return DEFAULT_EXTRA_LAYOUT_SPACE;
}
}
Then I assigned the LayoutManager to my custom Recyclerview in the constructor by using setLayoutManager()
It is "custom", but I only wanted to set the LayoutManager in the costructor, thats why I overwrote the RecyclerView
Unfortunately, this did not have any effect
The RecyclerView is a very efficient API already and usually if you have frames dropping, you can optimize your item layout to be lighter and also make sure you free resources when views are recycled. There is really no need to pre-load anything.
Here is a blog post that gives some ideas of how to do those things
https://blog.workable.com/recyclerview-achieved-60-fps-workables-android-app-tips/
They don't mention it in this post, but also if your items have images, make sure those images are not too big.
The main point is to make the drawing of your views as fast as possible because everything is constantly redrawn as you scroll.

How can I fill RecyclerView with GridLayoutManager from right to left

I'm trying to fill some data into a RecyclerView with GridLayoutManager:
GridLayoutManager layoutManager = new GridLayoutManager(this, 3, GridLayoutManager.VERTICAL, false);
This will fill the data into the grid from left to right. The first item will be put into top-left place, etc.
But the app I'm developing is designed for an RTL language, so I need to make it fill from right to left.
I tried to change the last argument to true. But it just scrolled the RecyclerView all the way down.
Also setting layoutDirection attribute for RecyclerView doesn't work (without considering that my min API is 16 and this attribute is added for API17+):
<android.support.v7.widget.RecyclerView
android:id="#+id/grid"
android:layoutDirection="rtl"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
How can I create a right to left filled grid using RecyclerView?
Create a class that extends GridLayoutMAnager ,and override the isLayoutRTL() method like this:
public class RtlGridLayoutManager extends GridLayoutManager {
public RtlGridLayoutManager(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
public RtlGridLayoutManager(Context context, int spanCount) {
super(context, spanCount);
}
public RtlGridLayoutManager(Context context, int spanCount, int orientation, boolean reverseLayout) {
super(context, spanCount, orientation, reverseLayout);
}
#Override
protected boolean isLayoutRTL(){
return true;
}
}
The answer by Mohammad is true but you do not need to create a new class. you can simply override isLayoutRTL method.
GridLayoutManager lm = new GridLayoutManager(this, 2) {
#Override
protected boolean isLayoutRTL() {
return true;
}
};
There is a better way to do it
You can programmatically change the layout direction of the RecycleView
workHourRecycler = view.findViewById(R.id.market_hours_recycler);
workHourRecycler.setLayoutManager(new GridLayoutManager(getContext(),4));
//Programtically set the direction
workHourRecycler.setLayoutDirection(View.LAYOUT_DIRECTION_RTL);
On the face of it, GridLayouManager should fill rows from right when getLayoutDirection() gives ViewCompat.LAYOUT_DIRECTION_RTL. Normally, this direction will be inherited from the RecyclerView's container.
But if this does not work as expected, or you need to push it down to API 16, or you some device with bad implementation of RTL support, you can simply pull the GridLayoutManager from github, and use a custom manager that you tune to your liking.
It may be enough to extend the stock GridLayoutManager and override layoutChunk().
Its very simple, just call method setReverseLayout as,
GridLayoutManager layoutManager = new GridLayoutManager(this, 3, GridLayoutManager.VERTICAL, false);
layoutManager.setReverseLayout(true);

Scrollup not working in StickyListviewHeader with SwipeRefreshLayout

I am using StickyHeaderListview in my project to display contents and for refreshing the list, I am using SwipeRefreshLayout.
The problem here is, when I try to scroll up the list, it starts refreshing the list and not allowing to view the previous items of list.
I want the behavior should be such as the list get refresh only when I've reached to the first item and I try to scroll up , not everytime when i scroll up the list.
Can anyone help on this?
P.s. For implementing SwipeRefreshLayout, I am refering this example
I faced the same problem when using StickyHeaderListview as a direct child of SwipeRefreshLayout. StickyHeaderListview is in fact a FrameLayout wrapping a ListView inside. As nitesh goel explained, this would lead to problems with canChildScrollUp(). Based on nitesh goel's example, this is a full version of CustomSwipeRefreshLayout that works well for me:
public class CustomSwipeRefreshLayout extends SwipeRefreshLayout {
/**
* A StickyListHeadersListView whose parent view is this SwipeRefreshLayout
*/
private StickyListHeadersListView mStickyListHeadersListView;
public CustomSwipeRefreshLayout(Context context) {
super(context);
}
public CustomSwipeRefreshLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public void setStickyListHeadersListView(StickyListHeadersListView stickyListHeadersListView) {
mStickyListHeadersListView = stickyListHeadersListView;
}
#Override
public boolean canChildScrollUp() {
if (mStickyListHeadersListView != null) {
// In order to scroll a StickyListHeadersListView up:
// Firstly, the wrapped ListView must have at least one item
return (mStickyListHeadersListView.getListChildCount() > 0) &&
// And then, the first visible item must not be the first item
((mStickyListHeadersListView.getFirstVisiblePosition() > 0) ||
// If the first visible item is the first item,
// (we've reached the first item)
// make sure that its top must not cross over the padding top of the wrapped ListView
(mStickyListHeadersListView.getListChildAt(0).getTop() < 0));
// If the wrapped ListView is empty or,
// the first item is located below the padding top of the wrapped ListView,
// we can allow performing refreshing now
} else {
// Fall back to default implementation
return super.canChildScrollUp();
}
}
}
Ok I have got it working. If the SwipeRefreshLayout is the root of the layout and the ListView resides deep into the hierarchy (I had put the ListView inside a RelativeLayout along with the empty TextView) and not the direct child of the SwipeRefreshLayout, it won’t detect a swipe up on the list view properly.
You should create a custom class that extends SwipeRefreshLayout and override canChildScrollUp() method in SwipRefreshLayout
Here is a example :
public class CustomSwipeRefreshLayout extends SwipeRefreshLayout{
private AbsListView view;
public CustomSwipeRefreshLayout(Context context) {
super(context);
}
public CustomSwipeRefreshLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public void setView(AbsListView view){
this.view=view;
}
#Override
public boolean canChildScrollUp() {
return view.getFirstVisiblePosition()!=0;
}
}
I have had a similar problem, the direct child should be an instance of ScrollView (or ListView). The SwipeRefreshLayout will only take in account the direct child's scroll and not the child's of that direct child. I managed to solve this by using two SwipeRefreshLayouts.
I posted the code on github.
Hi i think i made something for a generally use :
public class CustomSwipeRefreshLayout extends SwipeRefreshLayout {
private View v;
public CustomSwipeRefreshLayout(Context context) {
super(context);
}
public CustomSwipeRefreshLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public void setView(View v) {
this.v = v;
}
#Override
public boolean canChildScrollUp() {
return this.v.canScrollVertically(-1);
}
}
With that solution, only set the view you want to scroll inside the SwipeRefreshLayout, after call canChildScrollUp(). like this :
this.refreshLayout.setView(aView);
this.refreshLayout.canChildScrollUp();
I don't test it a lot, but if i'm right it will work for every view at every place (direct child or not) in the SwipeRefreshLayout.
(for me it was SwipeRefreshLayout => RelativeLayout => SrcollView => linearLayout)
This is very simple solution:
list.setOnScrollListener(new AbsListView.OnScrollListener() {
#Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
}
#Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
int topRowVerticalPosition = (list == null || list.getChildCount() == 0) ?
0 : list.getChildAt(0).getTop();
swipeRefreshLayout.setEnabled((topRowVerticalPosition >= 0));
}
});
So, if you're on the top of the listview you will be enabled to do refresh.

Categories

Resources