高级UIview

侧滑效果[第四篇]:侧滑框架SmartSwipe之抽屉效果

2019-11-11  本文已影响0人  NoBugException

SmartSwipe框架的抽屉效果有两个消费者,分别是:DrawerConsumerSlidingConsumer。他们的区别是:
DrawerConsumer:抽屉显示在被包裹的控件上层
SlidingConsumer:抽屉显示在被包裹的控件下层

(1)DrawerConsumer的使用

代码如下:

DrawerConsumer.java

/**
 * contains content view and at most 4 drawer view
 * drawer view shows above content view
 * default release mode is {@link #RELEASE_MODE_AUTO_OPEN_CLOSE}
 *
 * @author billy.qi
 */
public class DrawerConsumer extends SwipeConsumer implements View.OnClickListener {

    protected final View[] mDrawerViews = new View[4];
    protected View mCurDrawerView;
    protected int l, t, r, b;
    protected int mScrimColor = 0;
    protected int mShadowColor = 0;
    protected ScrimView mScrimView;
    protected int mShadowSize;
    protected boolean mDrawerViewRequired = true;
    protected boolean mShowScrimAndShadowOutsideContentView;

    public DrawerConsumer() {
        //set default release mode
        setReleaseMode(SwipeConsumer.RELEASE_MODE_AUTO_OPEN_CLOSE);
    }

    @Override
    public void onAttachToWrapper(SmartSwipeWrapper wrapper, SwipeHelper swipeHelper) {
        super.onAttachToWrapper(wrapper, swipeHelper);
        for (int i = 0; i < mDrawerViews.length; i++) {
            attachDrawerView(i);
        }
        if (mShadowSize == 0) {
            //10dp by default
            mShadowSize = SmartSwipe.dp2px(10, wrapper.getContext());
        }
    }

    @Override
    protected void initChildrenFormXml() {
        final SmartSwipeWrapper wrapper = mWrapper;
        int childCount = wrapper.getChildCount();
        View contentView = wrapper.getContentView();
        for (int i = 0; i < childCount; i++) {
            View child = wrapper.getChildAt(i);
            if (child == contentView || !(child.getLayoutParams() instanceof SmartSwipeWrapper.LayoutParams)) {
                continue;
            }
            final int gravity = ((SmartSwipeWrapper.LayoutParams) child.getLayoutParams()).gravity;
            if (mDrawerViews[0] == null && (gravity & DIRECTION_LEFT) == DIRECTION_LEFT) {
                // This child is a left drawer
                setLeftDrawerView(child);
                mWrapper.consumeInflateFromXml();
            }
            if (mDrawerViews[1] == null && (gravity & DIRECTION_RIGHT) == DIRECTION_RIGHT) {
                // This child is a right drawer
                setRightDrawerView(child);
                mWrapper.consumeInflateFromXml();
            }
            if (mDrawerViews[2] == null && (gravity & DIRECTION_TOP) == DIRECTION_TOP) {
                // This child is a top drawer
                setTopDrawerView(child);
                mWrapper.consumeInflateFromXml();
            }
            if (mDrawerViews[3] == null && (gravity & DIRECTION_BOTTOM) == DIRECTION_BOTTOM) {
                // This child is a bottom drawer
                setBottomDrawerView(child);
                mWrapper.consumeInflateFromXml();
            }
        }
    }

    @Override
    public void onDetachFromWrapper() {
        super.onDetachFromWrapper();
        if (mScrimView != null) {
            mWrapper.removeView(mScrimView);
            mScrimView.setOnClickListener(null);
            mScrimView = null;
        }
        for (View drawerView : mDrawerViews) {
            if (drawerView != null) {
                mWrapper.removeView(drawerView);
            }
        }
        mCurDrawerView = null;
    }

    @Override
    protected void onOpened() {
        super.onOpened();
        if (mScrimView != null && !mShowScrimAndShadowOutsideContentView) {
            mScrimView.setOnClickListener(this);
        }
    }

    @Override
    protected void onClosed() {
        super.onClosed();
        if (mCurDrawerView != null) {
            changeDrawerViewVisibility(INVISIBLE);
        }
        if (mScrimView != null) {
            mScrimView.setOnClickListener(null);
            mScrimView.setClickable(false);
            mScrimView.setFocusable(false);
            mScrimView.setVisibility(GONE);
        }
    }

    @Override
    public boolean tryAcceptMoving(int pointerId, float downX, float downY, float dx, float dy) {
        boolean handle = super.tryAcceptMoving(pointerId, downX, downY, dx, dy);
        if (handle && mCachedSwipeDistanceX == 0 && mCachedSwipeDistanceY == 0) {
            if (mDrawerViewRequired && getDrawerView(mDirection) == null) {
                handle = false;
            }
        }
        return handle;
    }

    @Override
    public void onSwipeAccepted(int activePointerId, boolean settling, float initialMotionX, float initialMotionY) {
        if (mCachedSwipeDistanceX == 0 && mCachedSwipeDistanceY == 0) {
            changeDrawerViewVisibility(INVISIBLE);
            mCurDrawerView = getDrawerView(mDirection);
            changeDrawerViewVisibility(VISIBLE);
        }
        int w = mWidth;
        int h = mHeight;
        if (mCurDrawerView != null) {
            w = mCurDrawerView.getMeasuredWidth();
            h = mCurDrawerView.getMeasuredHeight();
        } else if (mDrawerViewRequired) {
            return;
        }
        if (!mOpenDistanceSpecified) {
            if ((mDirection & DIRECTION_HORIZONTAL) > 0) {
                mOpenDistance = w;
            } else {
                mOpenDistance = h;
            }
        }
        calculateDrawerDirectionInitPosition(mDirection, w, h);
        changeDrawerViewVisibility(VISIBLE);
        initScrimView();
        layoutChildren();
        orderChildren();
        super.onSwipeAccepted(activePointerId, settling, initialMotionX, initialMotionY);
    }

    protected void changeDrawerViewVisibility(int visibility) {
        if (mCurDrawerView != null) {
            mCurDrawerView.setVisibility(visibility);
        }
    }

