我的编程空间,编程开发者的网络收藏夹
学习永远不晚

Android实现三段式滑动效果

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

Android实现三段式滑动效果

高德的效果:

实现的效果:

我们实现的效果和高德差距不是很大,也很顺滑。具体实现其实就是集成CoordinatorLayout.Behavior



public class GaoDeBottomSheetBehavior<V extends View> extends CoordinatorLayout.Behavior<V> {
    public static final int STATE_DRAGGING = 1;
    public static final int STATE_SETTLING = 2;
    public static final int STATE_EXPANDED = 3;
    public static final int STATE_COLLAPSED = 4;
    public static final int STATE_HIDDEN = 5;
    public static final int STATE_HALF_EXPANDED = 6;
    public static final int PEEK_HEIGHT_AUTO = -1;
    private static final float HIDE_THRESHOLD = 0.5F;
    private static final float HIDE_FRICTION = 0.1F;
    public static final int MIDDLE_HEIGHT_AUTO = -1;
    private boolean fitToContents = true;
    private float maximumVelocity;
    private int peekHeight;
    private boolean peekHeightAuto;
    private int peekHeightMin;
    private int lastPeekHeight;
    int fitToContentsOffset;
    int halfExpandedOffset;
    int collapsedOffset;
    boolean hideable;
    private boolean skipCollapsed;
    int state = STATE_COLLAPSED;
    ViewDragHelper viewDragHelper;
    private boolean ignoreEvents;
    private int lastNestedScrollDy;
    private boolean nestedScrolled;
    int parentHeight;
    WeakReference<V> viewRef;
    WeakReference<View> nestedScrollingChildRef;
    private GaoDeBottomSheetBehavior.BottomSheetCallback callback;
    private VelocityTracker velocityTracker;
    int activePointerId;
    private int initialY;
    boolean touchingScrollingChild;
    private Map<View, Integer> importantForAccessibilityMap;
    private final Callback dragCallback;


    private int mMiddleHeight;
    private boolean mMiddleHeightAuto;

    public GaoDeBottomSheetBehavior() {
        this.dragCallback = new NamelessClass_1();
    }

    public GaoDeBottomSheetBehavior(Context context, AttributeSet attrs) {
        super(context, attrs);
        this.dragCallback = new NamelessClass_1();
        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.BottomSheetBehavior_Layout);
        TypedValue value = a.peekValue(R.styleable.BottomSheetBehavior_Layout_behavior_peekHeight);
        if (value != null && value.data == -1) {
            this.setPeekHeight(value.data);
        } else {
            this.setPeekHeight(a.getDimensionPixelSize(R.styleable.BottomSheetBehavior_Layout_behavior_peekHeight, -1));
        }

        this.setHideable(a.getBoolean(R.styleable.BottomSheetBehavior_Layout_behavior_hideAble, false));
        this.setFitToContents(a.getBoolean(R.styleable.BottomSheetBehavior_Layout_behavior_fitToContents, true));
        this.setSkipCollapsed(a.getBoolean(R.styleable.BottomSheetBehavior_Layout_behavior_skipCollapse, false));
        setMiddleHeight(a.getDimensionPixelSize(R.styleable.BottomSheetBehavior_Layout_behavior_middleHeight, MIDDLE_HEIGHT_AUTO));

