I implemented a collapsingtoolbar layout with a recyclerview as shown in the sample code attached. My issue is that, when I fling the list downward, it does not go all the way to the top.
What happens is that, the scrolling stops right at the point where the AppBarLayout is supposed to end.
The effect that I want is upon flinging the list downward, the list will go all the way to the top AND reveal/expand the AppBarLayout
My minSdk is 14. Any help or suggestion is greatly appreciated.
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.AppBarLayout>
<android.support.design.widget.CollapsingToolbarLayout
app:layout_scrollFlags="scroll|exitUntilCollapsed">
<LinearLayout
app:layout_collapseMode="parallax">
//some elements
</LinearLayout>
</android.support.design.widget.CollapsingToolbarLayout>
</android.support.design.widget.AppBarLayout>
<android.support.v7.widget.RecyclerView
app:layout_behavior="#string/appbar_scrolling_view_behavior"/> //value android.support.design.widget.AppBarLayout$ScrollingViewBehavior
<android.support.v7.widget.Toolbar
app:popupTheme="#style/AppTheme.PopupOverlay"
app:layout_collapseMode="parallax" />
I had similar problem and I used a simple trick to expand AppBarLayout when RecyclerView fling to top (you need to have support library >= 23.x.x)
mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
if (newState == RecyclerView.SCROLL_STATE_IDLE) {
int firstVisiblePosition = linearLayoutManager.findFirstCompletelyVisibleItemPosition();
if (firstVisiblePosition == 0) {
mAppBarLayout.setExpanded(true, true);
}
}
}
});
You can fully expand or collapse the App Bar with the setExpanded() method. One implementation could involve overriding dispatchTouchEvent() in your Activity class, and auto-collapsing/expanding your App Bar based on whether it is collapsed past the halfway point:
#Override
public boolean dispatchTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_UP) {
float per = Math.abs(mAppBarLayout.getY()) / mAppBarLayout.getTotalScrollRange();
boolean setExpanded = (per <= 0.5F);
mAppBarLayout.setExpanded(setExpanded, true);
}
return super.dispatchTouchEvent(event);
}
In respect to automatically scrolling to the last position on a fling, I have put some code on GitHub that shows how to programmatically smooth scroll to a specific location that may help. Calling a scroll to list.size() - 1 on a fling for instance could replicate the behaviour. Parts of this code by the way are adapted from the StylingAndroid and Novoda blogs:
public class RecyclerLayoutManager extends LinearLayoutManager {
private AppBarManager mAppBarManager;
private int visibleHeightForRecyclerView;
public RecyclerLayoutManager(Context context) {
super(context);
}
#Override
public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state, int position) {
View firstVisibleChild = recyclerView.getChildAt(0);
final int childHeight = firstVisibleChild.getHeight();
int distanceInPixels = ((findFirstVisibleItemPosition() - position) * childHeight);
if (distanceInPixels == 0) {
distanceInPixels = (int) Math.abs(firstVisibleChild.getY());
}
//Called Once
if (visibleHeightForRecyclerView == 0) {
visibleHeightForRecyclerView = mAppBarManager.getVisibleHeightForRecyclerViewInPx();
}
//Subtract one as adapter position 0 based
final int visibleChildCount = visibleHeightForRecyclerView/childHeight - 1;
if (position <= visibleChildCount) {
//Scroll to the very top and expand the app bar
position = 0;
mAppBarManager.expandAppBar();
} else {
mAppBarManager.collapseAppBar();
}
SmoothScroller smoothScroller = new SmoothScroller(recyclerView.getContext(), Math.abs(distanceInPixels), 1000);
smoothScroller.setTargetPosition(position);
startSmoothScroll(smoothScroller);
}
public void setAppBarManager(AppBarManager appBarManager) {
mAppBarManager = appBarManager;
}
private class SmoothScroller extends LinearSmoothScroller {
private static final int TARGET_SEEK_SCROLL_DISTANCE_PX = 10000;
private final float distanceInPixels;
private final float duration;
public SmoothScroller(Context context, int distanceInPixels, int duration) {
super(context);
this.distanceInPixels = distanceInPixels;
float millisecondsPerPx = calculateSpeedPerPixel(context.getResources().getDisplayMetrics());
this.duration = distanceInPixels < TARGET_SEEK_SCROLL_DISTANCE_PX ?
(int) (Math.abs(distanceInPixels) * millisecondsPerPx) : duration;
}
#Override
public PointF computeScrollVectorForPosition(int targetPosition) {
return RecyclerLayoutManager.this
.computeScrollVectorForPosition(targetPosition);
}
#Override
protected int calculateTimeForScrolling(int dx) {
float proportion = (float) dx / distanceInPixels;
return (int) (duration * proportion);
}
}
}
Edit:
AppBarManager in the above code snippet refers to an interface used to communicate with the AppBarLayout in an Activity. Collapse/expand app bar methods do just that, with animations. The final method is used to calculate the number of RecyclerView rows visible on screen:
AppBarManager.java
public interface AppBarManager {
void collapseAppBar();
void expandAppBar();
int getVisibleHeightForRecyclerViewInPx();
}
MainActivity.java
public class MainActivity extends AppCompatActivity implements AppBarManager{
#Override
public void collapseAppBar() {
mAppBarLayout.setExpanded(false, true);
}
#Override
public void expandAppBar() {
mAppBarLayout.setExpanded(true, true);
}
#Override
public int getVisibleHeightForRecyclerViewInPx() {
if (mRecyclerFragment == null) mRecyclerFragment =
(RecyclerFragment) getSupportFragmentManager().findFragmentByTag(RecyclerFragment.TAG);
int windowHeight, appBarHeight, headerViewHeight;
windowHeight = getWindow().getDecorView().getHeight();
appBarHeight = mAppBarLayout.getHeight();
headerViewHeight = mRecyclerFragment.getHeaderView().getHeight();
return windowHeight - (appBarHeight + headerViewHeight);
}
Related
How can I snap to particular position for LinearSnapHelper() in horizontal RecyclerView? There is a function scrolltoposition for RecyclerView which scroll to that position but did not keep it in center for this snaphelper.
I am looking for something like below image. So when I set to particular position, it will keep it in center. I dont find anything related to select position for SnapHelper
i find this , but this doesn't help me.
Any help would be appreciated.
If I understand your question, you are looking for a way to jump to a position and have that position centered in the RecyclerView.
Maybe you have tried RecyclerView.scrollToPosition() but that doesn't snap to the view. You may also have tried RecyclerView.smoothScrollToPosition() and that works better but you may want to avoid all the movement if you have a lot of items and are scrolling a long way.
The reason that scrollToPosition() doesn't work is that it doesn't trigger the LinearSnapHelper which uses a scroll listener to detect when to snap. Since smoothScrollToPosition() does trigger the LinearSnapHelper, we will use scrollToPosition() to get us in the area of the target view then use smoothScrollToPosition() to get the view centered as follows:
private RecyclerView mRecycler;
private void newScrollTo(final int pos) {
RecyclerView.ViewHolder vh = mRecycler.findViewHolderForLayoutPosition(pos);
if (vh != null) {
// Target view is available, so just scroll to it.
mRecycler.smoothScrollToPosition(pos);
} else {
// Target view is not available. Scroll to it.
mRecycler.addOnScrollListener(new RecyclerView.OnScrollListener() {
// From the documentation:
// This callback will also be called if visible item range changes after a layout
// calculation. In that case, dx and dy will be 0.This callback will also be called
// if visible item range changes after a layout calculation. In that case,
// dx and dy will be 0.
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
mRecycler.removeOnScrollListener(this);
if (dx == 0) {
newScrollTo(pos);
}
}
});
mRecycler.scrollToPosition(pos);
}
}
Sample app
MainActivity.java
public class MainActivity extends AppCompatActivity {
private final LinearLayoutManager mLayoutManager = new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false);
private final List<String> mItems = new ArrayList<>();
private RecyclerView mRecycler;
private final int mItemCount = 2000;
private final Handler mHandler = new Handler();
private final LinearSnapHelper mLinearSnapHelper = new LinearSnapHelper();
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
for (int i = 0; i < mItemCount; i++) {
mItems.add(i + "");
}
mRecycler = findViewById(R.id.recyclerView);
final RecyclerViewAdapter adapter = new RecyclerViewAdapter(null);
adapter.setItems(mItems);
mRecycler.setLayoutManager(mLayoutManager);
mRecycler.setAdapter(adapter);
mLinearSnapHelper.attachToRecyclerView(mRecycler);
newScrollTo(1);
// fireScrollTo();
}
private int maxScrolls = mItemCount;
private void fireScrollTo() {
if (--maxScrolls > 0) {
int pos = (int) (Math.random() * mItemCount);
newScrollTo(pos);
mHandler.postDelayed(new Runnable() {
#Override
public void run() {
fireScrollTo();
}
}, 2000);
}
}
private void newScrollTo(final int pos) {
mRecycler.smoothScrollToPosition(pos);
RecyclerView.ViewHolder vh = mRecycler.findViewHolderForLayoutPosition(pos);
if (vh != null) {
// Target view is available, so just scroll to it.
mRecycler.smoothScrollToPosition(pos);
} else {
// Target view is not available. Scroll to it.
mRecycler.addOnScrollListener(new RecyclerView.OnScrollListener() {
// From the documentation:
// This callback will also be called if visible item range changes after a layout
// calculation. In that case, dx and dy will be 0.This callback will also be called
// if visible item range changes after a layout calculation. In that case,
// dx and dy will be 0.
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
mRecycler.removeOnScrollListener(this);
if (dx == 0) {
newScrollTo(pos);
}
}
});
mRecycler.scrollToPosition(pos);
}
}
}
activity_main.xml
<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<View
android:layout_width="3px"
android:layout_height="match_parent"
android:background="#android:color/holo_red_light"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<android.support.v7.widget.RecyclerView
android:id="#+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:paddingStart="660px"
android:paddingEnd="660px"/>
</android.support.constraint.ConstraintLayout>
RecyclerViewAdapter.java
class RecyclerViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private List<String> mItems;
RecyclerViewAdapter(List<String> items) {
mItems = items;
}
#Override
public #NonNull
RecyclerView.ViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(android.R.layout.simple_list_item_1, parent, false);
view.getLayoutParams().width = 220;
view.getLayoutParams().height = 220;
// view.setPadding(220 * 3, 0, 220 * 3, 0);
((TextView) view).setGravity(Gravity.CENTER);
return new ItemViewHolder(view);
}
#Override
public void onBindViewHolder(#NonNull RecyclerView.ViewHolder holder, int position) {
ItemViewHolder vh = (ItemViewHolder) holder;
String itemText = mItems.get(position);
vh.mItemTextView.setText(itemText);
int bgColor = (position % 2 == 0)
? android.R.color.holo_blue_light
: android.R.color.holo_green_light;
holder.itemView.setBackgroundColor(
holder.itemView.getContext().getResources().getColor(bgColor));
}
#Override
public int getItemCount() {
return (mItems == null) ? 0 : mItems.size();
}
#Override
public int getItemViewType(int position) {
return TYPE_ITEM;
}
static class ItemViewHolder extends RecyclerView.ViewHolder {
private TextView mItemTextView;
ItemViewHolder(View item) {
super(item);
mItemTextView = item.findViewById(android.R.id.text1);
}
}
public void setItems(List<String> items) {
mItems = items;
}
#SuppressWarnings("unused")
private final static String TAG = "RecyclerViewAdapter";
private final static int TYPE_ITEM = 1;
}
Add this where ever you want to scroll your recycler view
recyclerView.scrollToPosition(position)
recyclerView.post {
var view = recyclerView.layoutManager?.findViewByPosition(position);
if (view == null) {
// do nothing
}
var snapDistance = snapHelper.calculateDistanceToFinalSnap(recyclerView.layoutManager!!, view!!)
if (snapDistance?.get(0) != 0 || snapDistance[1] != 0) {
recyclerView.scrollBy(snapDistance?.get(0)!!, snapDistance?.get(1));
}
}
By using this LinearSnap Helper which is attached to your recycler view
var snapHelper = LinearSnapHelper()
snapHelper.attachToRecyclerView(recyclerView)
For horizontal RecyclerView, you should use PagerSnapHelper() instead of LinearSnapHelper().
Just did some research and trace the SnapHelper's source code, it turns out the solution could be very simple:
class MyPagerSnapHelper: PagerSnapHelper() {
fun smoothScrollToPosition(layoutManager: RecyclerView.LayoutManager, position: Int) {
val smoothScroller = createScroller(layoutManager) ?: return
smoothScroller.targetPosition = position
layoutManager.startSmoothScroll(smoothScroller)
}
}
And then you can pass RecyclerView's LayoutManager and target position here
snapHelper.smoothScrollToPosition(recyclerView.layoutManager!!, index)
I'm using a LinearSnapHelper to make items in my RecyclerView "snap" into place on the screen (my cards take up most of the screen, so I want them to snap into place and fill the screen on every swipe/fling/scroll).
I'm struggling with how to make the cards snap into place faster. I've tried creating a custom LinearLayoutManager (and editing the calculateSpeedPerPixel method in scrollToPosition or smoothScrollToPosition), as well as a custom RecyclerView (and editing the fling method). But nothing effects the speed that cards "snap" into place.
I suppose the issue is that I don't really understand how LinearSnapHelper "scrolls" the cards into position. It doesn't seem to use LinearLayoutManager's scrollToPosition or smoothScrollToPosition methods.
snapHelper = new LinearSnapHelper() {
#Override
public int findTargetSnapPosition(RecyclerView.LayoutManager layoutManager, int velocityX, int velocityY) {
View centerView = findSnapView(layoutManager);
if (centerView == null) {
return RecyclerView.NO_POSITION;
}
int position = layoutManager.getPosition(centerView);
int targetPosition = -1;
if (layoutManager.canScrollHorizontally()) {
if (velocityX < 0) {
targetPosition = position - 1;
} else {
targetPosition = position + 1;
}
}
if (layoutManager.canScrollVertically()) {
if (velocityY > 0) {
targetPosition = position + 1;
} else {
targetPosition = position - 1;
}
}
final int firstItem = 0;
final int lastItem = layoutManager.getItemCount() - 1;
targetPosition = Math.min(lastItem, Math.max(targetPosition, firstItem));
return targetPosition;
}
};
snapHelper.attachToRecyclerView(mRecyclerView);
As 郭玉龙 mentioned, SnapHelper call RecyclerView.smoothScrollBy() method. And it use default sQuinticInterpolator.
To change speed of snap you can do next:
public class SlowdownRecyclerView extends RecyclerView {
// Change pow to control speed.
// Bigger = faster. RecyclerView default is 5.
private static final int POW = 2;
private Interpolator interpolator;
public SlowdownRecyclerView(Context context) {
super(context);
createInterpolator();
}
public SlowdownRecyclerView(Context context, #Nullable AttributeSet attrs) {
super(context, attrs);
createInterpolator();
}
public SlowdownRecyclerView(Context context, #Nullable AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
createInterpolator();
}
private void createInterpolator(){
interpolator = new Interpolator() {
#Override
public float getInterpolation(float t) {
t = Math.abs(t - 1.0f);
return (float) (1.0f - Math.pow(t, POW));
}
};
}
#Override
public void smoothScrollBy(int dx, int dy) {
super.smoothScrollBy(dx, dy, interpolator);
}
Or you can implement your own interpolator.
The speed of snapping scroll is affected by RecyclerView.smoothScrollBy().
Here's the snippet of source code.
Override this function to increase or decrease the speed of snapping scroll.
I wound up doing this by adding a ScrollListener to my RecycleView, and then creating a custom LinearLayoutManager and custom smoothScrollToPosition method.
final CustomLinearLayoutManager mLayoutManager = new CustomLinearLayoutManager(getActivity());
mRecyclerView.setLayoutManager(mLayoutManager);
mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
private boolean scrollingUp;
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
scrollingUp = dy < 0;
}
#Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
int visiblePosition = scrollingUp ? mLayoutManager.findFirstVisibleItemPosition() : mLayoutManager.findLastVisibleItemPosition();
int completelyVisiblePosition = scrollingUp ? mLayoutManager
.findFirstCompletelyVisibleItemPosition() : mLayoutManager
.findLastCompletelyVisibleItemPosition();
if (visiblePosition != completelyVisiblePosition) {
recyclerView.smoothScrollToPosition(visiblePosition);
return;
}
}
});
I achieved this using a library https://github.com/rubensousa/GravitySnapHelper
you can also override findTargetSnapPosition to get pager like scroll
tweek the scrollMsPerInch to increase / decrease speed
val snapHelper : GravitySnapHelper = GravitySnapHelper(Gravity.CENTER)
// the lower the higher the speed, default is 100f
snapHelper.scrollMsPerInch = 40f
snapHelper.attachToRecyclerView(binding?.mRecyclerView)
Actually you can modify the LinearSnapHelper and SnapHelperClass by simply copy/paste the existing code the only thing you will do is to set MILLISECONDS_PER_INCH on SnapHelper as you want and then use simply use the LinearSnapHelper you created
I want to center the clicked position in the Recyclerview. I am able to scroll the Recyclerview to certain position but i want to middle that position in the screen.
I used this method to scroll to that position.
videoRecyclerview.scrollToPosition(position);
If you are using a RecyclerView and LinearLayoutManager this will work:
private void scrollToCenter(View v) {
int itemToScroll = mRecyclerView.getChildPosition(v);
int centerOfScreen = mRecyclerView.getWidth() / 2 - v.getWidth() / 2;
mLayoutManager.scrollToPositionWithOffset(itemToScroll, centerOfScreen);
}
if you use linearlayoutManager, you can use this code,
linearLayoutManager.scrollToPositionWithOffset(2, 20);
(linearLayoutManager.void scrollToPositionWithOffset (int position,
int offset))
Setting the offset to 0 should align with the top
first move scroll to your item, but whenever recyclerView scrolls it just brings the item in visible region, it is never sure that the item is in center or not, so we find the center item and then check if we are on next to center to item or behind it, here is working logic
recyclerView.smoothScrollToPosition(index);
int firstVisibleItemPosition = rvLayoutManager.findFirstVisibleItemPosition();
int lastVisibleItemPosition = rvLayoutManager.findLastVisibleItemPosition();
int centerPosition = (firstVisibleItemPosition + lastVisibleItemPosition) / 2;
if (index > centerPosition) {
recyclerView.smoothScrollToPosition(index + 1);
} else if (index < centerPosition) {
recyclerView.smoothScrollToPosition(index - 1);
}
If you need smooth scroll to centre for LieanerLayoutManager both horizontal & vertical
Copy the entire code and simply call** scrollToCenter:
public void scrollToCenter(LinearLayoutManager layoutManager, RecyclerView recyclerList, int clickPosition) {
RecyclerView.SmoothScroller smoothScroller = createSnapScroller(recyclerList, layoutManager);
if (smoothScroller != null) {
smoothScroller.setTargetPosition(clickPosition);
layoutManager.startSmoothScroll(smoothScroller);
}
}
// This number controls the speed of smooth scroll
private static final float MILLISECONDS_PER_INCH = 70f;
private final static int DIMENSION = 2;
private final static int HORIZONTAL = 0;
private final static int VERTICAL = 1;
#Nullable
private LinearSmoothScroller createSnapScroller(RecyclerView mRecyclerView, RecyclerView.LayoutManager layoutManager) {
if (!(layoutManager instanceof RecyclerView.SmoothScroller.ScrollVectorProvider)) {
return null;
}
return new LinearSmoothScroller(mRecyclerView.getContext()) {
#Override
protected void onTargetFound(View targetView, RecyclerView.State state, Action action) {
int[] snapDistances = calculateDistanceToFinalSnap(layoutManager, targetView);
final int dx = snapDistances[HORIZONTAL];
final int dy = snapDistances[VERTICAL];
final int time = calculateTimeForDeceleration(Math.max(Math.abs(dx), Math.abs(dy)));
if (time > 0) {
action.update(dx, dy, time, mDecelerateInterpolator);
}
}
#Override
protected float calculateSpeedPerPixel(DisplayMetrics displayMetrics) {
return MILLISECONDS_PER_INCH / displayMetrics.densityDpi;
}
};
}
private int[] calculateDistanceToFinalSnap(#NonNull RecyclerView.LayoutManager layoutManager, #NonNull View targetView) {
int[] out = new int[DIMENSION];
if (layoutManager.canScrollHorizontally()) {
out[HORIZONTAL] = distanceToCenter(layoutManager, targetView,
OrientationHelper.createHorizontalHelper(layoutManager));
}
if (layoutManager.canScrollVertically()) {
out[VERTICAL] = distanceToCenter(layoutManager, targetView,
OrientationHelper.createHorizontalHelper(layoutManager));
}
return out;
}
private int distanceToCenter(#NonNull RecyclerView.LayoutManager layoutManager,
#NonNull View targetView, OrientationHelper helper) {
final int childCenter = helper.getDecoratedStart(targetView)
+ (helper.getDecoratedMeasurement(targetView) / 2);
final int containerCenter;
if (layoutManager.getClipToPadding()) {
containerCenter = helper.getStartAfterPadding() + helper.getTotalSpace() / 2;
} else {
containerCenter = helper.getEnd() / 2;
}
return childCenter - containerCenter;
}
it's worked for me with this code :
layoutManager.scrollToPositionWithOffset(pos - 1,0);
//on the click callback
view.OnClickListener { callback?.onItemClicked(it)}
// code in activity or your fragment
override fun onItemClicked(view: View) {
val position = recyclerView.getChildLayoutPosition(view)
recyclerView.smoothScrollToPosition(position)
}
simplest hack ever, this was good enough for me:
videoRecyclerview.scrollToPosition(position+2);
if position+2 is within the arraylist.
I made a custom LayoutManager to smooth scroll after clicking a left or right button. Everything works when scrolling left only! For right, computeScrollVectorForPosition never event gets called. What gives? I've tried setting mReverseLayout when going right, but that hasn't help. Anything I'm not doing/overlooking?
public class SmoothScrollLayoutManager extends LinearLayoutManager {
private static final float MILLISECONDS_PER_INCH = 50f;
private Context context;
public boolean shouldGoRight = false;
public SmoothScrollLayoutManager(Context context, int orientation, boolean reverseLayout) {
super(context, orientation, reverseLayout);
this.context = context;
}
#Override
public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state, int position) {
LinearSmoothScroller smoothScroller = new LinearSmoothScroller(context) {
#Override
public PointF computeScrollVectorForPosition(int targetPosition) {
if (getChildCount() == 0) {
return null;
}
final int firstChildPos = getPosition(getChildAt(0));
final int direction = targetPosition < firstChildPos != shouldGoRight ? -1 : 1;
return new PointF(direction, 0);
}
#Override
protected float calculateSpeedPerPixel(DisplayMetrics displayMetrics) {
return MILLISECONDS_PER_INCH / displayMetrics.densityDpi;
}
};
smoothScroller.setTargetPosition(position);
startSmoothScroll(smoothScroller);
}
}
comupteScrollVectorForPosition is only called to find out the direction into which the LinearSmoothScroller has to scroll to eventually find the element. If the LinearSmoothScroller already thinks to know where the element is, it will not call this function. This is the case for elements that are already loaded to be shown, like your elements to the right.
Id like to achieve a similar effect as the one you can see in Google Play store, where by scrolling the content the Toolbar goes off-screen as you scroll.
This works fine with the CoordinatorLayout (1) introduced at #io15, however: If you stop the scroll "mid-way" the Toolbar remains on screen, but is cut in half: I want it to animate off-screen, just like in the Google Play store. How can I achieve that?
Now the Android Support Library 23.1.0 has a new scroll flag SCROLL_FLAG_SNAP which allows you to achieve this effect.
AppBarLayout supports a number of scroll flags which affect how children views react to scrolling (e.g. scrolling off the screen). New to this release is SCROLL_FLAG_SNAP, ensuring that when scrolling ends, the view is not left partially visible. Instead, it will be scrolled to its nearest edge, making fully visible or scrolled completely off the screen.
Activity Layout file :
<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/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingTop="?attr/actionBarSize"
android:clipToPadding="false"/>
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"/>
</FrameLayout>
Now inside the activity, setup Toolbar and RecyclerView. Assign OnScrollListener to RecyclerView
recyclerView.setOnScrollListener(new MyScrollListener(this));
Extend MyScrollListerner from RecyclerView.OnScrollListener.
public abstract class MyScrollListener extends RecyclerView.OnScrollListener {
private static final float TOOLBAR_HIDE_THRESHOLD = 10;
private static final float TOOLBAR_SHOW_THRESHOLD = 70;
private int mToolbarOffset = 0;
private boolean mControlsVisible = true;
private int mToolbarHeight;
private int mTotalScrolledDistance;
public MyScrollListener(Context context) {
final TypedArray styledAttributes = context.getTheme().obtainStyledAttributes(
new int[]{R.attr.actionBarSize});
mToolbarHeight = (int) styledAttributes.getDimension(0, 0);
styledAttributes.recycle();
return toolbarHeight;
mToolbarHeight = Utils.getToolbarHeight(context);
}
#Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
if(newState == RecyclerView.SCROLL_STATE_IDLE) {
if(mTotalScrolledDistance < mToolbarHeight) {
setVisible();
} else {
if (mControlsVisible) {
if (mToolbarOffset > TOOLBAR_HIDE_THRESHOLD) {
setInvisible();
} else {
setVisible();
}
} else {
if ((mToolbarHeight - mToolbarOffset) > TOOLBAR_SHOW_THRESHOLD) {
setVisible();
} else {
setInvisible();
}
}
}
}
}
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
clipToolbarOffset();
onMoved(mToolbarOffset);
if((mToolbarOffset <mToolbarHeight && dy>0) || (mToolbarOffset >0 && dy<0)) {
mToolbarOffset += dy;
}
if (mTotalScrolledDistance < 0) {
mTotalScrolledDistance = 0;
} else {
mTotalScrolledDistance += dy;
}
}
private void clipToolbarOffset() {
if(mToolbarOffset > mToolbarHeight) {
mToolbarOffset = mToolbarHeight;
} else if(mToolbarOffset < 0) {
mToolbarOffset = 0;
}
}
private void setVisible() {
if(mToolbarOffset > 0) {
onShow();
mToolbarOffset = 0;
}
mControlsVisible = true;
}
private void setInvisible() {
if(mToolbarOffset < mToolbarHeight) {
onHide();
mToolbarOffset = mToolbarHeight;
}
mControlsVisible = false;
}
public abstract void onMoved(int distance);
public abstract void onShow();
public abstract void onHide();
}
Overriding the AppBarLayout seems to be a better solution, as there are two possible scroll-events - of the entire CoordinatorLayout, and of the RecyclerView/NestedScrollView
See this answer as a possible working code:
https://stackoverflow.com/a/32110089/819355