    @Override
    public void setCurrentStateAsClosed() {
        mCurDrawerView = null;
        super.setCurrentStateAsClosed();
    }

    protected void initScrimView() {
        if (mScrimColor != 0 || mShadowColor != 0 && mShadowSize > 0) {
            if (mScrimView == null) {
                mScrimView = new ScrimView(mWrapper.getContext());
                mWrapper.addView(mScrimView);
            }
            mScrimView.setScrimColor(mScrimColor);
            if (mShadowColor != 0 && mShadowSize > 0) {
                int shadowDirection = this.mDirection;
                if (mShowScrimAndShadowOutsideContentView) {
                    shadowDirection = SwipeUtil.getReverseDirection(mDirection);
                }
                mScrimView.setDirection(this.mDirection, mShadowColor, shadowDirection, mShadowSize, mWidth, mHeight);
            }
            mScrimView.setVisibility(VISIBLE);
        }
    }

    protected void calculateDrawerDirectionInitPosition(int direction, int w, int h) {
        switch (direction) {
            case DIRECTION_LEFT:    l = -w;     r = l + w;  t = 0;      b = h; break;
            case DIRECTION_RIGHT:   l = mWidth; r = l + w;  t = 0;      b = h; break;
            case DIRECTION_TOP:     l = 0;      r = mWidth; t = -h;     b = t + h; break;
            case DIRECTION_BOTTOM:  l = 0;      r = mWidth; t = mHeight;b = t + h; break;
            default: break;
        }
    }

    @Override
    public boolean onLayout(boolean changed, int left, int top, int right, int bottom) {
        if (mWrapper != null) {
            layoutChildren();
            return true;
        }
        return false;
    }

    @Override
    protected void onDisplayDistanceChanged(int distanceXToDisplay, int distanceYToDisplay, int dx, int dy) {
        View drawerView = mCurDrawerView;
        if (drawerView != null && drawerView.getParent() == mWrapper) {
            boolean horizontal = (mDirection & DIRECTION_HORIZONTAL) > 0;
            if (horizontal) {
                ViewCompat.offsetLeftAndRight(drawerView, dx);
            } else {
                ViewCompat.offsetTopAndBottom(drawerView, dy);
            }
            layoutScrimView();
        }
    }

    protected void orderChildren() {
        if (mCurDrawerView != null) {
            mCurDrawerView.bringToFront();
        }
        if (mScrimView != null) {
            mScrimView.bringToFront();
        }
    }

    protected void layoutChildren() {
        layoutContentView(mWrapper.getContentView());
        layoutDrawerView();
        layoutScrimView();
    }

    protected void layoutContentView(View contentView) {
        if (contentView != null) {
            contentView.layout(0, 0, mWidth, mHeight);
        }
    }

    protected void layoutDrawerView() {
        if (mCurDrawerView != null && mCurDrawerView.getVisibility() == VISIBLE) {
            mCurDrawerView.layout(l + mCurDisplayDistanceX, t + mCurDisplayDistanceY, r + mCurDisplayDistanceX, b + mCurDisplayDistanceY);
        }
    }

    protected void layoutScrimView() {
        if (mScrimView != null && mScrimView.getVisibility() == VISIBLE) {
            int l = 0, r = mWidth, t = 0, b = mHeight;
            if (mShowScrimAndShadowOutsideContentView) {
                switch (mDirection) {
                    case DIRECTION_LEFT:    r = mCurDisplayDistanceX;  break;
                    case DIRECTION_RIGHT:   l = r + mCurDisplayDistanceX;  break;
                    case DIRECTION_TOP:     b = mCurDisplayDistanceY;  break;
                    case DIRECTION_BOTTOM:  t = b + mCurDisplayDistanceY;  break;
                    default:
                }
            } else {
                switch (mDirection) {
                    case DIRECTION_LEFT:    l = mCurDisplayDistanceX;  break;
                    case DIRECTION_RIGHT:   r = r + mCurDisplayDistanceX;  break;
                    case DIRECTION_TOP:     t = mCurDisplayDistanceY;  break;
                    case DIRECTION_BOTTOM:  b = b + mCurDisplayDistanceY;  break;
                    default:
                }
            }
            mScrimView.layout(l, t, r, b);
            mScrimView.setProgress(mShowScrimAndShadowOutsideContentView ? (1 - mProgress) : mProgress);
        }
    }

    @Override
    protected void notifySwipeStart() {
        if (mCurDrawerView instanceof SwipeListener) {
            ((SwipeListener)mCurDrawerView).onSwipeStart(mWrapper, this, mDirection);
        }
        super.notifySwipeStart();
    }

    @Override
    protected void notifySwipeProgress(boolean settling) {
        if (mCurDrawerView instanceof SwipeListener) {
            ((SwipeListener) mCurDrawerView).onSwipeProcess(mWrapper, this, mDirection, settling, mProgress);
        }
        super.notifySwipeProgress(settling);
    }

    @Override
    protected void notifySwipeRelease(float xVelocity, float yVelocity) {
        if (mCurDrawerView instanceof SwipeListener) {
            ((SwipeListener) mCurDrawerView).onSwipeRelease(mWrapper, this, mDirection, mProgress, xVelocity, yVelocity);
        }
        super.notifySwipeRelease(xVelocity, yVelocity);
    }

    public View getDrawerView(int direction) {
        int viewIndex = -1;
        switch (direction) {
            default: break;
            case DIRECTION_LEFT:    viewIndex = 0; break;
            case DIRECTION_RIGHT:   viewIndex = 1; break;
            case DIRECTION_TOP:     viewIndex = 2; break;
            case DIRECTION_BOTTOM:  viewIndex = 3; break;
        }
        if (viewIndex < 0) {
            return null;
        }
        return mDrawerViews[viewIndex];
    }

