(六)仿QQ首頁drawer/側滑刪除/浮動imgaeView/角標拖拽

weixin_33785972發表於2018-11-23

效果圖如下:


6854827-7313cb97db297f52.gif
demo6.gif

1.首頁左側drawerLaout
借鑑 https://github.com/qiantao94/CoordinatorMenu
小作修改:
因為該庫不支援自定義側邊欄的寬度,我這邊增加了一個屬性drawerPercent,是指側邊欄佔據手機螢幕的百分比。

 <com.dl.common.widget.drawerlayout.DrawerMenu
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:id="@+id/menu"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:drawerPercent="0.8">

        <include layout="@layout/demo6_layout_drawer_view" />

        <include layout="@layout/demo6_layout_main_view" />


    </com.dl.common.widget.drawerlayout.DrawerMenu>

2.item側滑刪除
借鑑 https://github.com/mcxtzhang/SwipeDelMenuLayout
注意:如果側滑刪除要和角標拖拽一起使用,直接依賴該庫會出現滑動衝突。需要做如下修改:

 @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        LogUtils.d(TAG, "dispatchTouchEvent() called with: " + "ev = [" + ev + "]"+isSwipeEnable);
        if (isSwipeEnable) {
            acquireVelocityTracker(ev);
            final VelocityTracker verTracker = mVelocityTracker;
            switch (ev.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    isUserSwiped = false;//2016 11 03 add,判斷手指起始落點,如果距離屬於滑動了,就遮蔽一切點選事件。
                    isUnMoved = true;//2016 10 22 add , 仿QQ,側滑選單展開時,點選內容區域,關閉側滑選單。
                    iosInterceptFlag = false;//add by 2016 09 11 ,每次DOWN時,預設是不攔截的
                    if (isTouching) {//如果有別的指頭摸過了,那麼就return false。這樣後續的move..等事件也不會再來找這個View了。
                        return false;
                    } else {
                        isTouching = true;//第一個摸的指頭,趕緊改變標誌,宣誓主權。
                    }

                    mLastP.set(ev.getRawX(), ev.getRawY());
                    mFirstP.set(ev.getRawX(), ev.getRawY());//2016 11 03 add,判斷手指起始落點,如果距離屬於滑動了,就遮蔽一切點選事件。

                    //如果down,view和cacheview不一樣,則立馬讓它還原。且把它置為null
                    if (mViewCache != null) {
                        LogUtil.d("4");
                        if (mViewCache != this) {
                            mViewCache.smoothClose();

                            iosInterceptFlag = isIos;//add by 2016 09 11 ,IOS模式開啟的話,且當前有側滑選單的View,且不是自己的,就該攔截事件咯。
                        }

                        //只要有一個側滑選單處於開啟狀態, 就不給外層佈局上下滑動了
                        getParent().requestDisallowInterceptTouchEvent(true);
                    }

                    //求第一個觸點的id, 此時可能有多個觸點,但至少一個,計算滑動速率用
                    mPointerId = ev.getPointerId(0);

                    break;
                case MotionEvent.ACTION_MOVE:
                    //add by 2016 09 11 ,IOS模式開啟的話,且當前有側滑選單的View,且不是自己的,就該攔截事件咯。滑動也不該出現
                    if (iosInterceptFlag) {
                        break;
                    }
                    float gap = mLastP.x - ev.getRawX();
                    //為了在水平滑動中禁止父類ListView等再豎直滑動
                    if (Math.abs(gap) > 10 || Math.abs(getScrollX()) > 10) {//2016 09 29 修改此處,使遮蔽父佈局滑動更加靈敏,
                        getParent().requestDisallowInterceptTouchEvent(true);
                    }
                    //2016 10 22 add , 仿QQ,側滑選單展開時,點選內容區域,關閉側滑選單。begin
                    if (Math.abs(gap) > mScaleTouchSlop) {
                        isUnMoved = false;
                    }
                    //2016 10 22 add , 仿QQ,側滑選單展開時,點選內容區域,關閉側滑選單。end
                    //如果scroller還沒有滑動結束 停止滑動動畫
/*                    if (!mScroller.isFinished()) {
                        mScroller.abortAnimation();
                    }*/
                    scrollBy((int) (gap), 0);//滑動使用scrollBy
                    //越界修正
                    if (isLeftSwipe) {//左滑
                        if (getScrollX() < 0) {
                            scrollTo(0, 0);
                        }
                        if (getScrollX() > mRightMenuWidths) {
                            scrollTo(mRightMenuWidths, 0);
                        }
                    } else {//右滑
                        if (getScrollX() < -mRightMenuWidths) {
                            scrollTo(-mRightMenuWidths, 0);
                        }
                        if (getScrollX() > 0) {
                            scrollTo(0, 0);
                        }
                    }

                    mLastP.set(ev.getRawX(), ev.getRawY());
                    break;
                case MotionEvent.ACTION_UP:
                case MotionEvent.ACTION_CANCEL:
                    //2016 11 03 add,判斷手指起始落點,如果距離屬於滑動了,就遮蔽一切點選事件。
                    if (Math.abs(ev.getRawX() - mFirstP.x) > mScaleTouchSlop) {
                        isUserSwiped = true;
                    }

                    //add by 2016 09 11 ,IOS模式開啟的話,且當前有側滑選單的View,且不是自己的,就該攔截事件咯。滑動也不該出現
                    if (!iosInterceptFlag) {//且滑動了 才判斷是否要收起、展開menu
                        //求偽瞬時速度
                        verTracker.computeCurrentVelocity(1000, mMaxVelocity);
                        final float velocityX = verTracker.getXVelocity(mPointerId);
                        if (Math.abs(velocityX) > 1000) {//滑動速度超過閾值
                            if (velocityX < -1000) {
                                if (isLeftSwipe) {//左滑
                                    //平滑展開Menu
                                    smoothExpand();

                                } else {
                                    //平滑關閉Menu
                                    smoothClose();
                                }
                            } else {
                                if (isLeftSwipe) {//左滑
                                    // 平滑關閉Menu
                                    smoothClose();
                                } else {
                                    //平滑展開Menu
                                    smoothExpand();

                                }
                            }
                        } else {
                            if (Math.abs(getScrollX()) > mLimit) {//否則就判斷滑動距離
                                //平滑展開Menu
                                smoothExpand();
                            } else {
                                // 平滑關閉Menu
                                smoothClose();
                            }
                        }
                    }
       
                    releaseVelocityTracker();
                    isTouching = false;
                    break;
                default:
                    break;
            }
        } else {
            isTouching=false; //此處是處理衝突的位置,必須加
        }
        return super.dispatchTouchEvent(ev);
    }

