|
|
@@ -0,0 +1,581 @@
|
|
|
+package com.yc.baselibrary.behavior;
|
|
|
+
|
|
|
+import android.content.Context;
|
|
|
+import android.content.res.TypedArray;
|
|
|
+import android.util.AttributeSet;
|
|
|
+import android.view.MotionEvent;
|
|
|
+import android.view.View;
|
|
|
+import android.view.ViewGroup;
|
|
|
+import android.view.ViewParent;
|
|
|
+import android.widget.Scroller;
|
|
|
+
|
|
|
+
|
|
|
+import com.yc.baselibrary.R;
|
|
|
+import com.yc.baselibrary.utils.FlingHelper;
|
|
|
+
|
|
|
+import java.lang.annotation.Retention;
|
|
|
+import java.lang.annotation.RetentionPolicy;
|
|
|
+import java.lang.ref.WeakReference;
|
|
|
+
|
|
|
+import androidx.annotation.IntDef;
|
|
|
+import androidx.annotation.NonNull;
|
|
|
+import androidx.annotation.RestrictTo;
|
|
|
+import androidx.coordinatorlayout.widget.CoordinatorLayout;
|
|
|
+import androidx.core.math.MathUtils;
|
|
|
+import androidx.core.view.ViewCompat;
|
|
|
+import androidx.customview.widget.ViewDragHelper;
|
|
|
+
|
|
|
+import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
|
|
|
+
|
|
|
+public class BottomLinkedBehavior extends CoordinatorLayout.Behavior {
|
|
|
+
|
|
|
+ public interface BottomLinkedCallback {
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 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_EXPANDED},{@link #STATE_COLLAPSED}.
|
|
|
+ */
|
|
|
+ void onStateChanged(@NonNull View bottomSheet, @RecyclerViewBehavior.State int newState);
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 用来判断其子View是否滑到顶部
|
|
|
+ *
|
|
|
+ * @return 是否滑动顶部
|
|
|
+ */
|
|
|
+ boolean isChildScrollTop();
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 当前view的位置变化
|
|
|
+ *
|
|
|
+ * @param dy 竖直距离
|
|
|
+ */
|
|
|
+ void onPositionChanged(int dy);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * The bottom sheet is first.
|
|
|
+ */
|
|
|
+ public static final int STATE_FIRST = 0;
|
|
|
+ /**
|
|
|
+ * The bottom sheet is dragging.
|
|
|
+ */
|
|
|
+ public static final int STATE_DRAGGING = 1;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * The bottom sheet is expanded.
|
|
|
+ */
|
|
|
+ public static final int STATE_EXPANDED = 2;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * The bottom sheet is collapsed.
|
|
|
+ */
|
|
|
+ public static final int STATE_COLLAPSED = 3;
|
|
|
+ /**
|
|
|
+ * The bottom sheet is settling.
|
|
|
+ */
|
|
|
+ public static final int STATE_SETTLING = 4;
|
|
|
+
|
|
|
+
|
|
|
+ @RestrictTo(LIBRARY_GROUP)
|
|
|
+ @IntDef({STATE_EXPANDED, STATE_COLLAPSED, STATE_DRAGGING, STATE_SETTLING, STATE_FIRST})
|
|
|
+ @Retention(RetentionPolicy.SOURCE)
|
|
|
+ public @interface State {
|
|
|
+ }
|
|
|
+
|
|
|
+ private static final int THRESHOLD = 200;
|
|
|
+ private static final float FRICTION = 0.1f;
|
|
|
+
|
|
|
+ private int mPeekHeight;
|
|
|
+
|
|
|
+ private int mMinOffset;
|
|
|
+
|
|
|
+ private int mMaxOffset;
|
|
|
+
|
|
|
+ private int mFirstOffset;
|
|
|
+
|
|
|
+ @State
|
|
|
+ private int mState = STATE_COLLAPSED;
|
|
|
+
|
|
|
+ private ViewDragHelper mViewDragHelper;
|
|
|
+
|
|
|
+ private boolean mIgnoreEvents;
|
|
|
+
|
|
|
+ private int mParentHeight;
|
|
|
+
|
|
|
+ private WeakReference<View> mViewRef;
|
|
|
+
|
|
|
+ private int mActivePointerId;
|
|
|
+
|
|
|
+ private int mInitialY;
|
|
|
+
|
|
|
+ private boolean mTouchingScrollingChild;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 是否进行联动
|
|
|
+ */
|
|
|
+ private boolean isLinked;
|
|
|
+
|
|
|
+ private BottomLinkedCallback mCallback;
|
|
|
+
|
|
|
+ private Scroller mScroller;
|
|
|
+ private FlingRunnable mFlingRunnable;
|
|
|
+ private FlingHelper mFlingHelper;
|
|
|
+
|
|
|
+ public BottomLinkedBehavior() {
|
|
|
+ }
|
|
|
+
|
|
|
+ public BottomLinkedBehavior(Context context, AttributeSet attrs) {
|
|
|
+ super(context, attrs);
|
|
|
+ TypedArray a = context.obtainStyledAttributes(attrs,
|
|
|
+ R.styleable.BottomSheetBehavior_Layout);
|
|
|
+ setPeekHeight(a.getDimensionPixelSize(
|
|
|
+ R.styleable.BottomSheetBehavior_Layout_behavior_peekHeight, 0));
|
|
|
+ a.recycle();
|
|
|
+ mScroller = new Scroller(context);
|
|
|
+ mFlingHelper = new FlingHelper(context);
|
|
|
+ }
|
|
|
+
|
|
|
+ public Scroller getScroller() {
|
|
|
+ return mScroller;
|
|
|
+ }
|
|
|
+
|
|
|
+ public void fling(float velocityY) {
|
|
|
+ if (mViewRef.get() == null) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (isLinked) {
|
|
|
+ if (mFlingRunnable != null) {
|
|
|
+ mViewRef.get().removeCallbacks(mFlingRunnable);
|
|
|
+ mFlingRunnable = null;
|
|
|
+ }
|
|
|
+ mScroller.fling(
|
|
|
+ 0, mViewRef.get().getTop(),
|
|
|
+ 0, Math.round(velocityY),
|
|
|
+ 0, 0,
|
|
|
+ mMinOffset, mMaxOffset);
|
|
|
+
|
|
|
+ if (mScroller.computeScrollOffset()) {
|
|
|
+ mFlingRunnable = new FlingRunnable(mViewRef.get(), true);
|
|
|
+ ViewCompat.postOnAnimation(mViewRef.get(), mFlingRunnable);
|
|
|
+ } else {
|
|
|
+ onFlingFinished(mViewRef.get());
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ double distance = mFlingHelper.getSplineFlingDistance(Math.round(velocityY));
|
|
|
+ int newTop = (int) (mViewRef.get().getTop() + Math.round(distance));
|
|
|
+ if (newTop <= mMinOffset + THRESHOLD) {
|
|
|
+ startSettlingAnimation(mViewRef.get(), STATE_EXPANDED);
|
|
|
+ } else {
|
|
|
+ startSettlingAnimation(mViewRef.get(), STATE_COLLAPSED);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public boolean isLinked() {
|
|
|
+ return isLinked;
|
|
|
+ }
|
|
|
+
|
|
|
+ public void setLinked(boolean linked) {
|
|
|
+ isLinked = linked;
|
|
|
+ }
|
|
|
+
|
|
|
+ public int getMaxOffset() {
|
|
|
+ return mMaxOffset;
|
|
|
+ }
|
|
|
+
|
|
|
+ public void setFirstOffset(int mFirstOffset) {
|
|
|
+ this.mFirstOffset = mFirstOffset;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public boolean onMeasureChild(CoordinatorLayout parent, View child, int parentWidthMeasureSpec,
|
|
|
+ int widthUsed, int parentHeightMeasureSpec, int heightUsed) {
|
|
|
+ final int heightMode = View.MeasureSpec.getMode(parentHeightMeasureSpec);
|
|
|
+ final int heightSize = View.MeasureSpec.getSize(parentHeightMeasureSpec);
|
|
|
+ parentHeightMeasureSpec = View.MeasureSpec.makeMeasureSpec(
|
|
|
+ heightSize - mMinOffset, heightMode);
|
|
|
+ final CoordinatorLayout.LayoutParams lp = (CoordinatorLayout.LayoutParams) child.getLayoutParams();
|
|
|
+ final int childWidthMeasureSpec = ViewGroup.getChildMeasureSpec(parentWidthMeasureSpec,
|
|
|
+ lp.leftMargin + lp.rightMargin + widthUsed, lp.width);
|
|
|
+ final int childHeightMeasureSpec = ViewGroup.getChildMeasureSpec(parentHeightMeasureSpec,
|
|
|
+ lp.topMargin + lp.bottomMargin + heightUsed, lp.height);
|
|
|
+ child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public boolean onLayoutChild(CoordinatorLayout parent, View child, int layoutDirection) {
|
|
|
+ if (ViewCompat.getFitsSystemWindows(parent) && !ViewCompat.getFitsSystemWindows(child)) {
|
|
|
+ child.setFitsSystemWindows(true);
|
|
|
+ }
|
|
|
+ int savedTop = child.getTop();
|
|
|
+ // First let the parent lay it out
|
|
|
+ parent.onLayoutChild(child, layoutDirection);
|
|
|
+ // Offset the bottom sheet
|
|
|
+ mParentHeight = parent.getHeight();
|
|
|
+ mMaxOffset = Math.max(mParentHeight - mPeekHeight, mMinOffset);
|
|
|
+ if (mState == STATE_EXPANDED) {
|
|
|
+ ViewCompat.offsetTopAndBottom(child, mMinOffset);
|
|
|
+ } else if (mState == STATE_COLLAPSED) {
|
|
|
+ ViewCompat.offsetTopAndBottom(child, mMaxOffset);
|
|
|
+ } 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);
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public boolean onInterceptTouchEvent(CoordinatorLayout parent, View child, MotionEvent event) {
|
|
|
+ if (!child.isShown()) {
|
|
|
+ mIgnoreEvents = true;
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ int action = event.getActionMasked();
|
|
|
+ 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();
|
|
|
+ mScroller.abortAnimation();
|
|
|
+ mInitialY = (int) event.getY();
|
|
|
+ if (parent.isPointInChildBounds(child, initialX, mInitialY)) {
|
|
|
+ mActivePointerId = event.getPointerId(event.getActionIndex());
|
|
|
+ mTouchingScrollingChild = true;
|
|
|
+ } else {
|
|
|
+ mActivePointerId = MotionEvent.INVALID_POINTER_ID;
|
|
|
+ }
|
|
|
+ mIgnoreEvents =
|
|
|
+ mActivePointerId == MotionEvent.INVALID_POINTER_ID
|
|
|
+ && !parent.isPointInChildBounds(child, initialX, mInitialY);
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ if (!mIgnoreEvents && mViewDragHelper.shouldInterceptTouchEvent(event)) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ // child未到parent顶部或者child滑动顶部,进行拦截
|
|
|
+ return action == MotionEvent.ACTION_MOVE
|
|
|
+ && !mIgnoreEvents
|
|
|
+ && mState != STATE_DRAGGING
|
|
|
+ && Math.abs(mInitialY - event.getY()) > mViewDragHelper.getTouchSlop()
|
|
|
+ && (mCallback != null && mCallback.isChildScrollTop())
|
|
|
+ && !(mState == STATE_EXPANDED && mInitialY > event.getY());
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public boolean onTouchEvent(CoordinatorLayout parent, View child, MotionEvent event) {
|
|
|
+ if (!child.isShown()) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ int action = event.getActionMasked();
|
|
|
+ if (mState == STATE_DRAGGING && action == MotionEvent.ACTION_DOWN) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ if (mViewDragHelper != null) {
|
|
|
+ mViewDragHelper.processTouchEvent(event);
|
|
|
+ }
|
|
|
+ // Record the velocity
|
|
|
+ if (action == MotionEvent.ACTION_DOWN) {
|
|
|
+ mActivePointerId = MotionEvent.INVALID_POINTER_ID;
|
|
|
+ }
|
|
|
+ // 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;
|
|
|
+ }
|
|
|
+
|
|
|
+ public final void setState(final @State int state) {
|
|
|
+ if (state == mState) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (mViewRef == null) {
|
|
|
+ // The view is not laid out yet; modify mState and let onLayoutChild handle it later
|
|
|
+ if (state == STATE_COLLAPSED || state == STATE_EXPANDED) {
|
|
|
+ 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);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @State
|
|
|
+ public final int getState() {
|
|
|
+ return mState;
|
|
|
+ }
|
|
|
+
|
|
|
+ private void setStateInternal(@State int state) {
|
|
|
+ if (mState == state) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ mState = state;
|
|
|
+ View bottomSheet = mViewRef.get();
|
|
|
+ if (bottomSheet != null && mCallback != null) {
|
|
|
+ mCallback.onStateChanged(bottomSheet, state);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private void startSettlingAnimation(View child, int state) {
|
|
|
+ int top;
|
|
|
+ if (state == STATE_COLLAPSED) {
|
|
|
+ top = mMaxOffset;
|
|
|
+ } else if (state == STATE_EXPANDED) {
|
|
|
+ top = mMinOffset;
|
|
|
+ } else if (state == STATE_FIRST) {
|
|
|
+ top = mParentHeight - mFirstOffset;
|
|
|
+ } else {
|
|
|
+ top = child.getTop();
|
|
|
+ }
|
|
|
+ if (mViewDragHelper.smoothSlideViewTo(child, child.getLeft(), top)) {
|
|
|
+ setStateInternal(STATE_SETTLING);
|
|
|
+ ViewCompat.postOnAnimation(child, new SettleRunnable(child, state));
|
|
|
+ } else {
|
|
|
+ setStateInternal(state);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private void onFlingFinished(View child) {
|
|
|
+ @State int targetState;
|
|
|
+ if (child.getTop() <= mMinOffset) {
|
|
|
+ targetState = STATE_EXPANDED;
|
|
|
+ } else if (child.getTop() >= mMaxOffset) {
|
|
|
+ targetState = STATE_COLLAPSED;
|
|
|
+ } else {
|
|
|
+ targetState = STATE_SETTLING;
|
|
|
+ }
|
|
|
+ startSettlingAnimation(child, targetState);
|
|
|
+ }
|
|
|
+
|
|
|
+ public void setBottomSheetCallback(BottomLinkedCallback callback) {
|
|
|
+ mCallback = callback;
|
|
|
+ }
|
|
|
+
|
|
|
+ private final ViewDragHelper.Callback mDragCallback = new ViewDragHelper.Callback() {
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public boolean tryCaptureView(@NonNull View child, int pointerId) {
|
|
|
+ if (mState == STATE_DRAGGING) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ if (mTouchingScrollingChild) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ if (mState == STATE_EXPANDED && mActivePointerId == pointerId) {
|
|
|
+ if (child.canScrollVertically(-1)) {
|
|
|
+ // Let the content scroll up
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return mViewRef != null && mViewRef.get() == child;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void onViewPositionChanged(@NonNull View changedView, int left, int top, int dx, int dy) {
|
|
|
+ if (mCallback != null && isLinked) {
|
|
|
+ mCallback.onPositionChanged(dy);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void onViewDragStateChanged(int state) {
|
|
|
+ if (state == ViewDragHelper.STATE_DRAGGING) {
|
|
|
+ setStateInternal(STATE_DRAGGING);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void onViewReleased(@NonNull View releasedChild, float xvel, float yvel) {
|
|
|
+ int top;
|
|
|
+ @State int targetState;
|
|
|
+ final float newTop = releasedChild.getTop() + yvel * FRICTION;
|
|
|
+ if (isLinked) {
|
|
|
+ mViewDragHelper.flingCapturedView(0, mMinOffset, 0, mMaxOffset);
|
|
|
+ if (mViewDragHelper.continueSettling(true)) {
|
|
|
+ ViewCompat.postOnAnimation(releasedChild, new FlingRunnable(releasedChild, false));
|
|
|
+ } else {
|
|
|
+ onFlingFinished(releasedChild);
|
|
|
+ }
|
|
|
+ return;
|
|
|
+ } else if (yvel < 0) {
|
|
|
+ // 向上滑
|
|
|
+ if (newTop >= mMaxOffset - THRESHOLD) {
|
|
|
+ top = mMaxOffset;
|
|
|
+ targetState = STATE_COLLAPSED;
|
|
|
+ } else {
|
|
|
+ top = mMinOffset;
|
|
|
+ targetState = STATE_EXPANDED;
|
|
|
+ }
|
|
|
+ } else if (yvel == 0.f) {
|
|
|
+ // 匀速滑动
|
|
|
+ int currentTop = releasedChild.getTop();
|
|
|
+ if (Math.abs(currentTop - mMinOffset) < Math.abs(currentTop - mMaxOffset)) {
|
|
|
+ top = mMinOffset;
|
|
|
+ targetState = STATE_EXPANDED;
|
|
|
+ } else {
|
|
|
+ top = mMaxOffset;
|
|
|
+ targetState = STATE_COLLAPSED;
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ // 向下滑
|
|
|
+ if (newTop <= mMinOffset + THRESHOLD) {
|
|
|
+ top = mMinOffset;
|
|
|
+ targetState = STATE_EXPANDED;
|
|
|
+ } else {
|
|
|
+ top = mMaxOffset;
|
|
|
+ targetState = STATE_COLLAPSED;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (mViewDragHelper.settleCapturedViewAt(releasedChild.getLeft(), top)) {
|
|
|
+ setStateInternal(STATE_SETTLING);
|
|
|
+ ViewCompat.postOnAnimation(releasedChild,
|
|
|
+ new SettleRunnable(releasedChild, targetState));
|
|
|
+ } else {
|
|
|
+ setStateInternal(targetState);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public int clampViewPositionVertical(@NonNull View child, int top, int dy) {
|
|
|
+ return MathUtils.clamp(top, mMinOffset, mMaxOffset);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public int clampViewPositionHorizontal(View child, int left, int dx) {
|
|
|
+ return child.getLeft();
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public int getViewVerticalDragRange(@NonNull View child) {
|
|
|
+ return mParentHeight;
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ public final void setPeekHeight(int peekHeight) {
|
|
|
+ mPeekHeight = Math.max(0, peekHeight);
|
|
|
+ mMaxOffset = mParentHeight - mPeekHeight;
|
|
|
+ if (mState == STATE_COLLAPSED && mViewRef != null) {
|
|
|
+ View view = mViewRef.get();
|
|
|
+ if (view != null) {
|
|
|
+ view.requestLayout();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public void setMinOffset(int minOffset) {
|
|
|
+ mMinOffset = minOffset;
|
|
|
+ }
|
|
|
+
|
|
|
+ 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);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private class FlingRunnable implements Runnable {
|
|
|
+
|
|
|
+ private final View mLayout;
|
|
|
+ private final boolean mHandleSelf;
|
|
|
+
|
|
|
+ FlingRunnable(View layout, boolean handleSelf) {
|
|
|
+ mLayout = layout;
|
|
|
+ mHandleSelf = handleSelf;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void run() {
|
|
|
+ if (mLayout != null && mViewDragHelper != null) {
|
|
|
+ if (mHandleSelf && mScroller != null) {
|
|
|
+ if (mScroller.computeScrollOffset()) {
|
|
|
+ final int y = mScroller.getCurrY();
|
|
|
+ final int dy = y - mLayout.getTop();
|
|
|
+ if (dy != 0) {
|
|
|
+ ViewCompat.offsetTopAndBottom(mLayout, dy);
|
|
|
+ }
|
|
|
+ if (dy != 0) {
|
|
|
+ mDragCallback.onViewPositionChanged(mLayout, 0, y, 0, dy);
|
|
|
+ }
|
|
|
+ if (y == mScroller.getFinalY()) {
|
|
|
+ mScroller.abortAnimation();
|
|
|
+ }
|
|
|
+ setStateInternal(STATE_SETTLING);
|
|
|
+ ViewCompat.postOnAnimation(mLayout, this);
|
|
|
+ } else {
|
|
|
+ onFlingFinished(mLayout);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ if (mViewDragHelper.continueSettling(true)) {
|
|
|
+ setStateInternal(STATE_SETTLING);
|
|
|
+ ViewCompat.postOnAnimation(mLayout, this);
|
|
|
+ } else {
|
|
|
+ onFlingFinished(mLayout);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public static BottomLinkedBehavior 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 BottomLinkedBehavior)) {
|
|
|
+ throw new IllegalArgumentException(
|
|
|
+ "The view is not associated with BottomLinkedBehavior");
|
|
|
+ }
|
|
|
+ return (BottomLinkedBehavior) behavior;
|
|
|
+ }
|
|
|
+
|
|
|
+}
|