    public DrawerConsumer setLeftDrawerView(View drawerView) {
        return setDrawerView(DIRECTION_LEFT, drawerView);
    }
    public DrawerConsumer setRightDrawerView(View drawerView) {
        return setDrawerView(DIRECTION_RIGHT, drawerView);
    }
    public DrawerConsumer setTopDrawerView(View drawerView) {
        return setDrawerView(DIRECTION_TOP, drawerView);
    }
    public DrawerConsumer setBottomDrawerView(View drawerView) {
        return setDrawerView(DIRECTION_BOTTOM, drawerView);
    }
    public DrawerConsumer setHorizontalDrawerView(View drawerView) {
        return setDrawerView(DIRECTION_HORIZONTAL, drawerView);
    }
    public DrawerConsumer setVerticalDrawerView(View drawerView) {
        return setDrawerView(DIRECTION_VERTICAL, drawerView);
    }
    public DrawerConsumer setAllDirectionDrawerView(View drawerView) {
        return setDrawerView(DIRECTION_ALL, drawerView);
    }

    /**
     * set a extension to the direction, also set direction enable if drawerView is not null(otherwise, disable the direction)
     * direction can be a single direction or mixed direction(eg: DIRECTION_LEFT | DIRECTION_RIGHT)
     * @param direction direction to set
     * @param drawerView view
     * @return this
     */
    public DrawerConsumer setDrawerView(int direction, View drawerView) {
        enableDirection(direction, drawerView != null);
        if ((direction & DIRECTION_LEFT)    > 0) {
            setOrUpdateDrawerView(0, drawerView);
        }
        if ((direction & DIRECTION_RIGHT)   > 0) {
            setOrUpdateDrawerView(1, drawerView);
        }
        if ((direction & DIRECTION_TOP)     > 0) {
            setOrUpdateDrawerView(2, drawerView);
        }
        if ((direction & DIRECTION_BOTTOM)  > 0) {
            setOrUpdateDrawerView(3, drawerView);
        }
        return this;
    }

    private void setOrUpdateDrawerView(int index, View drawerView) {
        View oldView = mDrawerViews[index];
        if (oldView == drawerView) {
            return;
        }
        mDrawerViews[index] = drawerView;
        attachDrawerView(index);
    }

    private void attachDrawerView(final int index) {
        final View drawerView = mDrawerViews[index];
        final SmartSwipeWrapper wrapper = mWrapper;
        if (drawerView != null && wrapper != null && drawerView.getParent() != wrapper) {
            if (drawerView.getParent() != null) {
                ((ViewGroup)drawerView.getParent()).removeView(drawerView);
            }
            int contentViewIndex = wrapper.indexOfChild(wrapper.getContentView());
            if (contentViewIndex >= 0) {
                ViewGroup.LayoutParams lp = drawerView.getLayoutParams();
                if (lp == null) {
                    int w = FrameLayout.LayoutParams.WRAP_CONTENT, h = FrameLayout.LayoutParams.WRAP_CONTENT;
                    switch (index) {
                        default: break;
                        case 0: case 1: h = FrameLayout.LayoutParams.MATCH_PARENT; break;
                        case 2: case 3: w = FrameLayout.LayoutParams.MATCH_PARENT; break;
                    }
                    lp = new FrameLayout.LayoutParams(w, h);
                    drawerView.setLayoutParams(lp);
                }
                wrapper.addView(drawerView, contentViewIndex);
                drawerView.setVisibility(INVISIBLE);
            }
        }
    }

    @Override
    public int getOpenDistance() {
        if (mCurDrawerView == null) {
            return super.getOpenDistance();
        }
        if ((mDirection & DIRECTION_HORIZONTAL) > 0) {
            return mCurDrawerView.getMeasuredWidth();
        }
        return mCurDrawerView.getMeasuredHeight();
    }

    /**
     * Set a color to use for the scrim that obscures primary content while a drawer is open.
     * @param color Color to use in 0xAARRGGBB format.
     * @return this
     */
    public DrawerConsumer setScrimColor(int color) {
        mScrimColor = color;
        return this;
    }

    /**
     * Set a color to use for the shadow at the edge of content view while a drawer is open.
     * @param shadowColor  Color to use in 0xAARRGGBB format.
     * @return this
     */
    public DrawerConsumer setShadowColor(int shadowColor) {
        mShadowColor = shadowColor;
        return this;
    }

    public int getShadowSize() {
        return mShadowSize;
    }

    /**
     * set the size of shadow at the edge of content view while a drawer is open.
     * @param size shadow size in pixel
     * @return this
     */
    public DrawerConsumer setShadowSize(int size) {
        this.mShadowSize = size;
        return this;
    }

    public boolean isDrawerViewRequired() {
        return mDrawerViewRequired;
    }

    /**
     * set the extension view as drawer is required or not
     * it useful inside this sdk framework,
     * developers who use this SDK do not call this function unless you really know what its mean
     * @param required required or not
     * @return this
     */
    public DrawerConsumer setDrawerViewRequired(boolean required) {
        this.mDrawerViewRequired = required;
        return this;
    }

    public boolean isScrimAndShadowOutsideContentView() {
        return mShowScrimAndShadowOutsideContentView;
    }

    public DrawerConsumer showScrimAndShadowOutsideContentView() {
        this.mShowScrimAndShadowOutsideContentView = true;
        return this;
    }
    public DrawerConsumer showScrimAndShadowInsideContentView() {
        this.mShowScrimAndShadowOutsideContentView = false;
        return this;
    }

    @Override
    public void onClick(View v) {
        if (getDragState() == SwipeHelper.STATE_IDLE && !mShowScrimAndShadowOutsideContentView && v == mScrimView) {
            smoothClose();
        }
    }

}