3.訊息角標自由拖拽
借鑑:https://github.com/qstumn/BadgeView
如果開啟拖拽和滑動刪除一起使用,上面衝突要處理,下面程式碼也要寫

       badge.setOnDragStateChangedListener((dragState, badge1, targetView) -> {
      //拖拽成功和拖拽取消後開啟側滑  其他狀態關閉側滑  
  //必須呼叫smoothClose()
            if (dragState == Badge.OnDragStateChangedListener.STATE_SUCCEED) {
                ((SwipeMenuLayout) helper.getRecyclerViewHolder().itemView).smoothClose();
                ((SwipeMenuLayout) helper.getRecyclerViewHolder().itemView).setIos(true).setSwipeEnable(true);
            } else if (dragState == Badge.OnDragStateChangedListener.STATE_CANCELED) {
                ((SwipeMenuLayout) helper.getRecyclerViewHolder().itemView).smoothClose();
                ((SwipeMenuLayout) helper.getRecyclerViewHolder().itemView).setIos(true).setSwipeEnable(true);

            } else {
                ((SwipeMenuLayout) helper.getRecyclerViewHolder().itemView).smoothClose();
                ((SwipeMenuLayout) helper.getRecyclerViewHolder().itemView).setIos(false).setSwipeEnable(false);
            }
        });
        badge.setBadgeNumber(model.getCount());

4.可浮動的ImageView

@SuppressLint("AppCompatCustomView")
public class MoveImageView extends ImageView {

