Swipe to another view in recyclerView with ItemTouchHelper - android

Given is a RecyclerView.
What is the best practice and the easiest way to show a different View while the swipe is performed?
Here is a similar question. But with the solution there only a Bitmap can be shown.
With the recyclerview comes the awesome ItemTouchHelper, which has the callback:
public void onChildDraw(Canvas canvas, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {...}
My question is: can i somehow exploit this callback to swipe between two views, and if yes, how.
Thank you.

I am working on the similar task - need to reveal the hidden underlying panel while item is swiping away.
I've managed to do it having two inner layouts inside the root RelativeLayout for item row view:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:fresco="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<RelativeLayout
android:id="#+id/actionsLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerInParent="true">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hidden stuff is here"/>
</RelativeLayout>
<RelativeLayout android:id="#+id/itemLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:background="#color/submission_row_bg">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Swiping stuff is here"/>
</RelativeLayout>
</RelativeLayout>
In your ItemViewHolder class I have corresponding fields:
public class ItemViewHolder extends RecyclerView.ViewHolder {
RelativeLayout itemLayout;
RelativeLayout actionsLayout;
}
Then inside ItemTouchHelper.Callback I am overriding onChildDraw method like this:
public static class MyItemTouchCallback extends ItemTouchHelper.Callback {
public void onChildDraw(Canvas c, RecyclerView recyclerView,
RecyclerView.ViewHolder viewHolder, float dX, float dY,
int actionState, boolean isCurrentlyActive) {
if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE) {
//HERE IS THE TRICK
((ItemViewHolder) viewHolder).itemLayout.setTranslationX(dX);
} else {
super.onChildDraw(c, recyclerView, viewHolder, dX, dY,
actionState, isCurrentlyActive);
}
}
}
It helps to swipe away one view and show the underlying one.

Related

Vertical reveal effect like collapsing view in RecycleView cell

I'm trying to find a solution to recycle view like this:
I've tried many ways but still have no success, maybe somebody could give me a hint for that or advise library?
I almost solved the problem with setting padding in onScroll, but actually have no idea why content offset is different than getTop()?
#Override
public void onScrolled(#NonNull RecyclerView recyclerView, int dx, int dy) {
int contentPaddingTop = -childView.getTop();
childView.setPadding(0,contentPaddingTop,0,0);
}
https://files.fm/f/vjwy46m7
May be something is wrong with my layout for the cell?
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
android:clipChildren="true">
<ImageView
android:id="#+id/image_foxy"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="#drawable/fox700"
/>
</RelativeLayout>
OK, I can't explain why but the solution in my case was to set paddings of content equal doubled distance to visible top of the table:
#Override
public void onScrolled(#NonNull RecyclerView recyclerView, int dx, int dy) {
int contentPaddingTop = -childView.getTop() * 2;
childView.setPadding(0,contentPaddingTop,0,0);
}

Using a view with enterAlways scrollFlag and another view with exitUntilCollapsed scrollFlag in an AppBarLayout