抽屉的创建

    //--- 横向抽屉布局的创建(左滑和右滑的抽屉)
    View horizontalMenu = LayoutInflater.from(this).inflate(R.layout.layout_main_menu, null);
    horizontalMenu.setLayoutParams(new ViewGroup.LayoutParams(500, ViewGroup.LayoutParams.MATCH_PARENT));
    SmartSwipeWrapper horizontalMenuWrapper = SmartSwipe.wrap(horizontalMenu);

    //--- 左边的抽屉
    View leftMenu = LayoutInflater.from(this).inflate(R.layout.layout_main_menu, null);
    leftMenu.setLayoutParams(new ViewGroup.LayoutParams(500, ViewGroup.LayoutParams.MATCH_PARENT));
    SmartSwipeWrapper leftMenuWrapper = SmartSwipe.wrap(leftMenu);

    //--- 右边的抽屉
    View rightMenu = LayoutInflater.from(this).inflate(R.layout.layout_main_menu, null);
    rightMenu.setLayoutParams(new ViewGroup.LayoutParams(500, ViewGroup.LayoutParams.MATCH_PARENT));
    SmartSwipeWrapper rightMenuWrapper = SmartSwipe.wrap(rightMenu);

    //--- 上边的抽屉
    View topMenu = LayoutInflater.from(this).inflate(R.layout.layout_main_menu, null);
    topMenu.setLayoutParams(new ViewGroup.LayoutParams(500, ViewGroup.LayoutParams.MATCH_PARENT));
    SmartSwipeWrapper topMenuWrapper = SmartSwipe.wrap(topMenu);

    //--- 下边的抽屉
    View bottomMenu = LayoutInflater.from(this).inflate(R.layout.layout_main_menu, null);
    bottomMenu.setLayoutParams(new ViewGroup.LayoutParams(500, ViewGroup.LayoutParams.MATCH_PARENT));
    SmartSwipeWrapper bottomMenuWrapper = SmartSwipe.wrap(horizontalMenu);

抽屉打开或者关闭的监听

    SimpleSwipeListener listener = new SimpleSwipeListener() {
        @Override
        public void onSwipeOpened(SmartSwipeWrapper wrapper, SwipeConsumer consumer, int direction) {
            super.onSwipeOpened(wrapper, consumer, direction);
        }

        @Override
        public void onSwipeClosed(SmartSwipeWrapper wrapper, SwipeConsumer consumer, int direction) {
            super.onSwipeClosed(wrapper, consumer, direction);
        }
    };

抽屉消费者对象的创建与属性

    mDrawerConsumer = new DrawerConsumer()
            //横向布局的抽屉
            //.setHorizontalDrawerView(horizontalMenuWrapper)
            //仅显示左边抽屉
            .setLeftDrawerView(leftMenuWrapper)
            //仅显示右边抽屉
            .setRightDrawerView(rightMenuWrapper)
            //仅显示上边抽屉
            //.setTopDrawerView(topMenuWrapper)
            //仅显示下边抽屉
            .setBottomDrawerView(bottomMenu)
            //设置遮罩层背景颜色,默认透明
            .setScrimColor(0x80A7BBBB)
            //设置边框阴影颜色,默认透明
            .setShadowColor(0x80CAA639)
            //设置边框阴影大小
            .setShadowSize(SmartSwipe.dp2px(20, this))
            //设置监听
            .addListener(listener)
            //设置边的大小,手指可以在某个方位的20dp范围内触发侧滑事件
            .setEdgeSize(SmartSwipe.dp2px(20, this))
            //将SwipeConsumer类型转换为DrawerConsumer类型
            .as(DrawerConsumer.class);

将消费者添加到某View上

SmartSwipe.wrap(this).addConsumer(mDrawerConsumer);

效果如下:

271.gif
(2)SlidingConsumer的使用

SlidingConsumer的使用方式和DrawerConsumer一样,只要将DrawerConsumer换成SlidingConsumer就可以了,SlidingConsumer代码如下:

SlidingConsumer.java

/**
 * @author billy.qi
 */
public class SlidingConsumer extends DrawerConsumer {
    /** contentView moves above the drawer view, drawer view show below contentView */
    public static final float FACTOR_COVER = 0F;

    /** Default factor, contentView moves and drawer view followed pixel by pixel */
    public static final float FACTOR_FOLLOW = 1F;

    protected float mRelativeMoveFactor = 0.5F;
    protected boolean mEdgeAffinity;
    protected boolean mDrawerExpandable;
    protected int mDrawerW, mDrawerH;

    @Override
    public void onDetachFromWrapper() {
        super.onDetachFromWrapper();
        for (View drawerView : mDrawerViews) {
            if (drawerView != null) {
                drawerView.scrollTo(0, 0);
            }
        }
        View contentView = mWrapper.getContentView();
        if (contentView != null) {
            contentView.layout(0, 0, mWidth, mHeight);
        }
    }

    @Override
    protected void calculateDrawerDirectionInitPosition(int direction, int w, int h) {
        mDrawerW = w;
        mDrawerH = h;
        int initDistance = (int) (mOpenDistance * (1 - mRelativeMoveFactor) + 0.5F);
        switch (direction) {
            case DIRECTION_LEFT:
                l = -w + initDistance;
                if (mEdgeAffinity && l > 0) {
                    l = 0;
                }
                r = l + w;  t = 0;      b = h;
                break;
            case DIRECTION_RIGHT:
                l = mWidth - initDistance;
                r = l + w;  t = 0;      b = h;
                if (mEdgeAffinity && r < mWidth) {
                    r = mWidth;
                    l = r - w;
                }
                break;
            case DIRECTION_TOP:
                l = 0;      r = mWidth; t = -h + initDistance;
                if (mEdgeAffinity && t > 0) {
                    t = 0;
                }
                b = t + h; break;
            case DIRECTION_BOTTOM:
                l = 0;      r = mWidth; t = mHeight - initDistance;b = t + h;
                if (mEdgeAffinity && b < mHeight) {
                    b = mHeight;
                    t = b - h;
                }
                break;
            default: break;
        }
    }

    @Override
    protected void onDisplayDistanceChanged(int distanceXToDisplay, int distanceYToDisplay, int dx, int dy) {
        layoutChildren();
    }

    @Override
    protected void orderChildren() {
        View contentView = mWrapper.getContentView();
        if (contentView != null) {
            contentView.bringToFront();
        }
        if (mScrimView != null) {
            mScrimView.bringToFront();
        }
    }