    private Drawable mDrawable;
    private int mLeft = 0;
    private int mTop = 0;
    private int mSpeed = 2;
    private boolean isSetVerticalMove;
    private boolean isMoveLeft;
    private boolean isMoveUp;
    private Handler mHandler;
    private int mCanvasBgSize;



    public MoveImageView(Context context) {
        super(context);
    }

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

    public MoveImageView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        setUp(context, attrs);
    }

    private void setUp(Context context, AttributeSet attrs) {
        TypedArray a =context.obtainStyledAttributes(attrs, R.styleable.MoveImage);
        int direction=a.getInteger(R.styleable.MoveImage_direction,0);
        mSpeed=a.getInteger(R.styleable.MoveImage_speed,2);

        if (direction == 0) {
            isSetVerticalMove = true;
        } else {
            isSetVerticalMove = false;
        }

        mDrawable = getDrawable();
        mHandler = new MoveHandler();
        mHandler.sendEmptyMessageDelayed(1, 220L);

    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (isSetVerticalMove) {
            canvas.translate(0.0F, mTop);
        } else {
            canvas.translate(mLeft, 0.0F);
        }
        mDrawable.draw(canvas);
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);

        if (isSetVerticalMove) {
            mCanvasBgSize = getMeasuredHeight() * 3 / 2;
            mDrawable.setBounds(0, 0, getMeasuredWidth(), mCanvasBgSize);
        } else {
            mCanvasBgSize = getMeasuredWidth() * 3 / 2;
            mDrawable.setBounds(0, 0, mCanvasBgSize, getMaxHeight());
        }
    }

    private class MoveHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            if (isSetVerticalMove) {
                if (isMoveUp) {
                    if (mTop <= getMeasuredHeight() - mCanvasBgSize)//此時表示移到了最up的位置
                    {
                        mTop += mSpeed;
                        isMoveUp = false;
                    } else//繼續下移
                    {
                        mTop -= mSpeed;
                    }
                } else {
                    if (mTop == 0)//此時表示移動到了最down,此時圖片的up側應該與螢幕up側對齊,即座標值為0
                    {
                        mTop -= mSpeed;
                        isMoveUp = true;//圖片已經移動到了最down側,需要修改其移動方向為up
                    } else {

                        mTop += mSpeed;//繼續下移
                    }
                }
            } else {
                if (isMoveLeft)//向左移動
                {

                    if (mLeft <= getMeasuredWidth() - mCanvasBgSize)//此時表示移到了最左側的位置
                    {
                        mLeft += mSpeed;
                        isMoveLeft = false;
                    } else//繼續左移
                    {
                        mLeft -= mSpeed;
                    }

                } else {
                    if (mLeft == 0)//此時表示移動到了最右側,此時圖片的左側應該與螢幕左側對齊,即座標值為0
                    {
                        mLeft -= mSpeed;
                        isMoveLeft = true;//圖片已經移動到了最右側,需要修改其移動方向為向左
                    } else {
                        mLeft += mSpeed;//繼續右移
                    }
                }
            }
            invalidate();
            mHandler.sendEmptyMessageDelayed(1, 22);
        }
    }
}

5.討論組頭像展示
借鑑 https://github.com/jinyb09017/MutiImgLoader

6.仿IOS彈性scrollview

/**
 * created by dalang at 2018/11/21
 * 仿IOS 彈性scrollview
 */
public class ReboundScrollView extends NestedScrollView {

    //移動因子, 是一個百分比, 比如手指移動了100px, 那麼View就只移動50px
    //目的是達到一個延遲的效果
    private static final float MOVE_FACTOR = 0.3f;

    //鬆開手指後, 介面回到正常位置需要的動畫時間
    private static final int ANIM_TIME = 300;

    //ScrollView的子View, 也是ScrollView的唯一一個子View
    private View contentView;

    //手指按下時的Y值, 用於在移動時計算移動距離
    //如果按下時不能上拉和下拉, 會在手指移動時更新為當前手指的Y值
    private float startY;

    //用於記錄正常的佈局位置
    private Rect originalRect = new Rect();

    //手指按下時記錄是否可以繼續下拉
    private boolean canPullDown = false;