I'm trying to build a following layout using CoordinatorLayout and AppBarLayout:
|View 1 (Header)|
|View 2 ------------|
|RecyclerView--- |
The behavior I want to achieve is as the following:
When I scroll the RecyclerView, View 1 will completely collapse.
As I continue scrolling, View 2 will collapse until it's "collapsed" state.
RecyclerView should start scroll once View 2 is collapsed.
When I scroll back up the RecyclerView from the middle, View 1 should enter back right away while View 2 is left as its collapsed state.
Once the RecyclerView reaches the top, it should expand View 2.
This is the testing layout I created as a proof of concept.
<android.support.design.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.design.widget.AppBarLayout
android:id="#+id/app_bar_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="#+id/view1"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:text="TEST TITLE"
android:textSize="50sp"
app:layout_scrollFlags="scroll|enterAlways|snap" />
<TextView
android:id="#+id/view2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="TEST TEST TEST TEST TEST TEST TEST TEST"
android:textSize="70sp"
android:minHeight="50dp"
app:layout_scrollFlags="scroll|exitUntilCollapsed" />
</android.support.design.widget.AppBarLayout>
<android.support.v7.widget.RecyclerView
android:id="#+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="#string/appbar_scrolling_view_behavior" />
</android:support.design.widget.CoordinatorLayout>
I created a test adapter to add few TextView items in RV (nothing fancy here). When I run the code, It does not actually work as expected.
Initial Screen
As I scroll down, I confirmed that View 1 is completely collapsed.
Scroll down more. View 2 collapses until it reaches minHeight. RV started scrolling after that. This is working as expected so far.
Here comes the problem. When I scroll up the RV. View 2 is expanded by the height of View 1. I'd like to see View 1 appear again.
I looked into the AppBarLayout implementation and the issue seems to be because the AppBarLayout calculates the scroll range of the whole view based on the scrollFlags, and offsetting the whole view based on the scroll offset, rather than updating each child View.
Does anyone know if there's any workaround or open source lib to resolve this issue? It doesn't have to be CoordinatorLayout/AppBarLayout approach, but I need to produce the behavior.
Thank you in advance.
Okay, I found a solution by myself, and decided to post my solution for people with similar problems.
The solution was to create a NestedCoordinatorLayout that extends CoordinatorLayout by implementing NestedScollingChild so that we can interact between two AppBarLayouts. I referenced NestedScrollView source code and the answer in this post, https://stackoverflow.com/a/36881816/6272520, but I had to make few changes to make it work the way I want to.
Here's the code for the NestedCoordinatorLayout.
public class NestedCoordinatorLayout extends CoordinatorLayout implements NestedScrollingChild {
private final NestedScrollingChildHelper scrollingChildHelper;
private final int[] parentOffsetInWindow = new int[2];
private final int[] parentScrollConsumed = new int[2];
public NestedCoordinatorLayout(Context context) {
this(context, null);
}
public NestedCoordinatorLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public NestedCoordinatorLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
scrollingChildHelper = new NestedScrollingChildHelper(this);
setNestedScrollingEnabled(true);
}
//NestedScrollingChild
#Override
public void setNestedScrollingEnabled(boolean enabled) {
scrollingChildHelper.setNestedScrollingEnabled(enabled);
}
#Override
public boolean isNestedScrollingEnabled() {
return scrollingChildHelper.isNestedScrollingEnabled();
}
#Override
public boolean startNestedScroll(int axes) {
return scrollingChildHelper.startNestedScroll(axes);
}
#Override
public void stopNestedScroll() {
scrollingChildHelper.stopNestedScroll();
}
#Override
public boolean hasNestedScrollingParent() {
return scrollingChildHelper.hasNestedScrollingParent();
}
#Override
public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed,
int dyUnconsumed, int[] offsetInWindow) {
return scrollingChildHelper.dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, offsetInWindow);
}
#Override
public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) {
return scrollingChildHelper.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow);
}
#Override
public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) {
return scrollingChildHelper.dispatchNestedFling(velocityX, velocityY, consumed);
}
#Override
public boolean dispatchNestedPreFling(float velocityX, float velocityY) {
return scrollingChildHelper.dispatchNestedPreFling(velocityX, velocityY);
}
//NestedScrollingParent
#Override
public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
scrollingChildHelper.startNestedScroll(nestedScrollAxes);
return super.onStartNestedScroll(child, target, nestedScrollAxes);
}
#Override
public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {
final int[] parentConsumed = parentScrollConsumed;
//This is where the most important change happens.
//During the prescroll, we want to decrease dx/dy.
//This will make sure the top bar gets the scroll event first.
if (dispatchNestedPreScroll(dx, dy, parentConsumed, null)) {
dx -= parentConsumed[0];
dy -= parentConsumed[1];
consumed[0] += parentConsumed[0];
consumed[1] += parentConsumed[1];
}
super.onNestedPreScroll(target, dx, dy, consumed);
}
#Override
public void onNestedScroll(View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, parentOffsetInWindow);
super.onNestedScroll(target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed);
}
#Override
public void onStopNestedScroll(View target) {
scrollingChildHelper.onStopNestedScroll(target);
super.onStopNestedScroll(target);
}
#Override
public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) {
scrollingChildHelper.dispatchNestedFling(velocityX, velocityY, consumed);
return super.onNestedFling(target, velocityX, velocityY, consumed);
}
#Override
public boolean onNestedPreFling(View target, float velocityX, float velocityY) {
scrollingChildHelper.dispatchNestedPreFling(velocityX, velocityY);
return super.onNestedPreFling(target, velocityX, velocityY);
}
#Override
public void onNestedScrollAccepted(View child, View target, int axes) {
super.onNestedScrollAccepted(child, target, axes);
startNestedScroll(axes & ViewCompat.SCROLL_AXIS_VERTICAL);
}
}
And the updated xml file looks like this:
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.design.widget.AppBarLayout
android:id="#+id/app_bar_layout_for_view1"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="#+id/view1"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:text="TEST TITLE"
android:background="#android:color/white"
android:textSize="50sp"
app:layout_scrollFlags="scroll|enterAlways|snap" />
</android.support.design.widget.AppBarLayout>
<!-- Consider this like a NestedScrollView.
You need to have a scrolling behavior -->
<NestedCoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="#string/appbar_scrolling_view_behavior">
<android.support.design.widget.AppBarLayout
android:id="#+id/app_bar_layout_for_view2"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="#+id/view2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="50dp"
android:text="TEST TEST TEST TEST TEST TEST TEST TEST"
android:textSize="70sp"
app:layout_scrollFlags="scroll|exitUntilCollapsed" />
</android.support.design.widget.AppBarLayout>
<android.support.v7.widget.RecyclerView
android:id="#+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="#string/appbar_scrolling_view_behavior" />
</NestedCoordinatorLayout>
</android.support.design.widget.CoordinatorLayout>
And it works like a charm