    @Override
    protected void layoutContentView(View contentView) {
        if (contentView != null) {
            contentView.layout(mCurDisplayDistanceX, mCurDisplayDistanceY, mWidth + mCurDisplayDistanceX, mHeight + mCurDisplayDistanceY);
        }
    }

    @Override
    protected void layoutDrawerView() {
        View contentView = mWrapper.getContentView();
        final View drawerView = mCurDrawerView;
        if (contentView == null || drawerView == null) {
            return;
        }
        if (drawerView.getVisibility() == VISIBLE) {
            //layout drawer view
            int rdx = (int) (mCurDisplayDistanceX * mRelativeMoveFactor + (mCurDisplayDistanceX > 0 ? 0.5f : -0.5f));
            int rdy = (int) (mCurDisplayDistanceY * mRelativeMoveFactor + (mCurDisplayDistanceY > 0 ? 0.5f : -0.5f));
            int scrollX = 0, scrollY = 0;
            int left = l, top = t, right = r, bottom = b;
            switch (mDirection) {
                case DIRECTION_LEFT:
                    left = l + rdx;
                    right = contentView.getLeft();
                    if (!mDrawerExpandable) {
                        if (!mEdgeAffinity && right > mDrawerW) {
                            left = right - mDrawerW;
                        } else if (mEdgeAffinity && left > 0) {
                            left = 0;
                        }
                        if (right - left > mDrawerW) {
                            right = left + mDrawerW;
                        }
                    } else if (left > 0) {
                        left = 0;
                    }
                    break;
                case DIRECTION_RIGHT:
                    left = contentView.getRight();
                    right = r + rdx;
                    if (!mDrawerExpandable) {
                        if (!mEdgeAffinity && left + mDrawerW < mWidth) {
                            right = left + mDrawerW;
                        } else if (mEdgeAffinity && right < mWidth) {
                            right = mWidth;
                        }
                        if (right - left > mDrawerW) {
                            left = right - mDrawerW;
                        }
                    } else if (right < mWidth) {
                        right = mWidth;
                    }
                    scrollX = (int) ((mOpenDistance + mCurDisplayDistanceX) * (1 - mRelativeMoveFactor));
                    scrollX = Math.max(scrollX, 0);
                    break;
                case DIRECTION_TOP:
                    top = t + rdy;
                    bottom = contentView.getTop();
                    if (!mDrawerExpandable) {
                        if (!mEdgeAffinity && bottom > mDrawerH) {
                            top = bottom - mDrawerH;
                        } else if (mEdgeAffinity && top > 0) {
                            top = 0;
                        }
                        if (bottom - top > mDrawerH) {
                            bottom = top + mDrawerH;
                        }
                    } else if (top > 0) {
                        top = 0;
                    }
                    break;
                case DIRECTION_BOTTOM:
                    top = contentView.getBottom();
                    bottom = b + rdy;
                    if (!mDrawerExpandable) {
                        if (!mEdgeAffinity && top + mDrawerH < mHeight) {
                            bottom = top + mDrawerH;
                        } else if (mEdgeAffinity && bottom < mHeight) {
                            bottom = mHeight;
                        }
                        if (bottom - top > mDrawerH) {
                            top = bottom - mDrawerH;
                        }
                    } else if (bottom < mHeight) {
                        bottom = mHeight;
                    }
                    scrollY = (int) ((mOpenDistance + mCurDisplayDistanceY) * (1 - mRelativeMoveFactor));
                    scrollY = Math.max(scrollY, 0);
                    break;
                default: break;
            }
            drawerView.layout(left, top, right, bottom);
            //compat for contentView background is transparent
            // drawer view don`t overlap with contentView
            drawerView.scrollTo(scrollX, scrollY);
        }
    }

    public float getRelativeMoveFactor() {
        return mRelativeMoveFactor;
    }

    /**
     * Set the movement factor of drawer view relative to content view
     * @param factor Multiplier of mCurDrawerView relative movement to mWrapper.getContentView().
     *               this value must to be between 0F and 1F.
     *               0: drawer view not move
     *               0~1:
     *               1: drawer view followed contentView pixel by pixel
     * @return this
     */
    public SlidingConsumer setRelativeMoveFactor(float factor) {
        this.mRelativeMoveFactor = SmartSwipe.ensureBetween(factor, FACTOR_COVER, FACTOR_FOLLOW);
        return this;
    }

    public boolean isEdgeAffinity() {
        return mEdgeAffinity;
    }

    /**
     * set drawer is affinity to the edge or not
     * <pre>
     * this property make effect when {@link #mDrawerExpandable} is false and {@link #mSwipeMaxDistance} is bigger than drawerView`s size(width or height).
     * it may be cause by:
     *  1. {@link #mSwipeOpenDistance} is bigger than drawerView`s size via {@link #setOpenDistance(int)}
     *  2. {@link #mOverSwipeFactor} is bigger than 0F via {@link #setOverSwipeFactor(float)}
     * </pre>
     * if set true, drawer view adsorbs to {@link com.billy.android.swipe.SmartSwipeWrapper}`s edge
     * @param affinity adsorbs to edge or not
     * @return this
     * @see #setOpenDistance(int)
     * @see #setOverSwipeFactor(float)
     * @see #setDrawerExpandable(boolean)
     */
    public SlidingConsumer setEdgeAffinity(boolean affinity) {
        this.mEdgeAffinity = affinity;
        return this;
    }

    public boolean isDrawerExpandable() {
        return mDrawerExpandable;
    }

    /**
     * determine whether drawers expandable.
     * <pre>
     * this property make effect when {@link #mSwipeMaxDistance} is bigger than drawerView`s size(width or height).
     * it may be cause by:
     *  1. {@link #mSwipeOpenDistance} is bigger than drawerView`s size via {@link #setOpenDistance(int)}
     *  2. {@link #mOverSwipeFactor} is bigger than 0F via {@link #setOverSwipeFactor(float)}
     * </pre>
     * if set true, {@link #setEdgeAffinity(boolean)} will be ignored.
     * @param expandable expandable or not
     * @return this
     * @see #setEdgeAffinity(boolean)
     * @see #setOverSwipeFactor(float)
     */
    public SlidingConsumer setDrawerExpandable(boolean expandable) {
        this.mDrawerExpandable = expandable;
        return this;
    }
}