    //手指按下時記錄是否可以繼續上拉
    private boolean canPullUp = false;

    //在手指滑動的過程中記錄是否移動了佈局
    private boolean isMoved = false;


    public ReboundScrollView(Context context) {
        super(context);
    }


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

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        if (getChildCount() > 0) {
            contentView = getChildAt(0);
        }
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);

        if(contentView == null) return;

        //ScrollView中的唯一子控制元件的位置資訊, 這個位置資訊在整個控制元件的生命週期中保持不變
        originalRect.set(contentView.getLeft(), contentView.getTop(), contentView
                .getRight(), contentView.getBottom());
    }

    /**
     * 在觸控事件中, 處理上拉和下拉的邏輯
     */
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {

        if (contentView == null) {
            return super.dispatchTouchEvent(ev);
        }

        int action = ev.getAction();

        switch (action) {
            case MotionEvent.ACTION_DOWN:

                //判斷是否可以上拉和下拉
                canPullDown = isCanPullDown();
                canPullUp = isCanPullUp();

                //記錄按下時的Y值
                startY = ev.getY();
                break;

            case MotionEvent.ACTION_UP:

                if(!isMoved) break;  //如果沒有移動佈局, 則跳過執行

                // 開啟動畫
                TranslateAnimation anim = new TranslateAnimation(0, 0, contentView.getTop(),
                        originalRect.top);
                anim.setDuration(ANIM_TIME);

                contentView.startAnimation(anim);

                // 設定回到正常的佈局位置
                contentView.layout(originalRect.left, originalRect.top,
                        originalRect.right, originalRect.bottom);

                //將標誌位設回false
                canPullDown = false;
                canPullUp = false;
                isMoved = false;

                break;
            case MotionEvent.ACTION_MOVE:

                //在移動的過程中, 既沒有滾動到可以上拉的程度, 也沒有滾動到可以下拉的程度
                if(!canPullDown && !canPullUp) {
                    startY = ev.getY();
                    canPullDown = isCanPullDown();
                    canPullUp = isCanPullUp();

                    break;
                }

                //計算手指移動的距離
                float nowY = ev.getY();
                int deltaY = (int) (nowY - startY);

                //是否應該移動佈局
                boolean shouldMove =
                        (canPullDown && deltaY > 0)    //可以下拉, 並且手指向下移動
                                || (canPullUp && deltaY< 0)    //可以上拉, 並且手指向上移動
                                || (canPullUp && canPullDown); //既可以上拉也可以下拉
                            (這種情況出現在ScrollView包裹的控制元件比ScrollView還小)

                if(shouldMove){
                    //計算偏移量
                    int offset = (int)(deltaY * MOVE_FACTOR);

                    //隨著手指的移動而移動佈局
                    contentView.layout(originalRect.left, originalRect.top + offset,
                            originalRect.right, originalRect.bottom + offset);

                    isMoved = true;  //記錄移動了佈局
                }

                break;
            default:
                break;
        }

        return super.dispatchTouchEvent(ev);
    }


    /**
     * 判斷是否滾動到頂部
     */
    private boolean isCanPullDown() {
        return getScrollY() == 0 ||
                contentView.getHeight() < getHeight() + getScrollY();
    }

    /**
     * 判斷是否滾動到底部
     */
    private boolean isCanPullUp() {
        return  contentView.getHeight() <= getHeight() + getScrollY();
    }
}

其他文章連結地址:
(一)高斯模糊實現毛玻璃效果丶共享元素動畫 丶地址選擇器
(二)仿京東頂部伸縮漸變丶自定義viewpager指示器丶viewpager3D迴廊丶recyclerview瀑布流
(三)RxJava2常用操作符merge、flatmap、zip--結合MVP架構講解
(四)仿支付寶首頁頂部伸縮滑動/中間層下拉重新整理
(五)TabLayout+ViewPager懸浮吸頂及重新整理數量動畫顯示
(七)仿微信釋出朋友圈拖拽刪除

將持續更新.. 不喜勿噴,僅個人分享,希望能幫助到你

原始碼地址:Github傳送門

相關文章