        a.recycle();
        ViewConfiguration configuration = ViewConfiguration.get(context);
        this.maximumVelocity = (float) configuration.getScaledMaximumFlingVelocity();
    }

    class NamelessClass_1 extends Callback {
        NamelessClass_1() {
        }

        @Override
        public boolean tryCaptureView(@NonNull View child, int pointerId) {
            if (GaoDeBottomSheetBehavior.this.state == STATE_DRAGGING) {
                return false;
            } else if (GaoDeBottomSheetBehavior.this.touchingScrollingChild) {
                return false;
            } else {
                if (GaoDeBottomSheetBehavior.this.state == 3 && GaoDeBottomSheetBehavior.this.activePointerId == pointerId) {
                    View scroll = (View) GaoDeBottomSheetBehavior.this.nestedScrollingChildRef.get();
                    if (scroll != null && scroll.canScrollVertically(-1)) {
                        return false;
                    }
                }

                return GaoDeBottomSheetBehavior.this.viewRef != null && GaoDeBottomSheetBehavior.this.viewRef.get() == child;
            }
        }

        @Override
        public void onViewPositionChanged(@NonNull View changedView, int left, int top, int dx, int dy) {
            GaoDeBottomSheetBehavior.this.dispatchOnSlide(top);
        }

        @Override
        public void onViewDragStateChanged(int state) {
            if (state == 1) {
                GaoDeBottomSheetBehavior.this.setStateInternal(STATE_DRAGGING);
            }

        }

        @Override
        public void onViewReleased(@NonNull View releasedChild, float xvel, float yvel) {
            int top;
            byte targetState;
            int currentTop;
            if (yvel < 0.0F) {
                if (GaoDeBottomSheetBehavior.this.fitToContents) {
                    currentTop = releasedChild.getTop();

                    if (currentTop < (collapsedOffset + HIDE_THRESHOLD) && currentTop >= halfExpandedOffset) {
                        top = GaoDeBottomSheetBehavior.this.halfExpandedOffset;
                        targetState = STATE_HALF_EXPANDED;
                    } else {
                        top = GaoDeBottomSheetBehavior.this.fitToContentsOffset;
                        targetState = STATE_EXPANDED;
                    }

                } else {
                    currentTop = releasedChild.getTop();
                    if (currentTop > GaoDeBottomSheetBehavior.this.halfExpandedOffset) {
                        top = GaoDeBottomSheetBehavior.this.halfExpandedOffset;
                        targetState = STATE_HALF_EXPANDED;
                    } else {
                        top = 0;
                        targetState = STATE_EXPANDED;
                    }
                }
            } else if (!GaoDeBottomSheetBehavior.this.hideable || !GaoDeBottomSheetBehavior.this.shouldHide(releasedChild, yvel) || releasedChild.getTop() <= GaoDeBottomSheetBehavior.this.collapsedOffset && Math.abs(xvel) >= Math.abs(yvel)) {
                if (yvel != 0.0F && Math.abs(xvel) <= Math.abs(yvel)) {
                    currentTop = releasedChild.getTop();
                    if (currentTop < halfExpandedOffset) {
                        top = GaoDeBottomSheetBehavior.this.halfExpandedOffset;
                        targetState = STATE_HALF_EXPANDED;
                    } else {
                        top = GaoDeBottomSheetBehavior.this.collapsedOffset;
                        targetState = STATE_COLLAPSED;
                    }

                } else {
                    currentTop = releasedChild.getTop();
                    if (GaoDeBottomSheetBehavior.this.fitToContents) {
                        if (Math.abs(currentTop - GaoDeBottomSheetBehavior.this.fitToContentsOffset) < Math.abs(currentTop - GaoDeBottomSheetBehavior.this.collapsedOffset)) {
                            top = GaoDeBottomSheetBehavior.this.fitToContentsOffset;
                            targetState = STATE_EXPANDED;
                        } else {
                            top = GaoDeBottomSheetBehavior.this.collapsedOffset;
                            targetState = STATE_COLLAPSED;
                        }
                    } else if (currentTop < GaoDeBottomSheetBehavior.this.halfExpandedOffset) {
                        if (currentTop < Math.abs(currentTop - GaoDeBottomSheetBehavior.this.collapsedOffset)) {
                            top = 0;
                            targetState = STATE_EXPANDED;
                        } else {
                            top = GaoDeBottomSheetBehavior.this.halfExpandedOffset;
                            targetState = STATE_HALF_EXPANDED;
                        }
                    } else if (Math.abs(currentTop - GaoDeBottomSheetBehavior.this.halfExpandedOffset) < Math.abs(currentTop - GaoDeBottomSheetBehavior.this.collapsedOffset)) {
                        top = GaoDeBottomSheetBehavior.this.halfExpandedOffset;
                        targetState = STATE_HALF_EXPANDED;
                    } else {
                        top = GaoDeBottomSheetBehavior.this.collapsedOffset;
                        targetState = STATE_COLLAPSED;
                    }
                }
            } else {
                top = GaoDeBottomSheetBehavior.this.parentHeight;
                targetState = STATE_HIDDEN;
            }

            if (GaoDeBottomSheetBehavior.this.viewDragHelper.settleCapturedViewAt(releasedChild.getLeft(), top)) {
                GaoDeBottomSheetBehavior.this.setStateInternal(STATE_SETTLING);
                ViewCompat.postOnAnimation(releasedChild, GaoDeBottomSheetBehavior.this.new SettleRunnable(releasedChild, targetState));
            } else {
                GaoDeBottomSheetBehavior.this.setStateInternal(targetState);
            }

        }

        @Override
        public int clampViewPositionVertical(@NonNull View child, int top, int dy) {
            return MathUtils.clamp(top, GaoDeBottomSheetBehavior.this.getExpandedOffset(), GaoDeBottomSheetBehavior.this.hideable ? GaoDeBottomSheetBehavior.this.parentHeight : GaoDeBottomSheetBehavior.this.collapsedOffset);
        }

        @Override
        public int clampViewPositionHorizontal(@NonNull View child, int left, int dx) {
            return child.getLeft();
        }

        @Override
        public int getViewVerticalDragRange(@NonNull View child) {
            return GaoDeBottomSheetBehavior.this.hideable ? GaoDeBottomSheetBehavior.this.parentHeight : GaoDeBottomSheetBehavior.this.collapsedOffset;
        }
    }

    @Override
    public Parcelable onSaveInstanceState(CoordinatorLayout parent, V child) {
        return new GaoDeBottomSheetBehavior.SavedState(super.onSaveInstanceState(parent, child), this.state);
    }

    @Override
    public void onRestoreInstanceState(CoordinatorLayout parent, V child, Parcelable state) {
        GaoDeBottomSheetBehavior.SavedState ss = (GaoDeBottomSheetBehavior.SavedState) state;
        super.onRestoreInstanceState(parent, child, ss.getSuperState());
        if (ss.state != STATE_DRAGGING && ss.state != STATE_SETTLING) {
            this.state = ss.state;
        } else {
            this.state = STATE_COLLAPSED;
        }

    }

    @Override
    public boolean onLayoutChild(CoordinatorLayout parent, V child, int layoutDirection) {
        if (ViewCompat.getFitsSystemWindows(parent) && !ViewCompat.getFitsSystemWindows(child)) {
            child.setFitsSystemWindows(true);
        }

        int savedTop = child.getTop();
        parent.onLayoutChild(child, layoutDirection);
        this.parentHeight = parent.getHeight();
        if (this.peekHeightAuto) {
            if (this.peekHeightMin == 0) {
                this.peekHeightMin = parent.getResources().getDimensionPixelSize(R.dimen.design_bottom_sheet_peek_height_min);
            }

            this.lastPeekHeight = Math.max(this.peekHeightMin, this.parentHeight - parent.getWidth() * 9 / 16);
        } else {
            this.lastPeekHeight = this.peekHeight;
        }
        if (mMiddleHeightAuto) {
            mMiddleHeight = this.parentHeight;
        }
        this.fitToContentsOffset = Math.max(0, this.parentHeight - child.getHeight());
        this.halfExpandedOffset = this.parentHeight - mMiddleHeight;
        this.calculateCollapsedOffset();
        if (this.state == STATE_EXPANDED) {
            ViewCompat.offsetTopAndBottom(child, this.getExpandedOffset());
        } else if (this.state == STATE_HALF_EXPANDED) {
            ViewCompat.offsetTopAndBottom(child, this.halfExpandedOffset);
        } else if (this.hideable && this.state == STATE_HIDDEN) {
            ViewCompat.offsetTopAndBottom(child, this.parentHeight);
        } else if (this.state == STATE_COLLAPSED) {
            ViewCompat.offsetTopAndBottom(child, this.collapsedOffset);
        } else if (this.state == STATE_DRAGGING || this.state == STATE_SETTLING) {
            ViewCompat.offsetTopAndBottom(child, savedTop - child.getTop());
        }

        if (this.viewDragHelper == null) {
            this.viewDragHelper = ViewDragHelper.create(parent, this.dragCallback);
        }

        this.viewRef = new WeakReference(child);
        this.nestedScrollingChildRef = new WeakReference(this.findScrollingChild(child));
        return true;
    }

    @Override
    public boolean onInterceptTouchEvent(CoordinatorLayout parent, V child, MotionEvent event) {
        if (!child.isShown()) {
            this.ignoreEvents = true;
            return false;
        } else {
            int action = event.getActionMasked();
            if (action == 0) {
                this.reset();
            }

            if (this.velocityTracker == null) {
                this.velocityTracker = VelocityTracker.obtain();
            }

            this.velocityTracker.addMovement(event);
            switch (action) {
                case 0:
                    int initialX = (int) event.getX();
                    this.initialY = (int) event.getY();
                    View scroll = this.nestedScrollingChildRef != null ? (View) this.nestedScrollingChildRef.get() : null;
                    if (scroll != null && parent.isPointInChildBounds(scroll, initialX, this.initialY)) {
                        this.activePointerId = event.getPointerId(event.getActionIndex());
                        this.touchingScrollingChild = true;
                    }

                    this.ignoreEvents = this.activePointerId == -1 && !parent.isPointInChildBounds(child, initialX, this.initialY);
                    break;
                case 1:
                case 3:
                    this.touchingScrollingChild = false;
                    this.activePointerId = -1;
                    if (this.ignoreEvents) {
                        this.ignoreEvents = false;
                        return false;
                    }
                case 2:
            }

            if (!this.ignoreEvents && this.viewDragHelper != null && this.viewDragHelper.shouldInterceptTouchEvent(event)) {
                return true;
            } else {
                View scroll = this.nestedScrollingChildRef != null ? (View) this.nestedScrollingChildRef.get() : null;
                return action == 2 && scroll != null && !this.ignoreEvents && this.state != 1 && !parent.isPointInChildBounds(scroll, (int) event.getX(), (int) event.getY()) && this.viewDragHelper != null && Math.abs((float) this.initialY - event.getY()) > (float) this.viewDragHelper.getTouchSlop();
            }
        }
    }

    @Override
    public boolean onTouchEvent(CoordinatorLayout parent, V child, MotionEvent event) {
        if (!child.isShown()) {
            return false;
        } else {
            int action = event.getActionMasked();
            if (this.state == STATE_DRAGGING && action == 0) {
                return true;
            } else {
                if (this.viewDragHelper != null) {
                    this.viewDragHelper.processTouchEvent(event);
                }

                if (action == 0) {
                    this.reset();
                }

                if (this.velocityTracker == null) {
                    this.velocityTracker = VelocityTracker.obtain();
                }

                this.velocityTracker.addMovement(event);
                if (action == 2 && !this.ignoreEvents && Math.abs((float) this.initialY - event.getY()) > (float) this.viewDragHelper.getTouchSlop()) {
                    this.viewDragHelper.captureChildView(child, event.getPointerId(event.getActionIndex()));
                }

                return !this.ignoreEvents;
            }
        }
    }

    @Override
    public boolean onStartNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull V child, @NonNull View directTargetChild, @NonNull View target, int axes, int type) {
        this.lastNestedScrollDy = 0;
        this.nestedScrolled = false;
        return (axes & 2) != 0;
    }

    @Override
    public void onNestedPreScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull V child, @NonNull View target, int dx, int dy, @NonNull int[] consumed, int type) {
        if (type != 1) {
            View scrollingChild = (View) this.nestedScrollingChildRef.get();
            if (target == scrollingChild) {
                int currentTop = child.getTop();
                int newTop = currentTop - dy;
                if (dy > 0) {
                    if (newTop < this.getExpandedOffset()) {
                        consumed[1] = currentTop - this.getExpandedOffset();
                        ViewCompat.offsetTopAndBottom(child, -consumed[1]);
                        this.setStateInternal(STATE_EXPANDED);
                    } else {
                        consumed[1] = dy;
                        ViewCompat.offsetTopAndBottom(child, -dy);
                        this.setStateInternal(STATE_DRAGGING);
                    }
                } else if (dy < 0 && !target.canScrollVertically(-1)) {
                    if (newTop > this.collapsedOffset && !this.hideable) {
                        consumed[1] = currentTop - this.collapsedOffset;
                        ViewCompat.offsetTopAndBottom(child, -consumed[1]);
                        this.setStateInternal(STATE_COLLAPSED);
                    } else {
                        consumed[1] = dy;
                        ViewCompat.offsetTopAndBottom(child, -dy);
                        this.setStateInternal(STATE_DRAGGING);
                    }
                }

                this.dispatchOnSlide(child.getTop());
                this.lastNestedScrollDy = dy;
                this.nestedScrolled = true;
            }
        }
    }

    @Override
    public void onStopNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull V child, @NonNull View target, int type) {
        if (child.getTop() == this.getExpandedOffset()) {
            this.setStateInternal(STATE_EXPANDED);
        } else if (target == this.nestedScrollingChildRef.get() && this.nestedScrolled) {
            int top;
            byte targetState;
            if (this.lastNestedScrollDy > 0) {
                int currentTop = child.getTop();
                if (currentTop <= collapsedOffset - HIDE_THRESHOLD && currentTop >= halfExpandedOffset) {
                    top = this.halfExpandedOffset;
                    targetState = STATE_HALF_EXPANDED;

                } else {
                    top = this.getExpandedOffset();
                    targetState = STATE_EXPANDED;
                }

            } else if (this.hideable && this.shouldHide(child, this.getYVelocity())) {
                top = this.parentHeight;
                targetState = STATE_HIDDEN;
            } else if (this.lastNestedScrollDy == 0) {
                int currentTop = child.getTop();
                if (this.fitToContents) {
                    if (Math.abs(currentTop - this.fitToContentsOffset) < Math.abs(currentTop - this.collapsedOffset)) {
                        top = this.fitToContentsOffset;
                        targetState = STATE_EXPANDED;
                    } else {
                        top = this.collapsedOffset;
                        targetState = STATE_COLLAPSED;
                    }
                } else if (currentTop < this.halfExpandedOffset) {
                    if (currentTop < Math.abs(currentTop - this.collapsedOffset)) {
                        top = 0;
                        targetState = STATE_EXPANDED;
                    } else {
                        top = this.halfExpandedOffset;
                        targetState = STATE_HALF_EXPANDED;
                    }
                } else if (Math.abs(currentTop - this.halfExpandedOffset) < Math.abs(currentTop - this.collapsedOffset)) {
                    top = this.halfExpandedOffset;
                    targetState = STATE_HALF_EXPANDED;
                } else {
                    top = this.collapsedOffset;
                    targetState = STATE_COLLAPSED;
                }
            } else {
                int currentTop = child.getTop();
                if (currentTop <= halfExpandedOffset + HIDE_THRESHOLD && currentTop > HIDE_THRESHOLD) {
                    top = this.halfExpandedOffset;
                    targetState = STATE_HALF_EXPANDED;
                } else {
                    top = this.collapsedOffset;
                    targetState = STATE_COLLAPSED;
                }
            }

            if (this.viewDragHelper.smoothSlideViewTo(child, child.getLeft(), top)) {
                this.setStateInternal(STATE_SETTLING);
                ViewCompat.postOnAnimation(child, new GaoDeBottomSheetBehavior.SettleRunnable(child, targetState));
            } else {
                this.setStateInternal(targetState);
            }

            this.nestedScrolled = false;
        }
    }

    @Override
    public boolean onNestedPreFling(@NonNull CoordinatorLayout coordinatorLayout, @NonNull V child, @NonNull View target, float velocityX, float velocityY) {
        return target == this.nestedScrollingChildRef.get() && (this.state != STATE_EXPANDED || super.onNestedPreFling(coordinatorLayout, child, target, velocityX, velocityY));
    }

    public boolean isFitToContents() {
        return this.fitToContents;
    }

    public void setFitToContents(boolean fitToContents) {
        if (this.fitToContents != fitToContents) {
            this.fitToContents = fitToContents;
            if (this.viewRef != null) {
                this.calculateCollapsedOffset();
            }

            this.setStateInternal(this.fitToContents && this.state == STATE_HALF_EXPANDED ? STATE_HALF_EXPANDED : this.state);
        }
    }

    public final void setPeekHeight(int peekHeight) {
        boolean layout = false;
        if (peekHeight == -1) {
            if (!this.peekHeightAuto) {
                this.peekHeightAuto = true;
                layout = true;
            }
        } else if (this.peekHeightAuto || this.peekHeight != peekHeight) {
            this.peekHeightAuto = false;
            this.peekHeight = Math.max(0, peekHeight);
            this.collapsedOffset = this.parentHeight - peekHeight;
            layout = true;
        }

        if (layout && this.state == STATE_COLLAPSED && this.viewRef != null) {
            V view = (V) this.viewRef.get();
            if (view != null) {
                view.requestLayout();
            }
        }

    }

    public final void setMiddleHeight(int middleHeight) {
        boolean layout = false;
        if (middleHeight == PEEK_HEIGHT_AUTO) {
            if (!mMiddleHeightAuto) {
                mMiddleHeightAuto = true;
                layout = true;
            }
        } else if (mMiddleHeightAuto || mMiddleHeight != middleHeight) {
            mMiddleHeightAuto = false;
            mMiddleHeight = Math.max(0, middleHeight);
            layout = true;
        }
        if (layout && this.state == STATE_COLLAPSED && viewRef != null) {
            V view = viewRef.get();
            if (view != null) {
                view.requestLayout();
            }
        }
    }

    public final int getPeekHeight() {
        return this.peekHeightAuto ? -1 : this.peekHeight;
    }

    public final int getMiddleHeight() {
        return this.mMiddleHeightAuto ? -1 : this.mMiddleHeight;
    }

    public final int getParentHeight() {
        return this.parentHeight;
    }

    public void setHideable(boolean hideable) {
        this.hideable = hideable;
    }

    public boolean isHideable() {
        return this.hideable;
    }

    public void setSkipCollapsed(boolean skipCollapsed) {
        this.skipCollapsed = skipCollapsed;
    }

    public boolean getSkipCollapsed() {
        return this.skipCollapsed;
    }

    public void setBottomSheetCallback(GaoDeBottomSheetBehavior.BottomSheetCallback callback) {
        this.callback = callback;
    }

    public final void setState(final int state) {
        if (state != this.state) {
            if (this.viewRef == null) {
                if (state == STATE_COLLAPSED || state == STATE_EXPANDED || state == STATE_HALF_EXPANDED || this.hideable && state == STATE_HIDDEN) {
                    this.state = state;
                }

            } else {
                final V child = (V) this.viewRef.get();
                if (child != null) {
                    ViewParent parent = child.getParent();
                    if (parent != null && parent.isLayoutRequested() && ViewCompat.isAttachedToWindow(child)) {
                        child.post(new Runnable() {
                            @Override
                            public void run() {
                                GaoDeBottomSheetBehavior.this.startSettlingAnimation(child, state);
                            }
                        });
                    } else {
                        this.startSettlingAnimation(child, state);
                    }

                }
            }
        }
    }

    public final int getState() {
        return this.state;
    }

    void setStateInternal(int state) {
        if (this.state != state) {
            this.state = state;
            if (state != STATE_HALF_EXPANDED && state != STATE_EXPANDED) {
                if (state == STATE_HIDDEN || state == STATE_COLLAPSED) {
                    this.updateImportantForAccessibility(false);
                }
            } else {
                this.updateImportantForAccessibility(true);
            }

            View bottomSheet = (View) this.viewRef.get();
            if (bottomSheet != null && this.callback != null) {
                this.callback.onStateChanged(bottomSheet, state);
            }

        }
    }

    private void calculateCollapsedOffset() {
        if (this.fitToContents) {
            this.collapsedOffset = Math.max(this.parentHeight - this.lastPeekHeight, this.fitToContentsOffset);
        } else {
            this.collapsedOffset = this.parentHeight - this.lastPeekHeight;
        }

    }

    private void reset() {
        this.activePointerId = -1;
        if (this.velocityTracker != null) {
            this.velocityTracker.recycle();
            this.velocityTracker = null;
        }

    }

    boolean shouldHide(View child, float yvel) {
        if (this.skipCollapsed) {
            return true;
        } else if (child.getTop() < this.collapsedOffset) {
            return false;
        } else {
            float newTop = (float) child.getTop() + yvel * HIDE_FRICTION;
            return Math.abs(newTop - (float) this.collapsedOffset) / (float) this.peekHeight > HIDE_THRESHOLD;
        }
    }

    @VisibleForTesting
    View findScrollingChild(View view) {
        if (ViewCompat.isNestedScrollingEnabled(view)) {
            return view;
        } else {
            if (view instanceof ViewGroup) {
                ViewGroup group = (ViewGroup) view;
                int i = 0;

                for (int count = group.getChildCount(); i < count; ++i) {
                    View scrollingChild = this.findScrollingChild(group.getChildAt(i));
                    if (scrollingChild != null) {
                        return scrollingChild;
                    }
                }
            }

            return null;
        }
    }

    private float getYVelocity() {
        if (this.velocityTracker == null) {
            return 0.0F;
        } else {
            this.velocityTracker.computeCurrentVelocity(1000, this.maximumVelocity);
            return this.velocityTracker.getYVelocity(this.activePointerId);
        }
    }

    private int getExpandedOffset() {
        return this.fitToContents ? this.fitToContentsOffset : 0;
    }

    void startSettlingAnimation(View child, int state) {
        int top;
        if (state == STATE_COLLAPSED) {
            top = this.collapsedOffset;
        } else if (state == STATE_HALF_EXPANDED) {
            top = this.halfExpandedOffset;
        } else if (state == STATE_EXPANDED) {
            top = this.getExpandedOffset();
        } else {
            if (!this.hideable || state != STATE_HIDDEN) {
                throw new IllegalArgumentException("Illegal state argument: " + state);
            }

            top = this.parentHeight;
        }

        if (this.viewDragHelper.smoothSlideViewTo(child, child.getLeft(), top)) {
            this.setStateInternal(STATE_SETTLING);
            ViewCompat.postOnAnimation(child, new GaoDeBottomSheetBehavior.SettleRunnable(child, state));
        } else {
            this.setStateInternal(state);
        }

    }

    void dispatchOnSlide(int top) {
        View bottomSheet = (View) this.viewRef.get();
        if (bottomSheet != null && this.callback != null) {
            if (top > this.collapsedOffset) {
                this.callback.onSlide(bottomSheet, (float) (this.collapsedOffset - top) / (float) (this.parentHeight - this.collapsedOffset));
            } else {
                this.callback.onSlide(bottomSheet, (float) (this.collapsedOffset - top) / (float) (this.collapsedOffset - this.getExpandedOffset()));
            }
        }

    }

    @VisibleForTesting
    int getPeekHeightMin() {
        return this.peekHeightMin;
    }

    public static <V extends View> GaoDeBottomSheetBehavior<V> from(V view) {
        LayoutParams params = view.getLayoutParams();
        if (!(params instanceof CoordinatorLayout.LayoutParams)) {
            throw new IllegalArgumentException("The view is not a child of CoordinatorLayout");
        } else {
            Behavior behavior = ((CoordinatorLayout.LayoutParams) params).getBehavior();
            if (!(behavior instanceof GaoDeBottomSheetBehavior)) {
                throw new IllegalArgumentException("The view is not associated with BottomSheetBehavior");
            } else {
                return (GaoDeBottomSheetBehavior) behavior;
            }
        }
    }

    @SuppressLint("WrongConstant")
    private void updateImportantForAccessibility(boolean expanded) {
        if (this.viewRef != null) {
            ViewParent viewParent = ((View) this.viewRef.get()).getParent();
            if (viewParent instanceof CoordinatorLayout) {
                CoordinatorLayout parent = (CoordinatorLayout) viewParent;
                int childCount = parent.getChildCount();
                if (VERSION.SDK_INT >= 16 && expanded) {
                    if (this.importantForAccessibilityMap != null) {
                        return;
                    }

                    this.importantForAccessibilityMap = new HashMap(childCount);
                }

                for (int i = 0; i < childCount; ++i) {
                    View child = parent.getChildAt(i);
                    if (child != this.viewRef.get()) {
                        if (!expanded) {
                            if (this.importantForAccessibilityMap != null && this.importantForAccessibilityMap.containsKey(child)) {
                                ViewCompat.setImportantForAccessibility(child, (Integer) this.importantForAccessibilityMap.get(child));
                            }
                        } else {
                            if (VERSION.SDK_INT >= 16) {
                                this.importantForAccessibilityMap.put(child, child.getImportantForAccessibility());
                            }

                            ViewCompat.setImportantForAccessibility(child, 4);
                        }
                    }
                }

                if (!expanded) {
                    this.importantForAccessibilityMap = null;
                }

            }
        }
    }

    protected static class SavedState extends AbsSavedState {
        final int state;
        public static final Creator<GaoDeBottomSheetBehavior.SavedState> CREATOR = new ClassLoaderCreator<GaoDeBottomSheetBehavior.SavedState>() {
            @Override
            public GaoDeBottomSheetBehavior.SavedState createFromParcel(Parcel in, ClassLoader loader) {
                return new GaoDeBottomSheetBehavior.SavedState(in, loader);
            }

            @Override
            public GaoDeBottomSheetBehavior.SavedState createFromParcel(Parcel in) {
                return new GaoDeBottomSheetBehavior.SavedState(in, (ClassLoader) null);
            }

            @Override
            public GaoDeBottomSheetBehavior.SavedState[] newArray(int size) {
                return new GaoDeBottomSheetBehavior.SavedState[size];
            }
        };

        public SavedState(Parcel source) {
            this(source, (ClassLoader) null);
        }

        public SavedState(Parcel source, ClassLoader loader) {
            super(source, loader);
            this.state = source.readInt();
        }

        public SavedState(Parcelable superState, int state) {
            super(superState);
            this.state = state;
        }

        @Override
        public void writeToParcel(Parcel out, int flags) {
            super.writeToParcel(out, flags);
            out.writeInt(this.state);
        }
    }

    private class SettleRunnable implements Runnable {
        private final View view;
        private final int targetState;

        SettleRunnable(View view, int targetState) {
            this.view = view;
            this.targetState = targetState;
        }

        @Override
        public void run() {
            if (GaoDeBottomSheetBehavior.this.viewDragHelper != null && GaoDeBottomSheetBehavior.this.viewDragHelper.continueSettling(true)) {
                ViewCompat.postOnAnimation(this.view, this);
            } else {
                GaoDeBottomSheetBehavior.this.setStateInternal(this.targetState);
            }

        }
    }

    @Retention(RetentionPolicy.SOURCE)
    @RestrictTo({Scope.LIBRARY_GROUP})
    public @interface State {
    }

    public abstract static class BottomSheetCallback {
        public BottomSheetCallback() {
        }

        public abstract void onStateChanged(@NonNull View var1, int var2);

        public abstract void onSlide(@NonNull View var1, float var2);
    }
}