将消费者添加到某View上

    mSlidingConsumer = new SlidingConsumer()
            //横向布局的抽屉
            //.setHorizontalDrawerView(horizontalMenuWrapper)
            //仅显示左边抽屉
            .setLeftDrawerView(leftMenuWrapper)
            //仅显示右边抽屉
            .setRightDrawerView(rightMenuWrapper)
            //仅显示上边抽屉
            //.setTopDrawerView(topMenuWrapper)
            //仅显示下边抽屉
            .setBottomDrawerView(bottomMenu)
            //设置遮罩层背景颜色,默认透明
            .setScrimColor(0x80A7BBBB)
            //设置边框阴影颜色,默认透明
            .setShadowColor(0x80CAA639)
            //设置边框阴影大小
            .setShadowSize(SmartSwipe.dp2px(20, this))
            //设置监听
            .addListener(listener)
            //设置边的大小,手指可以在某个方位的20dp范围内触发侧滑事件
            .setEdgeSize(SmartSwipe.dp2px(20, this))
            //将SwipeConsumer类型转换为DrawerConsumer类型
            .as(SlidingConsumer.class);

将消费者添加到某View上

    SmartSwipe.wrap(this).addConsumer(mSlidingConsumer);

效果如下:

272.gif
(3)侧滑布局的两个辅助类代码

ViewCompat.java

/**
 * @author billy.qi
 * @since 2019-05-23 11:25
 */
public class ViewCompat {

    /**
     * Indicates that the input type for the gesture is from a user touching the screen.
     */
    public static final int TYPE_TOUCH = 0;

    /**
     * Indicates that the input type for the gesture is caused by something which is not a user
     * touching a screen. This is usually from a fling which is settling.
     */
    public static final int TYPE_NON_TOUCH = 1;

    /**
     * Indicates no axis of view scrolling.
     */
    public static final int SCROLL_AXIS_NONE = 0;

    /**
     * Indicates scrolling along the horizontal axis.
     */
    public static final int SCROLL_AXIS_HORIZONTAL = 1 << 0;

    /**
     * Indicates scrolling along the vertical axis.
     */
    public static final int SCROLL_AXIS_VERTICAL = 1 << 1;

    private static ThreadLocal<Rect> sThreadLocalRect;

    private static Rect getEmptyTempRect() {
        if (sThreadLocalRect == null) {
            sThreadLocalRect = new ThreadLocal<>();
        }
        Rect rect = sThreadLocalRect.get();
        if (rect == null) {
            rect = new Rect();
            sThreadLocalRect.set(rect);
        }
        rect.setEmpty();
        return rect;
    }

    /**
     * <p>Cause an invalidate to happen on the next animation time step, typically the
     * next display frame.</p>
     *
     * <p>This method can be invoked from outside of the UI thread
     * only when this View is attached to a window.</p>
     *
     * @param view View to invalidate
     */
    public static void postInvalidateOnAnimation(View view) {
        if (Build.VERSION.SDK_INT >= 16) {
            view.postInvalidateOnAnimation();
        } else {
            view.postInvalidate();
        }
    }
    /**
     * Offset this view's vertical location by the specified number of pixels.
     * @param view view
     * @param offset the number of pixels to offset the view by
     */
    public static void offsetTopAndBottom(View view, int offset) {
        if (view == null || offset == 0) {
            return;
        }
        if (Build.VERSION.SDK_INT >= 23) {
            view.offsetTopAndBottom(offset);
        } else if (Build.VERSION.SDK_INT >= 21) {
            final Rect parentRect = getEmptyTempRect();
            boolean needInvalidateWorkaround = false;

            final ViewParent parent = view.getParent();
            if (parent instanceof View) {
                final View p = (View) parent;
                parentRect.set(p.getLeft(), p.getTop(), p.getRight(), p.getBottom());
                // If the view currently does not currently intersect the parent (and is therefore
                // not displayed) we may need need to invalidate
                needInvalidateWorkaround = !parentRect.intersects(view.getLeft(), view.getTop(),
                        view.getRight(), view.getBottom());
            }

            // Now offset, invoking the API 14+ implementation (which contains its own workarounds)
            compatOffsetTopAndBottom(view, offset);

            // The view has now been offset, so let's intersect the Rect and invalidate where
            // the View is now displayed
            if (needInvalidateWorkaround && parentRect.intersect(view.getLeft(), view.getTop(),
                    view.getRight(), view.getBottom())) {
                ((View) parent).invalidate(parentRect);
            }
        } else {
            compatOffsetTopAndBottom(view, offset);
        }
    }

    private static void compatOffsetTopAndBottom(View view, int offset) {
        view.offsetTopAndBottom(offset);
        if (view.getVisibility() == View.VISIBLE) {
            tickleInvalidationFlag(view);

            ViewParent parent = view.getParent();
            if (parent instanceof View) {
                tickleInvalidationFlag((View) parent);
            }
        }
    }

    /**
     * Offset this view's horizontal location by the specified amount of pixels.
     *
     * @param view view
     * @param offset the number of pixels to offset the view by
     */
    public static void offsetLeftAndRight(View view, int offset) {
        if (view == null || offset == 0) {
            return;
        }
        if (Build.VERSION.SDK_INT >= 23) {
            view.offsetLeftAndRight(offset);
        } else if (Build.VERSION.SDK_INT >= 21) {
            final Rect parentRect = getEmptyTempRect();
            boolean needInvalidateWorkaround = false;

            final ViewParent parent = view.getParent();
            if (parent instanceof View) {
                final View p = (View) parent;
                parentRect.set(p.getLeft(), p.getTop(), p.getRight(), p.getBottom());
                // If the view currently does not currently intersect the parent (and is therefore
                // not displayed) we may need need to invalidate
                needInvalidateWorkaround = !parentRect.intersects(view.getLeft(), view.getTop(),
                        view.getRight(), view.getBottom());
            }

            // Now offset, invoking the API 14+ implementation (which contains its own workarounds)
            compatOffsetLeftAndRight(view, offset);

            // The view has now been offset, so let's intersect the Rect and invalidate where
            // the View is now displayed
            if (needInvalidateWorkaround && parentRect.intersect(view.getLeft(), view.getTop(),
                    view.getRight(), view.getBottom())) {
                ((View) parent).invalidate(parentRect);
            }
        } else {
            compatOffsetLeftAndRight(view, offset);
        }
    }

