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
Related
I have a View and a RecyclerView housed in a LinearLayout. What I want to achieve is something like this:
https://material.google.com/patterns/scrolling-techniques.html#scrolling-techniques-behavior
Basically when I scroll the RecyclerView up, the View collapses. It expands if I scroll the RecyclerView down.
I've tried various methods but the animation stutters if the finger jerks around a scroll position. It only animates well if the finger does a deliberate scroll movement in one direction. How do I do this correctly?
You have to use Coordinator Layout with the CollapsingToolbarLayout
<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:clipToPadding="false">
<android.support.design.widget.AppBarLayout
android:id="#+id/app_bar_layout"
android:layout_width="match_parent"
android:layout_height="210dp"
android:stateListAnimator="#animator/appbar_always_elevated" //I put this here because I want to have shadow when is open, but you have to create the xml file.
android:background="#color/WHITE">
<android.support.design.widget.CollapsingToolbarLayout
android:id="#+id/collapsing_toolbar_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:collapsedTitleTextAppearance="#style/TextAppearance.AppCompat.Widget.ActionBar.Title.Inverse"
app:expandedTitleMarginStart="72dp"
app:expandedTitleTextAppearance="#style/TextAppearance.AppCompat.Widget.ActionBar.Title.Inverse"
app:layout_scrollFlags="scroll|exitUntilCollapsed"> //HERE you should take a look what you want your collapse bar do.
<Here you put the content for you collapse bar, like a ImageView>
<android.support.v7.widget.Toolbar //This is the size of your fixed bar when you collapse, even here you can put a back button, for example
android:id="#+id/app_bar"
android:layout_width="match_parent"
android:layout_height="40dp"
app:layout_collapseMode="pin" />
</android.support.design.widget.CollapsingToolbarLayout>
</android.support.design.widget.AppBarLayout>
<android.support.v4.widget.SwipeRefreshLayout
android:id="#+id/main_home_list_swipe"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="#string/appbar_scrolling_view_behavior" >
<android.support.v7.widget.RecyclerView
android:id="#+id/main_home_list"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</android.support.design.widget.CoordinatorLayout >
Obs: the comments will give errors if you put on a xml file. Is on propose so you will remember to read hahahah
Try this:-
I also want this kind of animation on custom view and i have achieved it this way.
public class TestActivity extends AppCompatActivity {
private static final int HIDE_THRESHOLD = 20;
//this is you custom layout it is any thing.
LinearLayout customLayout;
private int scrolledDistance = 0;
private boolean controlsVisible = true;
private RecyclerView recyclerView;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.your_layout);
recyclerView = (RecyclerView) findViewById(R.id.recyclerView);
recyclerView.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);
scrolledDistance = dy;
if (scrolledDistance > HIDE_THRESHOLD && controlsVisible) {
hideViews();
controlsVisible = false;
} else if (scrolledDistance < -HIDE_THRESHOLD && !controlsVisible) {
showViews();
controlsVisible = true;
}
}
});
}
private void hideViews() {
customLayout.animate().translationY(-customLayout.getHeight()).setInterpolator(new AccelerateInterpolator(2));
}
private void showViews() {
customLayout.animate().translationY(0).setInterpolator(new DecelerateInterpolator(2));
}
}
Edit - 1
for ScrollView try this listener
scrollView.getViewTreeObserver().addOnScrollChangedListener(new ViewTreeObserver.OnScrollChangedListener() {
#Override
public void onScrollChanged() {
if (scrolledDistance > HIDE_THRESHOLD && controlsVisible) {
hideViews();
controlsVisible = false;
scrolledDistance = 0;
} else if (scrolledDistance < -HIDE_THRESHOLD && !controlsVisible) {
showViews();
controlsVisible = true;
scrolledDistance = 0;
}
}
});
Hope it also helps you...
To achieve toolbar to expand and collapse smoothly you can apply translate animation or use CoordinatorLayout with AppBarLayout and Toolbar.
Animation : First you have to detect scroll up and scroll down on your RecyclerView. To do so you can set “setOnScrollListener” on your RecyclerView. Once you have both scroll up and scroll down, simply apply animation.
Code:
rvHomeList.setOnScrollListener(new RecyclerView.OnScrollListener() {
int verticalOffset;
boolean scrollingUp;
#Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
if (newState == RecyclerView.SCROLL_STATE_IDLE) {
if (scrollingUp) {
Log.e("onScrollStateChanged", "UP");
if (verticalOffset > llTop.getHeight()) {
toolbarAnimateHide();
}
} else {
Log.e("onScrollStateChanged", "down");
if (llTop.getTranslationY() < llTop.getHeight() * -0.6 && verticalOffset > llTop.getHeight()) {
toolbarAnimateHide();
} else {
toolbarAnimateShow(verticalOffset);
}
}
}
}
#Override
public final void onScrolled(RecyclerView recyclerView, int dx, int dy) {
verticalOffset += dy;
scrollingUp = dy > 0;
int toolbarYOffset = (int) (dy - llTop.getTranslationY());
llTop.animate().cancel();
if (scrollingUp) {
Log.e("onScrolled", "UP");
if (toolbarYOffset < llTop.getHeight()) {
llTop.setTranslationY(-toolbarYOffset);
} else {
llTop.setTranslationY(-llTop.getHeight());
}
} else {
Log.e("onScrolled", "down");
if (toolbarYOffset < 0) {
llTop.setTranslationY(0);
} else {
llTop.setTranslationY(-toolbarYOffset);
}
}
}
});
Animation Methods:
private void toolbarAnimateShow(final int verticalOffset) {
if (!isShowing) {
isShowing = true;
llTop.animate()
.translationY(0)
.setInterpolator(new LinearInterpolator())
.setDuration(180)
.setListener(new AnimatorListenerAdapter() {
#Override
public void onAnimationStart(Animator animation) {
llTop.setVisibility(View.VISIBLE);
isShowing = false;
}
});
}
}
private void toolbarAnimateHide() {
if (!isHidding) {
isHidding = true;
llTop.animate()
.translationY(-llTop.getHeight())
.setInterpolator(new LinearInterpolator())
.setDuration(180)
.setListener(new AnimatorListenerAdapter() {
#Override
public void onAnimationEnd(Animator animation) {
llTop.setVisibility(View.GONE);
isHidding = false;
}
});
}
}
CoordinatorLayout with AppBarLayout and Toolbar: By using coordinatorLayout with appBarLayout and toolbar, and set the scroll flag used within the attribute app:layout_scrollFlags to achieve the scroll effect. It must be enabled for any scroll effects to take into effect. This flag must be enabled along with enterAlways, enterAlwaysCollapsed, exitUntilCollapsed, or snap.
enterAlways: The view will become visible when scrolling up. This flag is useful in cases when scrolling from the bottom of a list and wanting to expose the Toolbar as soon as scrolling up takes place.
enterAlwaysCollapsed: Normally, when only enterAlways is used, the Toolbar will continue to expand as you scroll down.Assuming enterAlways is declared and you have specified a minHeight, you can also specify enterAlwaysCollapsed. When this setting is used, your view will only appear at this minimum height. Only when scrolling reaches to the top will the view expand to its full height
exitUntilCollapsed: When the scroll flag is set, scrolling down will normally cause the entire content to move.By specifying a minHeight and exitUntilCollapsed, the minimum height of the Toolbar will be reached before the rest of the content begins to scroll and exit from the screen
snap: Using this option will determine what to do when a view only has been partially reduced. If scrolling ends and the view size has been reduced to less than 50% of its original, then this view to return to its original size. If the size is greater than 50% of its sized, it will disappear completely.
Code:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/llBase"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/white"
android:orientation="vertical">
<android.support.design.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
<android.support.design.widget.AppBarLayout
android:id="#+id/appbarContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="#style/ThemeOverlay.AppCompat.Dark.ActionBar">
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="#dimen/_40sdp"
android:gravity="center"
android:theme="#style/ThemeOverlay.AppCompat.Light"
app:layout_scrollFlags="scroll|enterAlways">
<include
layout="#layout/top_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</android.support.v7.widget.Toolbar>
</android.support.design.widget.AppBarLayout>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_behavior="#string/appbar_scrolling_view_behavior">
<RelativeLayout
android:id="#+id/rlMain"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"/>
</RelativeLayout>
</android.support.design.widget.CoordinatorLayout>
</LinearLayout>
You need to use CoordinatorLayout to achieve what you want.
You could find all needed information in this tutorial.
I have a recycler view and a banner AD in fragment view and i want to scroll ad with the recycler view when i scroll it down.
i am using nested scroll for it and ad is scrolling fine initially. but when i scroll it down and more data comes from server then its not remain smooth and stopping some times.
I have tried every sol on stack but nothing working for me.
Here is some code.
fragment_all.xml :
<android.support.v4.widget.NestedScrollView
android:layout_width="match_parent"
android:id="#+id/nestedScroll"
android:layout_height="match_parent"
android:fillViewport="true"
android:scrollbars="none"
>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<com.google.android.gms.ads.AdView
android:id="#+id/adView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:visibility="gone"
android:focusableInTouchMode="true"
ads:adSize="BANNER"
ads:adUnitId="ca-app-pub-3940256099942544/6300978111">
</com.google.android.gms.ads.AdView>
<android.support.v7.widget.RecyclerView
android:id="#+id/news_recycler_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="3"
android:nestedScrollingEnabled="false"
/>
</LinearLayout>
</android.support.v4.widget.NestedScrollView>
fragment_all.java :
nestedScrollView.setOnScrollChangeListener(new NestedScrollView.OnScrollChangeListener() {
#Override
public void onScrollChange(NestedScrollView v, int scrollX, int scrollY, int oldScrollX, int oldScrollY) {
if(v.getChildAt(v.getChildCount() - 1) != null) {
if ((scrollY >= (v.getChildAt(v.getChildCount() - 1).getMeasuredHeight() - v.getMeasuredHeight())) &&
scrollY > oldScrollY) {
visibleItemCount = layoutManager.getChildCount();
totalItemCount = layoutManager.getItemCount();
pastVisibleItems = ((LinearLayoutManager) recyclerView.getLayoutManager())
.findFirstVisibleItemPosition();
if ((visibleItemCount + pastVisibleItems) >=totalItemCount) {
if (!loadingInside) {
loadingInside = true;
postNo = postNo + 15;
getDataFromUrl(postNo);
}
}
}
}
}
});
NOTE : i have tried solutions like :
<app:layout_behavior="#string/appbar_scrolling_view_behavior"
android:nestedScrollingEnabled="false"/>
Please set this
rec_view_notification.setLayoutManager(new LinearLayoutManager(getContext(),
LinearLayoutManager.VERTICAL, false) {
#Override
public boolean canScrollHorizontally() {
return false;
}
#Override
public boolean canScrollVertically() {
return false;
}
});
You can set your RecyclerView Nested Scrolling false
Like
recyclerViewSignup.setNestedScrollingEnabled(false);
I have problem with RecyclerView.
I want change widget location at screen when scroll recyclerView by
onScrolled(RecyclerView recyclerView, int dx, int dy)
method.
In this method I change another widget location by change LayoutParams. But I receive shiver when slowly scrolling.
RecyclerView has match_parent height, when I change location of another view who free space to stretch RecyclerView.
How I can solve shiver of RecyclerView when stretch?
Dynamically changing the height of RecyclerView seems anti-pattern.
I recommend you to use paddingTop and clipToPadding="false".
Your layout xml may be like this:
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.RecyclerView
android:id="#+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"/>
<FrameLayout
android:id="#+id/header_container"
android:layout_width="match_parent"
android:layout_height="match_parent">
... here is same layout, samll text and "STARTEN" button.
</FrameLayout>
</FrameLayout>
Your java program may be like this:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
final View headerContainer = findViewById(R.id.header_container);
final RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
... initialization of recyclerView ...
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
//change height of headerContainer
}
});
// set paddingTop of RecyclerView
headerContainer.getViewTreeObserver().addOnGlobalLayoutListener(
new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
headerContainer.getViewTreeObserver().removeOnGlobalLayoutListener(this);
} else {
headerContainer.getViewTreeObserver().removeGlobalOnLayoutListener(this);
}
recyclerView.setPadding(
recyclerView.getPaddingLeft(),
headerContainer.getHeight(),
recyclerView.getPaddingRight(),
recyclerView.getPaddingBottom()
);
}
});
}
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
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.