使用的时候直接设置:


    <androidx.appcompat.widget.LinearLayoutCompat
            android:id="@+id/bottom_sheet"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="@android:color/white"
            android:orientation="vertical"
            android:visibility="visible"
            app:behavior_hideable="false"
            app:behavior_middleHeight="200dp"
            app:behavior_peekHeight="80dp"
          app:layout_behavior=".gaode.GaoDeBottomSheetBehavior"
            tools:ignore="MissingPrefix">
         //....
    </androidx.appcompat.widget.LinearLayoutCompat>

对于按钮滑动及通明度渐变隐藏显示也是通过实现behavior,因为比较的简单直接上代码:



public class GaoDeBtnBehavior extends CoordinatorLayout.Behavior {

    private View rightActions;
    private View topActions;

    public GaoDeBtnBehavior() {
    }

    public GaoDeBtnBehavior(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    public boolean onLayoutChild(@NonNull CoordinatorLayout parent, @NonNull View child, int layoutDirection) {
        if (ViewCompat.getFitsSystemWindows(parent) && !ViewCompat.getFitsSystemWindows(child)) {
            child.setFitsSystemWindows(true);
        }
        if (rightActions == null) {
            rightActions = parent.findViewById(R.id.rightActions);
        }
        if (topActions == null) {
            topActions = parent.findViewById(R.id.topActions);
        }
        return super.onLayoutChild(parent, child, layoutDirection);
    }

    @Override
    public boolean layoutDependsOn(@NonNull CoordinatorLayout parent, @NonNull View child, @NonNull View dependency) {
        return dependency instanceof LinearLayoutCompat || super.layoutDependsOn(parent, child, dependency);
    }

    @Override
    public boolean onDependentViewChanged(@NonNull CoordinatorLayout parent, @NonNull View child, @NonNull View dependency) {
        //判断当前dependency 是内容布局
        if (dependency instanceof LinearLayoutCompat && dependency.getId() == R.id.bottom_sheet) {
            if (rightActions != null) {
                GaoDeBottomSheetBehavior behavior = GaoDeBottomSheetBehavior.from(dependency);
                int middleHeight = behavior.getParentHeight() - behavior.getMiddleHeight() - rightActions.getMeasuredHeight();
                CoordinatorLayout.LayoutParams layoutParams = (CoordinatorLayout.LayoutParams) rightActions.getLayoutParams();
                int newY = dependency.getTop() - rightActions.getHeight() - layoutParams.bottomMargin;
                if (newY >= middleHeight) {
                    rightActions.setTranslationY(newY - layoutParams.bottomMargin);
                } else {
                    rightActions.setTranslationY(middleHeight);
                }
                int offset = behavior.getParentHeight() - behavior.getMiddleHeight() - layoutParams.bottomMargin - dependency.getTop();
                float alpha = 1f - offset * 1.0f / rightActions.getHeight();

                rightActions.setAlpha(alpha);
                if (topActions != null) {
                    topActions.setAlpha(alpha);
                }
            }
        }
        return super.onDependentViewChanged(parent, child, dependency);
    }
}

源码地址:

https://github.com/yixiaolunhui/FGaoDeIndexDemo

到这里就结束啦。

以上就是Android实现三段式滑动效果的详细内容,更多关于Android 三段式滑动效果的资料请关注编程网其它相关文章!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

Android实现三段式滑动效果

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

Android仿探探卡片式滑动效果实现

前言 第一次进入探探软件界面,就被这种通过卡片式滑动来选择“喜欢/不喜欢”的设计所吸引了。当时就非常想通过自己来实现这种仿探探式的效果,然而却没什么思路。不过毋庸置疑的是,这种效果的原理肯定和 ListView / RecyclerView
2022-06-06

Android如何实现View滑动效果

这篇文章给大家分享的是有关Android如何实现View滑动效果的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。一、View的滑动简介View的滑动是Android实现自定义控件的基础,同时在开发中我们也难免会遇到
2023-06-14

Android实现果冻滑动效果的控件

前言 在微信是的处理方法是让用户滑动,但最终还是回滚到最初的地方,这样的效果很生动(毕竟成功还是取决于细节)。那么在安卓我们要怎么弄呢。下面为大家介绍一下JellyScrollView,是我继承ScrollView的一个有阻尼的效果的果冻滑
2022-06-06

Android Scroll滑动效果实例

相对于在Android2.x版本上出现的长按、点击事件的效果,不得不说,滑动操作具有更好的用户体验。因此,从Android 4.X版本开始,出现了更多滑动操作的效果。越来越多第三方应用模仿这样的效果,来改善自己的应用。本文,
2022-06-06

Android实现图片左右滑动效果

关于滑动效果,在Android中用得比较多,本示例实现的滑动效果是使用ViewFlipper来实现的,当然也可以使用其它的View来实现。接下来就让我们开始实现这种效果。接下来我们看一下程序结构图:MainActivity文件中代码:pac
2022-06-06

Android实现探探图片滑动效果

之前一段时间,在朋友的推荐下,玩了探探这一款软件,初玩的时候,就发现,这款软件与一般的社交软件如陌陌之类的大相径庭,让我耳目一新,特别是探探里关于图片滑动操作让人觉得非常新鲜。所以在下通过网上之前的前辈的经历加上自己的理解,也来涉涉水。下面
2023-05-31

Android如何实现平滑翻动效果

这篇文章给大家分享的是有关Android如何实现平滑翻动效果的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。具体内容如下效果1.activity加implementsimplements GestureDetect
2023-06-14

Android实现自定义滑动式抽屉效果菜单

在Andoird使用Android自带的那些组件,像SlidingDrawer和DrawerLayout都是抽屉效果的菜单,但是在项目很多要实现的功能都收到Android这些自带组件的限制,导致很难完成项目的需求,自定义的组件,各方面都在自
2022-06-06

Android使用ViewPager实现无限滑动效果

前言 其实仔细想一下原理还是挺简单的。无非是当我们滑动到最后一页,再向后滑动时定位到第一页;当我们滑动到第一页,再向前滑动时定位到最后一页。 但是,相信很多朋友都遇到过这个问题:视图的过度效果不自然。 小编也是通过百度和谷歌查找了很多解决方
2022-06-06

Android实现手势滑动和简单动画效果

一、手势滑动1.Activity都具有响应触摸事件,也就是说只要触摸Activity,他都会回调一个onTouchEvent()方法。但是在这个方法里无法处理事件,需要配合使用手势识别器(GestureDetector)中的方法onTouc
2023-05-31

Android 实现局部图片滑动指引效果

Android 实现局部图片滑动指引效果 而ViewPager的事件监听器代码如下:// 滑动页面更改事件监听器private class ImagePageChangeListener implements OnPageChangeLis
2022-06-06

Android继承ViewGroup如何实现Scroll滑动效果

这篇文章主要介绍Android继承ViewGroup如何实现Scroll滑动效果,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!具体如下:extends ViewGroup需要重写onMeasure和onLayout方
2023-05-30

android左右滑动切换效果怎么实现

在Android中,可以通过使用ViewPager组件来实现左右滑动切换效果。首先,在XML布局文件中添加一个ViewPager组件:然后,在Activity中找到ViewPager组件,并为其设置Adapter:ViewPager v
2023-10-23

Android实现滑动到顶部悬停的效果

先来看下要实现效果图:查阅资料后,发现网上大部分都是用这种方法实现的: 多写一个和需要悬浮的部分一模一样的layout,先把浮动区域的可见性设置为gone。当浮动区域滑动到顶部的时候,就把浮动区域B的可见性设置为VISIBLE。这样看起来就
2022-06-06

编程热搜