    private static void compatOffsetLeftAndRight(View view, int offset) {
        view.offsetLeftAndRight(offset);
        if (view.getVisibility() == View.VISIBLE) {
            tickleInvalidationFlag(view);

            ViewParent parent = view.getParent();
            if (parent instanceof View) {
                tickleInvalidationFlag((View) parent);
            }
        }
    }

    private static void tickleInvalidationFlag(View view) {
        final float y = view.getTranslationY();
        view.setTranslationY(y + 1);
        view.setTranslationY(y);
    }


    /**
     * <p>Convert script specific gravity to absolute horizontal value.</p>
     *
     * if horizontal direction is LTR, then START will set LEFT and END will set RIGHT.
     * if horizontal direction is RTL, then START will set RIGHT and END will set LEFT.
     *
     *
     * @param gravity The gravity to convert to absolute (horizontal) values.
     * @param layoutDirection The layout direction.
     * @return gravity converted to absolute (horizontal) values.
     */
    public static int getAbsoluteGravity(int gravity, int layoutDirection) {
        if (SDK_INT >= 17) {
            return Gravity.getAbsoluteGravity(gravity, layoutDirection);
        } else {
            // Just strip off the relative bit to get LEFT/RIGHT.
            return gravity & ~Gravity.RELATIVE_LAYOUT_DIRECTION;
        }
    }
    public static int getLayoutDirection(View view) {
        if (Build.VERSION.SDK_INT >= 17) {
            return view.getLayoutDirection();
        }
        return View.LAYOUT_DIRECTION_LTR;
    }


    /**
     * Check if the items in the list can be scrolled in a certain direction.
     *
     * @param listView listView
     * @param direction Negative to check scrolling up, positive to check
     *            scrolling down.
     * @return true if the list can be scrolled in the specified direction,
     *         false otherwise.
     */
    public static boolean canListViewScrollVertical(AbsListView listView, int direction) {
        if (Build.VERSION.SDK_INT >= 19) {
            // Call the framework version directly
            return listView.canScrollList(direction);
        } else {
            // provide backport on earlier versions
            final int childCount = listView.getChildCount();
            if (childCount == 0) {
                return false;
            }

            final int firstPosition = listView.getFirstVisiblePosition();
            if (direction > 0) {
                final int lastBottom = listView.getChildAt(childCount - 1).getBottom();
                final int lastPosition = firstPosition + childCount;
                return lastPosition < listView.getCount()
                        || (lastBottom > listView.getHeight() - listView.getListPaddingBottom());
            } else {
                final int firstTop = listView.getChildAt(0).getTop();
                return firstPosition > 0 || firstTop < listView.getListPaddingTop();
            }
        }
    }
}

ScrimView.java

/**
 * @author billy.qi
 */
public class ScrimView extends View {
    public static float MAX_PROGRESS = 1F;
    public static float MIN_PROGRESS = 0F;

    private int mSize = 60;

    private final Paint mPaint;

    private Rect mBounds = new Rect();
    private int mScrimColor;
    private int mBaseAlpha;
    private int mDirection;
    private Paint mShadowPaint;
    private Rect mShadowRect = new Rect();
    private int mShadowColor = 0x80000000;
    private int mShadowDirection;

    public ScrimView(Context context) {
        super(context);
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        mPaint.setStyle(Paint.Style.FILL);
        mShadowPaint = new Paint();
        mShadowPaint.setDither(true);
        mShadowPaint.setAntiAlias(true);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        this.mBounds.right = w;
        this.mBounds.bottom = h;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        if (mScrimColor != 0) {
            canvas.drawRect(mBounds, mPaint);
        }
        if (mSize > 0 && mShadowColor != 0 && (mDirection & SwipeConsumer.DIRECTION_ALL) > 0) {
            canvas.save();
            switch (mShadowDirection) {
                default: break;
                case SwipeConsumer.DIRECTION_RIGHT:   canvas.translate(mBounds.right - mSize, 0); break;
                case SwipeConsumer.DIRECTION_BOTTOM:  canvas.translate(0, mBounds.bottom - mSize); break;
            }
            canvas.clipRect(mShadowRect);
            canvas.drawPaint(mShadowPaint);
            canvas.restore();
        }
    }

    public void setProgress(float progress) {
        float mProgress = SmartSwipe.ensureBetween(progress, MIN_PROGRESS, MAX_PROGRESS);
        final int alpha = (int) (mBaseAlpha * mProgress);
        final int color = alpha << 24 | (mScrimColor & 0xFFFFFF);
        mPaint.setColor(color);
    }

    public void setScrimColor(int scrimColor) {
        this.mScrimColor = scrimColor;
        mBaseAlpha = (mScrimColor & 0xFF000000) >>> 24;
    }

