Reputation: 1472
Are there anyway to make Android Design Support Library's Collapsing animation smoother while scrolling? When I release scrolling, it stops suddenly. But what I want is: collapsing animation will continue smoothly even if you stop scrolling. Android-ObservableScrollView and Scrollable are the libraries that are collapsing smoothly.
Upvotes: 40
Views: 11855
Reputation: 2829
I'm doing it through AppBarLayout
. by overriding onNestedFling
and onNestedPreScroll
.
The trick is to reconsume fling event if ScrollingView's top child is close to the beginning of data in Adapter.
Source Flinging with RecyclerView + AppBarLayout
public final class FlingBehavior extends AppBarLayout.Behavior {
private static final int TOP_CHILD_FLING_THRESHOLD = 3;
private boolean isPositive;
public FlingBehavior() {
}
public FlingBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean onNestedFling(CoordinatorLayout coordinatorLayout, AppBarLayout child, View target, float velocityX, float velocityY, boolean consumed) {
if (velocityY > 0 && !isPositive || velocityY < 0 && isPositive) {
velocityY = velocityY * -1;
}
if (target instanceof RecyclerView && velocityY < 0) {
final RecyclerView recyclerView = (RecyclerView) target;
final View firstChild = recyclerView.getChildAt(0);
final int childAdapterPosition = recyclerView.getChildAdapterPosition(firstChild);
consumed = childAdapterPosition > TOP_CHILD_FLING_THRESHOLD;
}
return super.onNestedFling(coordinatorLayout, child, target, velocityX, velocityY, consumed);
}
@Override
public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, AppBarLayout child, View target, int dx, int dy, int[] consumed) {
super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed);
isPositive = dy > 0;
}
}
Then set the layout behavior as FlingBehavior
class
<android.support.design.widget.AppBarLayout
app:layout_behavior="package.FlingBehavior"
android:id="@+id/appbar"
android:layout_width="match_parent"
android:layout_height="250dip"
android:fitsSystemWindows="true">
Upvotes: 3
Reputation: 21
Try adding the following code:
app:layout_scrollFlags="scroll|snap"
Upvotes: 1
Reputation: 1287
You can use the new layout_scrollFlag snap for smooth scroll within the AppBarLayout states. But what I have experienced is that, when the RecyclerView reaches top, scrolling stops. i.e CollapsingToolbarLayout won't get expanded without another scroll. For the RecyclerView to scroll smoothly up and expand the CollapsingToolbarLayout I have used a ScrollListener on recyclerview.
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
int scrollDy = 0;
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
scrollDy += dy;
}
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
if(scrollDy==0&&(newState == AbsListView.OnScrollListener.SCROLL_STATE_IDLE))
{
AppBarLayout appBarLayout = ((AppBarLayout) view.findViewById(R.id.app_bar));
appBarLayout.setExpanded(true);
}
}
});
I used "scroll|exitUntilCollapsed" as layout_scrollFlags.
<android.support.design.widget.CollapsingToolbarLayout
android:id="@+id/collapsing_toolbar_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
android:minHeight="80dp"
app:layout_collapseMode="none"
app:layout_scrollFlags="scroll|exitUntilCollapsed">
Upvotes: 10
Reputation: 17453
I am working on this problem too and have come up with may not very optimized solution but you can improve it.Once I improve it I will definitely edit answer till the have a look at this.
public abstract class AppBarStateChangeListener implements AppBarLayout.OnOffsetChangedListener {
private static final String TAG = "app_AppBarStateChange";
public enum State {
EXPANDED,
COLLAPSED,
IDLE
}
private State mCurrentState = State.IDLE;
private int mInitialPosition = 0;
private boolean mWasExpanded;
private boolean isAnimating;
@Override
public final void onOffsetChanged(AppBarLayout appBarLayout, int i) {
if (i == 0) {
if (mCurrentState != State.EXPANDED) {
onStateChanged(appBarLayout, State.EXPANDED);
}
mCurrentState = State.EXPANDED;
mInitialPosition = 0;
mWasExpanded = true;
Log.d(TAG, "onOffsetChanged 1");
isAnimating = false;
appBarLayout.setEnabled(true);
} else if (Math.abs(i) >= appBarLayout.getTotalScrollRange()) {
if (mCurrentState != State.COLLAPSED) {
onStateChanged(appBarLayout, State.COLLAPSED);
}
mCurrentState = State.COLLAPSED;
mInitialPosition = appBarLayout.getTotalScrollRange();
mWasExpanded = false;
Log.d(TAG, "onOffsetChanged 2");
isAnimating = false;
appBarLayout.setEnabled(true);
} else {
Log.d(TAG, "onOffsetChanged 3");
int diff = Math.abs(Math.abs(i) - mInitialPosition);
if(diff >= appBarLayout.getTotalScrollRange()/3 && !isAnimating) {
Log.d(TAG, "onOffsetChanged 4");
isAnimating = true;
appBarLayout.setEnabled(false);
appBarLayout.setExpanded(!mWasExpanded,true);
}
if (mCurrentState != State.IDLE) {
onStateChanged(appBarLayout, State.IDLE);
}
mCurrentState = State.IDLE;
}
}
public abstract void onStateChanged(AppBarLayout appBarLayout, State state);
public State getCurrentState() {
return mCurrentState;
}
}
Create this class and call following code
private AppBarStateChangeListener mAppBarStateChangeListener = new AppBarStateChangeListener() {
@Override
public void onStateChanged(AppBarLayout appBarLayout, State state) {
Log.d(TAG, "ToBeDeletedActivity.onStateChanged :: " + state);
if(state == State.EXPANDED || state == State.IDLE) {
getSupportActionBar().setTitle("");
} else {
getSupportActionBar().setTitle("Hello World");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
mAppBarLayout.setElevation(0);
}
}
}
};
mAppBarLayout.addOnOffsetChangedListener(mAppBarStateChangeListener);
Note that do not set annonymous class OffsetChangedListener as this is kept as weak reference and will be collected by GC.I found my self in hard way.
Kindly explore this code and improve it(anyone) and re-share it .Thanks
Upvotes: 0
Reputation: 2059
add code
app:layout_scrollFlags="scroll|enterAlways"
in the view inside the AppBarLayout. This is my demo code collapsing the Toolbar with Android Design Support Library.
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/ThemeOverlay.AppCompat.Dark">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:layout_scrollFlags="scroll|enterAlways">
<bubee.inews.Items.ItemMenu
android:id="@+id/itemMenu"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</android.support.v7.widget.Toolbar>
</android.support.design.widget.AppBarLayout>
Upvotes: 2
Reputation: 8304
This one is fairly new, but the AppBarLayout has been recently updated to handle exactly what you're looking for with a new layout_scrollFlag called snap
.
Usage:
app:layout_scrollFlags="scroll|snap"
I'll try to look for my source and update my answer when I do.
Edit: Of course, it's from the android developer blog.
Upvotes: 7