Android toolbar elevation when scrolling

I try to implement a search bar like in google maps android app:
When the recycler view is in its initial state, the toolbar has no elevation. Only when the users starts scrolling the elevation becomes visible. And the search bar (toolbar) never collapses. Here is what I tried to replicate this:
<android.support.design.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">
<android.support.v7.widget.RecyclerView
android:id="#+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<android.support.design.widget.AppBarLayout
android:id="#+id/appBarLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="64dp">
<!-- content -->
</android.support.v7.widget.Toolbar>
</android.support.design.widget.AppBarLayout>
</android.support.design.widget.CoordinatorLayout>
And here you can see the result:
So the problem with my solution is, that the elevation of the toolbar is always visible. But I want it to appear only when the recycler view scrolls behind it. Is there anything from the design support library that enables such behavior as seen in the google maps app?
I am using
com.android.support:appcompat-v7:23.2.0
com.android.support:design:23.2.0
The accepted answer is outdated. Now there is inbuilt functionality to do this. I am pasting the whole layout code so it will help you to understand.
You just need to use CoordinatorLayout with AppBarLayout. This design pattern is called Lift On Scroll and can be implemented by setting app:liftOnScroll="true" on your AppBarLayout.
Note: the liftOnScroll attribute requires that you apply the #string/appbar_scrolling_view_behavior layout_behavior to your scrolling view (e.g., NestedScrollView, RecyclerView, etc.).
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity"
android:background="#color/default_background">
<com.google.android.material.appbar.AppBarLayout
android:id="#+id/appbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:liftOnScroll="true">
<androidx.appcompat.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="#color/default_background" />
</com.google.android.material.appbar.AppBarLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/list_recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="#+id/appbar"
app:layout_behavior="#string/appbar_scrolling_view_behavior"
android:orientation="vertical" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
Refered this documentation https://github.com/material-components/material-components-android/blob/master/docs/components/AppBarLayout.md
EDIT As pointed out in the comments, my answer is now outdated, see https://stackoverflow.com/a/58272283/4291272
Whether you are using a CoordinatorLayout or not, a RecyclerView.OnScrollListener seems like the right way to go as far as the elevation is concerned. However, from my experience recyclerview.getChild(0).getTop() is not reliable and should not be used for determining the scrolling state. Instead, this is what's working:
private static final int SCROLL_DIRECTION_UP = -1;
// ...
// Put this into your RecyclerView.OnScrollListener > onScrolled() method
if (recyclerview.canScrollVertically(SCROLL_DIRECTION_UP)) {
// Remove elevation
toolbar.setElevation(0f);
} else {
// Show elevation
toolbar.setElevation(50f);
}
Be sure to assign a LayoutManager to your RecyclerView or the call of canScrollVertically may cause a crash!
This is a good question but none of the existing answers are good enough. Calling getTop() is absolutely not recommended as it's very unreliable. If you look at newer versions of Google apps that follow Material Design Refresh (2018) guidelines, they hide the elevation at the beginning and immediately add it as user scrolls down and hide it again as user scrolls and reaches the top again.
I managed to achieve the same effect using the following:
val toolbar: android.support.v7.widget.Toolbar? = activity?.findViewById(R.id.toolbar);
recyclerView?.addOnScrollListener(object: RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy);
if(toolbar == null) {
return;
}
if(!recyclerView.canScrollVertically(-1)) {
// we have reached the top of the list
toolbar.elevation = 0f
} else {
// we are not at the top yet
toolbar.elevation = 50f
}
}
});
This works perfectly with vertical recycler views (even with tab view or other recycler views inside them);
A couple of important notes:
Here I'm doing this inside a fragment hence activity?.findViewById...
If your Toolbar is nested inside an AppBarLayout, then instead of applying elevation to Toolbar, you should apply it to the AppBarLayout.
You should add android:elevation="0dp" and app:elevation="0dp" attributes to your Toolbar or AppBarLayout so that the recycler view doesn't have elevation at the beginning.
I have a RecyclerView in my fragment. I could achieve similar effect using code below:
It is not the Smartest way and you can wait for better answers.
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Initial Elevation
final Toolbar toolbar = (Toolbar) getActivity().findViewById(R.id.toolbar);
if(toolbar!= null)
toolbar.setElevation(0);
// get initial position
final int initialTopPosition = mRecyclerView.getTop();
// Set a listener to scroll view
mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
}
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
if(toolbar!= null && mRecyclerView.getChildAt(0).getTop() < initialTopPosition ) {
toolbar.setElevation(50);
} else {
toolbar.setElevation(0);
}
}
});
}
I found this when page when I wanted to do something similar, but for a more complex View Hierarchy.
After some research, I was able to get the same effect using a custom behavior. This works for any view in a coordinator layout (given that there's a nested scroll element such as RecyclerView or NestedScrollView)
Note: This only works on API 21 and above as ViewCompat.setElevation does not seem to have any effect pre lollipop and AppBarLayout#setTargetElevation is deprecated
ShadowScrollBehavior.java
public class ShadowScrollBehavior extends AppBarLayout.ScrollingViewBehavior
implements View.OnLayoutChangeListener {
int totalDy = 0;
boolean isElevated;
View child;
public ShadowScrollBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
public boolean layoutDependsOn(CoordinatorLayout parent, View child,
View dependency) {
parent.addOnLayoutChangeListener(this);
this.child = child;
return super.layoutDependsOn(parent, child, dependency);
}
#Override
public boolean onStartNestedScroll(#NonNull CoordinatorLayout coordinatorLayout,
#NonNull View child, #NonNull View directTargetChild,
#NonNull View target, int axes, int type) {
// Ensure we react to vertical scrolling
return axes == ViewCompat.SCROLL_AXIS_VERTICAL ||
super.onStartNestedScroll(coordinatorLayout, child, directTargetChild,
target, axes, type);
}
#Override
public void onNestedPreScroll(#NonNull CoordinatorLayout coordinatorLayout,
#NonNull View child, #NonNull View target,
int dx, int dy, #NonNull int[] consumed, int type) {
totalDy += dy;
if (totalDy <= 0) {
if (isElevated) {
ViewGroup parent = (ViewGroup) child.getParent();
if (parent != null) {
TransitionManager.beginDelayedTransition(parent);
ViewCompat.setElevation(child, 0);
}
}
totalDy = 0;
isElevated = false;
} else {
if (!isElevated) {
ViewGroup parent = (ViewGroup) child.getParent();
if (parent != null) {
TransitionManager.beginDelayedTransition(parent);
ViewCompat.setElevation(child, dp2px(child.getContext(), 4));
}
}
if (totalDy > target.getBottom())
totalDy = target.getBottom();
isElevated = true;
}
super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed, type);
}
private float dp2px(Context context, int dp) {
Resources r = context.getResources();
float px = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, r.getDisplayMetrics());
return px;
}
#Override
public void onLayoutChange(View view, int i, int i1, int i2, int i3, int i4, int i5, int i6, int i7) {
totalDy = 0;
isElevated = false;
ViewCompat.setElevation(child, 0);
}
}
my_activity_layout.xml
<android.support.design.widget.CoordinatorLayout
android:fitsSystemWindows="true"
android:layout_height="match_parent"
android:layout_width="match_parent">
<android.support.v7.widget.RecyclerView
android:id="#+id/recyclerView"
android:layout_height="match_parent"
android:layout_width="match_parent" />
<android.support.design.widget.AppBarLayout
android:id="#+id/appBarLayout"
android:layout_height="wrap_content"
android:layout_width="match_parent"
app:layout_behavior="com.myapp.ShadowScrollBehavior">
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
android:layout_height="64dp"
android:layout_width="match_parent">
<!-- content -->
</android.support.v7.widget.Toolbar>
</android.support.design.widget.AppBarLayout>
</android.support.design.widget.CoordinatorLayout>
If you use CoordinatorLayout you dont need any extra code to make this work by yourself just some setup on style and layout XML, check this:
Your app style should use a MaterialCompoment style, like src/main/res/values/styles.xml.
Setup you AppBarLayout:
Use any MaterialCompoments style for this component like: Widget.MaterialComponents.AppBarLayout.Surface.
Set app:liftOnScroll="true" to enable the automatic elevation based on scroll.
Setup your scrolling view:
Set app:layout_behavior="#string/appbar_scrolling_view_behavior.
https://github.com/danielgomezrico/spike-appbarlayout-toolbar-automatic-elevation