    public void setDirection(int direction, int shadowColor, int shadowDirection, int shadowSize, int parentWidth, int parentHeight) {
        this.mDirection = direction;
        this.mShadowColor = shadowColor;
        this.mShadowDirection = shadowDirection;
        this.mSize = shadowSize;
        if (mShadowColor == 0) {
            return;
        }
        int l = 0, t = 0, r, b;
        switch (mShadowDirection) {
            default: return;
            case SwipeConsumer.DIRECTION_LEFT:    case SwipeConsumer.DIRECTION_RIGHT:   r = mSize; t = b = parentHeight; break;
            case SwipeConsumer.DIRECTION_TOP:     case SwipeConsumer.DIRECTION_BOTTOM:  r = parentWidth; b = mSize; break;
        }
        mShadowRect.right = r;
        mShadowRect.bottom = b;
        int alpha = (mShadowColor & 0xFF000000) >>> 24;
        int steps = 30;
        float[] positions = new float[steps + 1];
        int[] colors = new int[steps + 1];
        boolean revert = mShadowDirection == SwipeConsumer.DIRECTION_LEFT || mShadowDirection == SwipeConsumer.DIRECTION_TOP;
        for (int i = 0; i <= steps; i++) {
            positions[i] = i * 1F / steps;
        }
        float position;
        for (int i = 0; i <= steps; i++) {
            position = positions[revert ? steps - i : i];
            colors[i] = ((int) (alpha * position * position) << 24) | (mShadowColor & 0xFFFFFF);
        }

        boolean horizontal = direction == SwipeConsumer.DIRECTION_LEFT || direction == SwipeConsumer.DIRECTION_RIGHT;
        if (horizontal) {
            t = b = b >> 1;
        } else {
            l = r = r >> 1;
        }

        LinearGradient shader = new LinearGradient(l, t, r, b, colors, positions, LinearGradient.TileMode.CLAMP);
        mShadowPaint.setShader(shader);
    }

    public int getShadowColor() {
        return mShadowColor;
    }
}
(4)抽屉总结:

SmartSwipe框架的DrawerConsumerSlidingConsumer可以当成工具使用,当项目中需要使用抽屉布局时使用,它们的区别在效果图中可以看到,不再说明。

(5)抽屉效果补充:沉浸式状态栏

使用抽屉布局的话,往往需要做好沉浸式状态栏效果,以下简单贴一下实现代码,但不能作为沉浸式状态栏教程方案,如果想了解更多,请查看其它资料。

新增以下两个文件,如图:


图片.png

values-v19-->styles.xml

图片.png

values-v21-->styles.xml

图片.png
(6)绑定在某View或者ViewGroup上

另外,以下代码,是将Consumer绑定在Activity上

    SmartSwipe.wrap(this).addConsumer(mSlidingConsumer); 

我们也可以将Consumer绑定在某个View上,先来看一下效果:

273.gif

代码如下:

    View menu_1 = LayoutInflater.from(this).inflate(R.layout.layout_main_menu, null);
    menu_1.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT));
    SmartSwipeWrapper menu1Wrapper = SmartSwipe.wrap(menu_1);

    View menu_2 = LayoutInflater.from(this).inflate(R.layout.layout_main_menu, null);
    menu_2.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
    SmartSwipeWrapper menu2Wrapper = SmartSwipe.wrap(menu_2);
    mSlidingConsumer = new SlidingConsumer()
            //横向布局的抽屉
            .setHorizontalDrawerView(menu1Wrapper)
            .setVerticalDrawerView(menu2Wrapper)
            //仅显示左边抽屉
            //.setLeftDrawerView(menuWrapper)
            //仅显示右边抽屉
            //.setRightDrawerView(menuWrapper)
            //仅显示上边抽屉
            //.setTopDrawerView(menuWrapper)
            //仅显示下边抽屉
            //.setBottomDrawerView(menuWrapper)
            //设置遮罩层背景颜色,默认透明
            //.setScrimColor(0x80A7BBBB)
            //设置边框阴影颜色,默认透明
            //.setShadowColor(0x80CAA639)
            //设置边框阴影大小,默认为0
            //.setShadowSize(SmartSwipe.dp2px(20, this))
            //设置监听
            .addListener(listener)
            //设置边的大小,手指可以在某个方位的20dp范围内触发侧滑事件,默认值为View的宽度或高度
            //.setEdgeSize(SmartSwipe.dp2px(20, this))
            //将SwipeConsumer类型转换为DrawerConsumer类型
            .as(SlidingConsumer.class);
       SmartSwipe.wrap(findViewById(R.id.ll_swip)).addConsumer(mSlidingConsumer);

ID为ll_swip的布局:

图片.png

layout_main_menu.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#E0608C">

    <ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@mipmap/ic_launcher_round"
        android:padding="10dp"
        android:layout_centerInParent="true" />

</RelativeLayout>

如果将SlidingConsumer改成DrawerConsumer,效果如下:

274.gif
(7)布局实现

除了用代码实现,我们还可以直接使用布局实现,代码如下:

    SmartSwipeWrapper wrapper = findViewById(R.id.main_ui_wrap_view);
    wrapper.addConsumer(new SlidingConsumer()).setRelativeMoveFactor(SlidingConsumer.FACTOR_FOLLOW);
<org.bytedeco.xxx.smartswipedemo.swipe.SmartSwipeWrapper
    android:id="@+id/main_ui_wrap_view"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_centerInParent="true"
    android:background="#80CAA639">

    <RelativeLayout
        android:orientation="vertical"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        app:swipe_gravity="left|right"
        android:background="#E0608C">

        <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@mipmap/ic_launcher_round"
            android:padding="10dp"
            android:layout_centerInParent="true" />

    </RelativeLayout>
    <TextView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:textSize="26sp"
        android:gravity="center"
        android:text="侧滑数据项"/>
    <RelativeLayout
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:swipe_gravity="top|bottom"
        android:background="#E0608C">

        <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@mipmap/ic_launcher_round"
            android:padding="10dp"
            android:layout_centerInParent="true" />

    </RelativeLayout>
</org.bytedeco.xxx.smartswipedemo.swipe.SmartSwipeWrapper>
(8)SlidingConsumer之关联移动因子

如下代码:

    SmartSwipeWrapper wrapper = findViewById(R.id.main_ui_wrap_view);
    wrapper.addConsumer(new SlidingConsumer()).setRelativeMoveFactor(0.5f);

当使用SlidingConsumer时,setRelativeMoveFactor可以设置关联移动因子,它的取值范围是[0,1],默认情况下,移动因子是0.5,下面分别将移动因子设置成0,0.5,1来演示:

276.gif 275.gif 277.gif

[本章完...]

上一篇下一篇

猜你喜欢

热点阅读