  • Android:VolumeShaper
    VolumeShaper(支持版本改一下,minsdkversion:26,android8.0(api26)进一步学习对声音的编辑,可以让音频的声音有变化的播放 VolumeShaper.Configuration的三个参数 durati
    Android:VolumeShaper
  • Android崩溃异常捕获方法
    开发中最让人头疼的是应用突然爆炸,然后跳回到桌面。而且我们常常不知道这种状况会何时出现,在应用调试阶段还好,还可以通过调试工具的日志查看错误出现在哪里。但平时使用的时候给你闹崩溃,那你就欲哭无泪了。 那么今天主要讲一下如何去捕捉系统出现的U
    Android崩溃异常捕获方法
  • android开发教程之获取power_profile.xml文件的方法(android运行时能耗值)
    系统的设置–>电池–>使用情况中,统计的能耗的使用情况也是以power_profile.xml的value作为基础参数的1、我的手机中power_profile.xml的内容: HTC t328w代码如下:
    android开发教程之获取power_profile.xml文件的方法(android运行时能耗值)
  • Android SQLite数据库基本操作方法
    程序的最主要的功能在于对数据进行操作,通过对数据进行操作来实现某个功能。而数据库就是很重要的一个方面的,Android中内置了小巧轻便,功能却很强的一个数据库–SQLite数据库。那么就来看一下在Android程序中怎么去操作SQLite数
    Android SQLite数据库基本操作方法
  • ubuntu21.04怎么创建桌面快捷图标?ubuntu软件放到桌面的技巧
    工作的时候为了方便直接打开编辑文件,一些常用的软件或者文件我们会放在桌面,但是在ubuntu20.04下直接直接拖拽文件到桌面根本没有效果,在进入桌面后发现软件列表中的软件只能收藏到面板,无法复制到桌面使用,不知道为什么会这样,似乎并不是很
    ubuntu21.04怎么创建桌面快捷图标?ubuntu软件放到桌面的技巧
  • android获取当前手机号示例程序
    代码如下: public String getLocalNumber() { TelephonyManager tManager =
    android获取当前手机号示例程序
  • Android音视频开发(三)TextureView
    简介 TextureView与SurfaceView类似,可用于显示视频或OpenGL场景。 与SurfaceView的区别 SurfaceView不能使用变换和缩放等操作,不能叠加(Overlay)两个SurfaceView。 Textu
    Android音视频开发(三)TextureView
  • android获取屏幕高度和宽度的实现方法
    本文实例讲述了android获取屏幕高度和宽度的实现方法。分享给大家供大家参考。具体分析如下: 我们需要获取Android手机或Pad的屏幕的物理尺寸,以便于界面的设计或是其他功能的实现。下面就介绍讲一讲如何获取屏幕的物理尺寸 下面的代码即
    android获取屏幕高度和宽度的实现方法
  • Android自定义popupwindow实例代码
    先来看看效果图:一、布局
  • Android第一次实验
    一、实验原理 1.1实验目标 编程实现用户名与密码的存储与调用。 1.2实验要求 设计用户登录界面、登录成功界面、用户注册界面,用户注册时,将其用户名、密码保存到SharedPreference中,登录时输入用户名、密码,读取SharedP
    Android第一次实验

目录