How disable swiperefreshlayout when Recyclerview is not on the first item?

My android project includes a recyclerView that contains a list of cardViews, and also there is a swipeRefreshLayout on the top of this recyclerView. When I scroll down the list and pull up those cardViews, I just want to disable swipeRefreshLayout. In other word, when RecyclerView is not on the first item, and if the user wants to scrolling back to first item, it must not show swipeRefreshLayout.
I googled a lot about this issue and there are some solutions for this problem that overrides onScrollStateChanged method, but they not behave very smooth and still swipeRefreshLayout remains enabled in some situations.
EDIT 1:
Following links are include some of these solutions I mentioned above:
https://stackoverflow.com/a/27042911/4257703
https://gist.github.com/NikolaDespotoski/1a6bb83dbae133f67812
Here is my xml layout:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="center"
android:orientation="horizontal">
<android.support.v4.widget.SwipeRefreshLayout
android:id="#+id/swipe_refresh_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.RecyclerView
android:id="#+id/recyclerview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="8dp"
android:choiceMode="none"
android:focusable="false"
android:listSelector="#android:color/darker_gray" />
<ImageView
android:id="#+id/empty"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center" />
</FrameLayout>
</android.support.v4.widget.SwipeRefreshLayout>
EDIT 2:
Today I realized that my broblem is occured because of implementing Tabs and swipeRefreshLayout together. For refreshing the data of Fragment which contains RecyclerView, user must drag the page to bottom, and in other hand for switching between tabs, user must drag the screen to right or left. Due to this touch gestures, some bugs and lags occur in scrolling my list. Please help me to address this problem. Thanks a lot.
Maybe I am late, but have a try to this solution:
mRecyclerView = (RecyclerView) view.findViewById(R.id.recyclerView);
mLayoutManager = new LinearLayoutManager(getActivity()); // a LinearLayoutManager
mRecyclerView.setLayoutManager(mLayoutManager); // setting layoutManager on our RecyclerView
// Adding ScrollListener to getting whether we're on First Item position or not
mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
mSwipeRefreshLayout.setEnabled(mLinearLayoutManager.findFirstCompletelyVisibleItemPosition() == 0); // 0 is for first item position
}
});
mSwipeRefreshLayout is your SwipeRefreshLayout
After putting above code, you'll be able to swipe only when your First item is visible.
Hope this helps! 😊
Here is the fix:
public class SwipeRefreshLayoutToggleScrollListener extends RecyclerView.OnScrollListener {
private List<RecyclerView.OnScrollListener> mScrollListeners = new ArrayList<RecyclerView.OnScrollListener>();
private int mExpectedVisiblePosition = 0;
private SwipeRefreshLayout mSwipeLayout;
public SwipeRefreshLayoutToggleScrollListener(SwipeRefreshLayout swipeLayout) {
mSwipeLayout = swipeLayout;
}
public void addScrollListener(RecyclerView.OnScrollListener listener){
mScrollListeners.add(listener);
}
public boolean removeScrollListener(RecyclerView.OnScrollListener listener){
return mScrollListeners.remove(listener);
}
public void setExpectedFirstVisiblePosition(int position){
mExpectedVisiblePosition = position;
}
#Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
notifyScrollStateChanged(recyclerView,newState);
LinearLayoutManager llm = (LinearLayoutManager) recyclerView.getLayoutManager();
int firstVisible = llm.findFirstCompletelyVisibleItemPosition();
if(firstVisible != RecyclerView.NO_POSITION)
mSwipeLayout.setEnabled(firstVisible == mExpectedVisiblePosition);
}
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
notifyOnScrolled(recyclerView, dx, dy);
}
private void notifyOnScrolled(RecyclerView recyclerView, int dx, int dy){
for(RecyclerView.OnScrollListener listener : mScrollListeners){
listener.onScrolled(recyclerView, dx, dy);
}
}
private void notifyScrollStateChanged(RecyclerView recyclerView, int newState){
for(RecyclerView.OnScrollListener listener : mScrollListeners){
listener.onScrollStateChanged(recyclerView, newState);
}
}
}
for more info check this
URL:https://gist.github.com/NikolaDespotoski/1a6bb83dbae133f67812

