Created
April 28, 2017 00:55
-
-
Save imminent/9aa88c2ca379648755a7aab345e9ebac to your computer and use it in GitHub Desktop.
Overscrolling with Collapsing Toolbar
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import android.content.Context; | |
import android.support.design.widget.AppBarLayout; | |
import android.support.design.widget.CoordinatorLayout; | |
import android.util.AttributeSet; | |
import android.view.View; | |
@SuppressWarnings("unused") | |
public class OverscrollingAppBarBehavior extends AppBarLayout.Behavior { | |
@SuppressWarnings("unused") | |
public OverscrollingAppBarBehavior() { | |
} | |
@SuppressWarnings("unused") | |
public OverscrollingAppBarBehavior(Context context, AttributeSet attrs) { | |
super(context, attrs); | |
} | |
@Override | |
public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, AppBarLayout child, View target, int dx, int dy, | |
int[] consumed) { | |
if (OverscrollingScrollViewBehavior.isAssociatedWith(target)) { | |
final OverscrollingScrollViewBehavior behavior = OverscrollingScrollViewBehavior.from(target); | |
if (behavior.getState() != OverscrollingScrollViewBehavior.STATE_DISABLED) { | |
return; | |
} | |
} | |
super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed); | |
} | |
@Override | |
public void onNestedScroll(CoordinatorLayout coordinatorLayout, AppBarLayout child, View target, int dxConsumed, | |
int dyConsumed, int dxUnconsumed, int dyUnconsumed) { | |
if (OverscrollingScrollViewBehavior.isAssociatedWith(target)) { | |
final OverscrollingScrollViewBehavior behavior = OverscrollingScrollViewBehavior.from(target); | |
if (behavior.getState() != OverscrollingScrollViewBehavior.STATE_DISABLED) { | |
return; | |
} | |
} | |
super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed); | |
} | |
@Override | |
public void onStopNestedScroll(CoordinatorLayout coordinatorLayout, AppBarLayout abl, View target) { | |
if (OverscrollingScrollViewBehavior.isAssociatedWith(target)) { | |
final OverscrollingScrollViewBehavior behavior = OverscrollingScrollViewBehavior.from(target); | |
if (behavior.getState() != OverscrollingScrollViewBehavior.STATE_DISABLED) { | |
return; | |
} | |
} | |
super.onStopNestedScroll(coordinatorLayout, abl, target); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import android.content.Context; | |
import android.os.Parcel; | |
import android.os.Parcelable; | |
import android.support.annotation.IntDef; | |
import android.support.annotation.NonNull; | |
import android.support.design.widget.AppBarLayout; | |
import android.support.design.widget.CoordinatorLayout; | |
import android.support.v4.os.ParcelableCompat; | |
import android.support.v4.os.ParcelableCompatCreatorCallbacks; | |
import android.support.v4.view.AbsSavedState; | |
import android.support.v4.view.MotionEventCompat; | |
import android.support.v4.view.NestedScrollingChild; | |
import android.support.v4.view.VelocityTrackerCompat; | |
import android.support.v4.view.ViewCompat; | |
import android.support.v4.widget.ViewDragHelper; | |
import android.util.AttributeSet; | |
import android.view.MotionEvent; | |
import android.view.VelocityTracker; | |
import android.view.View; | |
import android.view.ViewConfiguration; | |
import android.view.ViewGroup; | |
import android.view.ViewParent; | |
import com.hoteltonight.android.prod.util.Utilities; | |
import java.lang.annotation.Retention; | |
import java.lang.annotation.RetentionPolicy; | |
import java.lang.ref.WeakReference; | |
@SuppressWarnings("unused") | |
public class OverscrollingScrollViewBehavior extends AppBarLayout.ScrollingViewBehavior { | |
/** | |
* Callback for monitoring events about bottom sheets. | |
*/ | |
public abstract static class OverscrollingCallback { | |
/** | |
* Called when the bottom sheet changes its state. | |
* | |
* @param bottomSheet The bottom sheet view. | |
* @param newState The new state. This will be one of {@link #STATE_DRAGGING}, | |
* {@link #STATE_SETTLING}, {@link #STATE_EXPANDED}, | |
* {@link #STATE_DISABLED}, or {@link #STATE_HIDDEN}. | |
*/ | |
public abstract void onStateChanged(@NonNull View bottomSheet, @State int newState); | |
/** | |
* Called when the bottom sheet is being dragged. | |
* | |
* @param bottomSheet The bottom sheet view. | |
* @param slideOffset The new offset of this bottom sheet within [-1,1] range. Offset | |
* increases as this bottom sheet is moving upward. From 0 to 1 the sheet | |
* is between collapsed and expanded states and from -1 to 0 it is | |
* between hidden and collapsed states. | |
*/ | |
public abstract void onSlide(@NonNull View bottomSheet, float slideOffset); | |
} | |
/** | |
* The bottom sheet is dragging. | |
*/ | |
public static final int STATE_DRAGGING = 1; | |
/** | |
* The bottom sheet is settling. | |
*/ | |
public static final int STATE_SETTLING = 2; | |
/** | |
* The bottom sheet is expanded. | |
*/ | |
public static final int STATE_EXPANDED = 3; | |
/** | |
* The bottom sheet is collapsed. | |
*/ | |
public static final int STATE_DISABLED = 4; | |
/** | |
* The bottom sheet is hidden. | |
*/ | |
public static final int STATE_HIDDEN = 5; | |
@IntDef({STATE_EXPANDED, STATE_DISABLED, STATE_DRAGGING, STATE_SETTLING, STATE_HIDDEN}) | |
@Retention(RetentionPolicy.SOURCE) | |
public @interface State { | |
} | |
private static final float HIDE_THRESHOLD = 0.5f; | |
private static final float HIDE_FRICTION = 0.1f; | |
private float mMaximumVelocity; | |
private int mPeekHeight; | |
private boolean mPeekHeightAuto; | |
private int mPeekHeightMin; | |
int mMinOffset; | |
int mMaxOffset; | |
boolean mHideable = true; | |
@State | |
int mState = STATE_DISABLED; | |
ViewDragHelper mViewDragHelper; | |
private boolean mIgnoreEvents; | |
private int mLastNestedScrollDy; | |
private boolean mNestedScrolled; | |
int mParentHeight; | |
WeakReference<View> mViewRef; | |
WeakReference<View> mNestedScrollingChildRef; | |
private OverscrollingCallback mCallback; | |
private VelocityTracker mVelocityTracker; | |
int mActivePointerId; | |
private int mInitialY; | |
boolean mTouchingScrollingChild; | |
/** | |
* Default constructor for instantiating BottomSheetBehaviors. | |
*/ | |
public OverscrollingScrollViewBehavior() { | |
} | |
/** | |
* Default constructor for inflating BottomSheetBehaviors from layout. | |
* | |
* @param context The {@link Context}. | |
* @param attrs The {@link AttributeSet}. | |
*/ | |
public OverscrollingScrollViewBehavior(Context context, AttributeSet attrs) { | |
super(context, attrs); | |
ViewConfiguration configuration = ViewConfiguration.get(context); | |
mMaximumVelocity = configuration.getScaledMaximumFlingVelocity(); | |
} | |
@Override | |
public Parcelable onSaveInstanceState(CoordinatorLayout parent, View child) { | |
return new SavedState(super.onSaveInstanceState(parent, child), mState); | |
} | |
@Override | |
public void onRestoreInstanceState(CoordinatorLayout parent, View child, Parcelable state) { | |
SavedState ss = (SavedState) state; | |
super.onRestoreInstanceState(parent, child, ss.getSuperState()); | |
// Intermediate states are restored as collapsed state | |
if (ss.state == STATE_DRAGGING || ss.state == STATE_SETTLING) { | |
mState = STATE_DISABLED; | |
} else { | |
mState = ss.state; | |
} | |
} | |
@Override | |
public boolean onLayoutChild(CoordinatorLayout parent, View child, int layoutDirection) { | |
super.onLayoutChild(parent, child, layoutDirection); | |
if (ViewCompat.getFitsSystemWindows(parent) && !ViewCompat.getFitsSystemWindows(child)) { | |
ViewCompat.setFitsSystemWindows(child, true); | |
} | |
int savedTop = child.getTop(); | |
// First let the parent lay it out | |
parent.onLayoutChild(child, layoutDirection); | |
// Offset the bottom sheet | |
mParentHeight = parent.getHeight(); | |
int peekHeight = mPeekHeight; | |
mMinOffset = Math.max(savedTop, mParentHeight - child.getHeight()); | |
mMaxOffset = Math.max(mParentHeight - peekHeight, mMinOffset); | |
if (mState == STATE_EXPANDED) { | |
ViewCompat.offsetTopAndBottom(child, mMinOffset); | |
} else if (mHideable && mState == STATE_HIDDEN) { | |
ViewCompat.offsetTopAndBottom(child, mParentHeight); | |
} else if (mState == STATE_DRAGGING || mState == STATE_SETTLING) { | |
ViewCompat.offsetTopAndBottom(child, savedTop - child.getTop()); | |
} | |
if (mViewDragHelper == null) { | |
mViewDragHelper = ViewDragHelper.create(parent, mDragCallback); | |
} | |
mViewRef = new WeakReference<>(child); | |
mNestedScrollingChildRef = new WeakReference<>(findScrollingChild(child)); | |
return true; | |
} | |
@Override | |
public boolean onInterceptTouchEvent(CoordinatorLayout parent, View child, MotionEvent event) { | |
if (!child.isShown()) { | |
mIgnoreEvents = true; | |
return false; | |
} | |
int action = MotionEventCompat.getActionMasked(event); | |
// Record the velocity | |
if (action == MotionEvent.ACTION_DOWN) { | |
reset(); | |
} | |
if (mVelocityTracker == null) { | |
mVelocityTracker = VelocityTracker.obtain(); | |
} | |
mVelocityTracker.addMovement(event); | |
switch (action) { | |
case MotionEvent.ACTION_UP: | |
case MotionEvent.ACTION_CANCEL: | |
mTouchingScrollingChild = false; | |
mActivePointerId = MotionEvent.INVALID_POINTER_ID; | |
// Reset the ignore flag | |
if (mIgnoreEvents) { | |
mIgnoreEvents = false; | |
return false; | |
} | |
break; | |
case MotionEvent.ACTION_DOWN: | |
int initialX = (int) event.getX(); | |
mInitialY = (int) event.getY(); | |
View scroll = mNestedScrollingChildRef.get(); | |
if (scroll != null && parent.isPointInChildBounds(scroll, initialX, mInitialY)) { | |
mActivePointerId = event.getPointerId(event.getActionIndex()); | |
mTouchingScrollingChild = true; | |
} | |
mIgnoreEvents = mActivePointerId == MotionEvent.INVALID_POINTER_ID && | |
!parent.isPointInChildBounds(child, initialX, mInitialY); | |
break; | |
} | |
if (!mIgnoreEvents && mViewDragHelper.shouldInterceptTouchEvent(event)) { | |
return true; | |
} | |
// We have to handle cases that the ViewDragHelper does not capture the bottom sheet because | |
// it is not the top most view of its parent. This is not necessary when the touch event is | |
// happening over the scrolling content as nested scrolling logic handles that case. | |
View scroll = mNestedScrollingChildRef.get(); | |
return action == MotionEvent.ACTION_MOVE && scroll != null && | |
!mIgnoreEvents && mState != STATE_DRAGGING && | |
!parent.isPointInChildBounds(scroll, (int) event.getX(), (int) event.getY()) && | |
Math.abs(mInitialY - event.getY()) > mViewDragHelper.getTouchSlop(); | |
} | |
@Override | |
public boolean onTouchEvent(CoordinatorLayout parent, View child, MotionEvent event) { | |
if (!child.isShown()) { | |
return false; | |
} | |
int action = MotionEventCompat.getActionMasked(event); | |
if (mState == STATE_DRAGGING && action == MotionEvent.ACTION_DOWN) { | |
return true; | |
} | |
mViewDragHelper.processTouchEvent(event); | |
// Record the velocity | |
if (action == MotionEvent.ACTION_DOWN) { | |
reset(); | |
} | |
if (mVelocityTracker == null) { | |
mVelocityTracker = VelocityTracker.obtain(); | |
} | |
mVelocityTracker.addMovement(event); | |
// The ViewDragHelper tries to capture only the top-most View. We have to explicitly tell it | |
// to capture the bottom sheet in case it is not captured and the touch slop is passed. | |
if (action == MotionEvent.ACTION_MOVE && !mIgnoreEvents) { | |
if (Math.abs(mInitialY - event.getY()) > mViewDragHelper.getTouchSlop()) { | |
mViewDragHelper.captureChildView(child, event.getPointerId(event.getActionIndex())); | |
} | |
} | |
return !mIgnoreEvents; | |
} | |
@Override | |
public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, View child, | |
View directTargetChild, View target, int nestedScrollAxes) { | |
mLastNestedScrollDy = 0; | |
mNestedScrolled = false; | |
return (nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0; | |
} | |
@Override | |
public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, View child, View target, int dx, | |
int dy, int[] consumed) { | |
View scrollingChild = mNestedScrollingChildRef.get(); | |
if (target != scrollingChild || mState == STATE_DISABLED) { | |
return; | |
} | |
int currentTop = child.getTop(); | |
int newTop = currentTop - dy; | |
if (dy > 0) { // Upward | |
if (newTop == mMinOffset) { | |
consumed[1] = dy; | |
ViewCompat.offsetTopAndBottom(child, -dy); | |
setStateInternal(STATE_EXPANDED); | |
} else if (newTop < mMinOffset) { | |
consumed[1] = currentTop - mMinOffset; | |
ViewCompat.offsetTopAndBottom(child, -consumed[1]); | |
setStateInternal(STATE_DISABLED); | |
} else { | |
consumed[1] = dy; | |
ViewCompat.offsetTopAndBottom(child, -dy); | |
setStateInternal(STATE_DRAGGING); | |
} | |
} else if (dy < 0) { // Downward | |
if (!ViewCompat.canScrollVertically(target, -1)) { | |
if (newTop <= mMaxOffset || mHideable) { | |
consumed[1] = dy; | |
ViewCompat.offsetTopAndBottom(child, -dy); | |
setStateInternal(STATE_DRAGGING); | |
} | |
} | |
} | |
dispatchOnSlide(child.getTop()); | |
mLastNestedScrollDy = dy; | |
mNestedScrolled = mState != STATE_DISABLED; | |
} | |
@Override | |
public void onStopNestedScroll(CoordinatorLayout coordinatorLayout, View child, View target) { | |
if (mState != STATE_DISABLED && child.getTop() == mMinOffset) { | |
setStateInternal(STATE_EXPANDED); | |
return; | |
} | |
if (target != mNestedScrollingChildRef.get() || !mNestedScrolled) { | |
return; | |
} | |
int top; | |
int targetState; | |
if (mLastNestedScrollDy > 0) { | |
top = mMinOffset; | |
targetState = STATE_EXPANDED; | |
} else if (mHideable && shouldHide(child, getYVelocity())) { | |
top = mParentHeight; | |
targetState = STATE_HIDDEN; | |
} else { | |
top = mMinOffset; | |
targetState = STATE_EXPANDED; | |
} | |
if (mViewDragHelper.smoothSlideViewTo(child, child.getLeft(), top)) { | |
setStateInternal(STATE_SETTLING); | |
ViewCompat.postOnAnimation(child, new SettleRunnable(child, targetState)); | |
} else { | |
setStateInternal(targetState); | |
} | |
mNestedScrolled = false; | |
} | |
@Override | |
public boolean onNestedPreFling(CoordinatorLayout coordinatorLayout, View child, View target, | |
float velocityX, float velocityY) { | |
return target == mNestedScrollingChildRef.get() && | |
((mState != STATE_EXPANDED && mState != STATE_DISABLED) || | |
super.onNestedPreFling(coordinatorLayout, child, target, | |
velocityX, velocityY)); | |
} | |
/** | |
* Sets a callback to be notified of bottom sheet events. | |
* | |
* @param callback The callback to notify when bottom sheet events occur. | |
*/ | |
public void setOverscrollingCallback(OverscrollingCallback callback) { | |
mCallback = callback; | |
} | |
/** | |
* Sets the state of the overscrolling. Will transition to that state with | |
* animation. | |
* | |
* @param state One of {@link #STATE_DISABLED}, {@link #STATE_EXPANDED}, or | |
* {@link #STATE_HIDDEN}. | |
*/ | |
public final void setState(final @State int state) { | |
if (state == mState) { | |
return; | |
} | |
if (state == STATE_DISABLED) { | |
mState = STATE_DISABLED; | |
return; | |
} | |
if (mViewRef == null) { | |
// The view is not laid out yet; modify mState and let onLayoutChild handle it later | |
if (state == STATE_EXPANDED || mHideable && state == STATE_HIDDEN) { | |
mState = state; | |
} | |
return; | |
} | |
final View child = mViewRef.get(); | |
if (child == null) { | |
return; | |
} | |
// Start the animation; wait until a pending layout if there is one. | |
ViewParent parent = child.getParent(); | |
if (parent != null && parent.isLayoutRequested() && ViewCompat.isAttachedToWindow(child)) { | |
child.post(new Runnable() { | |
@Override | |
public void run() { | |
startSettlingAnimation(child, state); | |
} | |
}); | |
} else { | |
startSettlingAnimation(child, state); | |
} | |
} | |
/** | |
* Gets the current state of the overscrolling. | |
* | |
* @return One of {@link #STATE_EXPANDED}, {@link #STATE_DISABLED}, {@link #STATE_DRAGGING}, | |
* and {@link #STATE_SETTLING}. | |
*/ | |
@State | |
public final int getState() { | |
return mState; | |
} | |
void setStateInternal(@State int state) { | |
if (mState == state) { | |
return; | |
} | |
mState = state; | |
View view = mViewRef.get(); | |
if (view != null && mCallback != null) { | |
mCallback.onStateChanged(view, state); | |
} | |
} | |
private void reset() { | |
mActivePointerId = ViewDragHelper.INVALID_POINTER; | |
if (mVelocityTracker != null) { | |
mVelocityTracker.recycle(); | |
mVelocityTracker = null; | |
} | |
} | |
boolean shouldHide(View child, float yvel) { | |
final float newTop = child.getTop() + yvel * HIDE_FRICTION; | |
return Math.abs(newTop) / (float) mParentHeight > HIDE_THRESHOLD; | |
} | |
private static View findScrollingChild(View view) { | |
if (view instanceof NestedScrollingChild) { | |
return view; | |
} | |
if (view instanceof ViewGroup) { | |
ViewGroup group = (ViewGroup) view; | |
for (int i = 0, count = group.getChildCount(); i < count; i++) { | |
View scrollingChild = findScrollingChild(group.getChildAt(i)); | |
if (scrollingChild != null) { | |
return scrollingChild; | |
} | |
} | |
} | |
return null; | |
} | |
private float getYVelocity() { | |
mVelocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); | |
return VelocityTrackerCompat.getYVelocity(mVelocityTracker, mActivePointerId); | |
} | |
void startSettlingAnimation(View child, int state) { | |
int top; | |
if (state == STATE_EXPANDED) { | |
top = mMinOffset; | |
} else if (mHideable && state == STATE_HIDDEN) { | |
top = mParentHeight; | |
} else { | |
throw new IllegalArgumentException("Illegal state argument: " + state); | |
} | |
setStateInternal(STATE_SETTLING); | |
if (mViewDragHelper.smoothSlideViewTo(child, child.getLeft(), top)) { | |
ViewCompat.postOnAnimation(child, new SettleRunnable(child, state)); | |
} | |
} | |
private final ViewDragHelper.Callback mDragCallback = new ViewDragHelper.Callback() { | |
@Override | |
public boolean tryCaptureView(View child, int pointerId) { | |
if (mState == STATE_DRAGGING) { | |
return false; | |
} | |
if (mTouchingScrollingChild) { | |
return false; | |
} | |
if (mState == STATE_EXPANDED && mActivePointerId == pointerId) { | |
View scroll = mNestedScrollingChildRef.get(); | |
if (scroll != null && ViewCompat.canScrollVertically(scroll, -1)) { | |
// Let the content scroll up | |
return false; | |
} | |
} | |
return mViewRef != null && mViewRef.get() == child; | |
} | |
@Override | |
public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) { | |
dispatchOnSlide(top); | |
} | |
@Override | |
public void onViewDragStateChanged(int state) { | |
if (state == ViewDragHelper.STATE_DRAGGING) { | |
setStateInternal(STATE_DRAGGING); | |
} | |
} | |
@Override | |
public void onViewReleased(View releasedChild, float xvel, float yvel) { | |
int top; | |
@State int targetState; | |
if (yvel < 0) { // Moving up | |
top = mMinOffset; | |
targetState = STATE_EXPANDED; | |
} else if (shouldHide(releasedChild, yvel)) { | |
top = mParentHeight; | |
targetState = STATE_HIDDEN; | |
} else if (yvel == 0.f) { | |
int currentTop = releasedChild.getTop(); | |
top = mMinOffset; | |
targetState = STATE_EXPANDED; | |
} else { | |
top = mMinOffset; | |
targetState = STATE_EXPANDED; | |
} | |
if (mViewDragHelper.settleCapturedViewAt(releasedChild.getLeft(), top)) { | |
setStateInternal(STATE_SETTLING); | |
ViewCompat.postOnAnimation(releasedChild, | |
new SettleRunnable(releasedChild, targetState)); | |
} else { | |
setStateInternal(targetState); | |
} | |
} | |
@Override | |
public int clampViewPositionVertical(View child, int top, int dy) { | |
return Utilities.constrain(top, mMinOffset, mParentHeight); | |
} | |
@Override | |
public int clampViewPositionHorizontal(View child, int left, int dx) { | |
return child.getLeft(); | |
} | |
@Override | |
public int getViewVerticalDragRange(View child) { | |
if (mHideable) { | |
return mParentHeight - mMinOffset; | |
} else { | |
return mMaxOffset - mMinOffset; | |
} | |
} | |
}; | |
void dispatchOnSlide(int top) { | |
View view = mViewRef.get(); | |
if (view != null && mCallback != null) { | |
if (top > mMaxOffset) { | |
mCallback.onSlide(view, (float) (mMaxOffset - top) / | |
(mParentHeight - mMaxOffset)); | |
} else { | |
mCallback.onSlide(view, | |
(float) (mMaxOffset - top) / ((mMaxOffset - mMinOffset))); | |
} | |
} | |
} | |
private class SettleRunnable implements Runnable { | |
private final View mView; | |
@State | |
private final int mTargetState; | |
SettleRunnable(View view, @State int targetState) { | |
mView = view; | |
mTargetState = targetState; | |
} | |
@Override | |
public void run() { | |
if (mViewDragHelper != null && mViewDragHelper.continueSettling(true)) { | |
ViewCompat.postOnAnimation(mView, this); | |
} else { | |
setStateInternal(mTargetState); | |
} | |
} | |
} | |
protected static class SavedState extends AbsSavedState { | |
@State | |
final int state; | |
public SavedState(Parcel source) { | |
this(source, null); | |
} | |
public SavedState(Parcel source, ClassLoader loader) { | |
super(source, loader); | |
//noinspection ResourceType | |
state = source.readInt(); | |
} | |
public SavedState(Parcelable superState, @State int state) { | |
super(superState); | |
this.state = state; | |
} | |
@Override | |
public void writeToParcel(Parcel out, int flags) { | |
super.writeToParcel(out, flags); | |
out.writeInt(state); | |
} | |
public static final Creator<SavedState> CREATOR = ParcelableCompat.newCreator( | |
new ParcelableCompatCreatorCallbacks<SavedState>() { | |
@Override | |
public SavedState createFromParcel(Parcel in, ClassLoader loader) { | |
return new SavedState(in, loader); | |
} | |
@Override | |
public SavedState[] newArray(int size) { | |
return new SavedState[size]; | |
} | |
}); | |
} | |
/** | |
* A utility function to get the {@link OverscrollingScrollViewBehavior} associated with the {@code view}. | |
* | |
* @param view The {@link View} with {@link OverscrollingScrollViewBehavior}. | |
* @return The {@link OverscrollingScrollViewBehavior} associated with the {@code view}. | |
*/ | |
@SuppressWarnings("unchecked") | |
public static OverscrollingScrollViewBehavior from(View view) { | |
ViewGroup.LayoutParams params = view.getLayoutParams(); | |
if (!(params instanceof CoordinatorLayout.LayoutParams)) { | |
throw new IllegalArgumentException("The view is not a child of CoordinatorLayout"); | |
} | |
CoordinatorLayout.Behavior behavior = ((CoordinatorLayout.LayoutParams) params).getBehavior(); | |
if (!(behavior instanceof OverscrollingScrollViewBehavior)) { | |
throw new IllegalArgumentException( | |
"The view is not associated with BottomSheetBehavior"); | |
} | |
return (OverscrollingScrollViewBehavior) behavior; | |
} | |
/** | |
* A utility function to check for {@link OverscrollingScrollViewBehavior} associated with the {@code view}. | |
* | |
* @param view The {@link View} with {@link OverscrollingScrollViewBehavior}. | |
* @return {@code true} if there's an {@link OverscrollingScrollViewBehavior} associated with the {@code view}. | |
*/ | |
@SuppressWarnings("unchecked") | |
public static boolean isAssociatedWith(View view) { | |
ViewGroup.LayoutParams params = view.getLayoutParams(); | |
if (!(params instanceof CoordinatorLayout.LayoutParams)) { | |
return false; | |
} | |
CoordinatorLayout.Behavior behavior = ((CoordinatorLayout.LayoutParams) params).getBehavior(); | |
return behavior instanceof OverscrollingScrollViewBehavior; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public void onCreate(Bundle icicle) { | |
super.onCreate(icicle); | |
/** | |
* ... | |
*/ | |
appBar = (AppBarLayout) view.findViewById(R.id.appBar); | |
final ViewGroup.MarginLayoutParams searchControlMargins = | |
(ViewGroup.MarginLayoutParams) searchControlsView.getLayoutParams(); | |
expandedAppBarMargin = searchControlMargins.leftMargin; | |
// Setup the overscrolls | |
final View appBarChild = appBar.getChildAt(0); | |
final int minHeight = ViewCompat.getMinimumHeight(appBarChild); | |
final OverscrollingScrollViewBehavior behavior = OverscrollingScrollViewBehavior.from(hotelList); | |
appBar.addOnOffsetChangedListener((appBarLayout, verticalOffset) -> { | |
final int scrollRange = appBar.getTotalScrollRange() - minHeight; | |
if (mCurrentState != STATE_EXPANDED && verticalOffset == 0) { | |
// Re-enable Overscrolling when settled back into default state | |
behavior.setState(OverscrollingScrollViewBehavior.STATE_EXPANDED); | |
mCurrentState = STATE_EXPANDED; | |
} else if (mCurrentState != STATE_COLLAPSED && Math.abs(verticalOffset) >= scrollRange) { | |
mCurrentState = STATE_COLLAPSED; | |
} else { | |
mCurrentState = STATE_TRANSITIONING; | |
} | |
}); | |
behavior.setState(OverscrollingScrollViewBehavior.STATE_EXPANDED); | |
} |
Yes, the Utilities.constrain method is missing.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
what about Utilities.constrain method?