Recyclerview with scrolling background

I'm trying to create a RecyclerView with scrolling background, like the one shown below.
The idea is, as I scroll up/down the viewholders, the background (light-green) image should also move up/down in sync. Any clue as to how to accomplish this?
Here is my basic RecyclerView configuration
<android.support.v7.widget.RecyclerView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="#dimen/item_margin"
android:clipToPadding="false"
android:background="#drawable/ic_light_green_image"/>
I had the same use-case -- scrolling a list of cards that lies above the app bar along the Z axis, just like Google Play Music. It's actually pretty simple, but the documentation for RecyclerView#computeVerticalScrollOffset() is completely misleading. It doesn't calculate the scrollbar thumb's offset, instead it calculates by how much the RecyclerView itself has scrolled (which is exactly what we need here).
mPostList.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
int scrollY = mPostList.computeVerticalScrollOffset();
// mAppBarBg corresponds to your light green background view
mAppBarBg.setTranslationY(-scrollY);
// I also have a drop shadow on the Toolbar, this removes the
// shadow when the list is scrolled to the top
mToolbarCard.setCardElevation(scrollY <= 0 ? 0 : toolbarElevation);
}
});
My layout looks like this, if it helps:
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- this corresponds to your light green background -->
<FrameLayout
android:id="#+id/app_bar_bg"
android:layout_width="match_parent"
android:layout_height="#dimen/toolbar_container_height"
android:background="#color/primary" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<!-- CardView adds a drop shadow to the Toolbar -->
<android.support.v7.widget.CardView
android:id="#+id/toolbar_card"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:cardCornerRadius="0dp">
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/colorPrimary"
app:popupTheme="#style/ThemeOverlay.AppCompat.Light"
app:theme="#style/ThemeOverlay.AppCompat.Dark.ActionBar" />
</android.support.v7.widget.CardView>
<android.support.v7.widget.RecyclerView
android:id="#+id/post_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:scrollbarStyle="outsideOverlay"
android:scrollbars="vertical" />
</LinearLayout>
</FrameLayout>
Hope this helps!
I probably wouldn't use the background of the RecyclerView for the actual thing that moves. Maybe don't put a background on the RecyclerView and instead of a different view that is behind the RecyclerView that actually moves. Then you could override onDraw or onLayout on the RecyclerView and update the position of your background to wherever you want it to be relative to the scroll percentage of the RecyclerView.
Something like this...
XML:
<RelativeLayout ...>
<SomeBackgroundView id="backgroundView" ...>
<MyRecyclerView ...>
</RelativeLayout>
Code:
class MyRecyclerView extends RecyclerView {
protected int mLastScroll = Integer.MAX_VALUE;
protected ScrollChangedListener mScrollChangedListener;
// ...
#Override
void onDraw(Canvas c) {
int scrollY = getScrollY();
if (scrollY != mLastScroll) {
mLastScroll = scrollY;
if (mScrollChangedListener!= null)
mScrollChangedListener.onScrollChanged(scrollY);
}
super.onDraw(c);
}
public void setScrollChangedListener(ScrollChangedListener listener) {
mScrollChangedListener = listener;
}
public interface ScrollChangedListener {
void onScrollChanged(int newScroll);
}
}
class SomeActivity extends Activity implements ScrollChangedListener {
// ...
#Override
void onScrollChanged(int newScroll) {
// Set the background view's position based on newScroll
}
}

Categories

Resources