2020-06-22

自定義側(cè)滑控件 SlideSlipView

功能介紹

本控件是模仿QQ的消息側(cè)滑功能來開發(fā)的,實(shí)現(xiàn)的效果基本跟QQ的側(cè)滑效果一致。而且本控件基本不依賴其它控件,盡量降低耦合性。同時使用起來也非常簡單,支持自定義側(cè)滑內(nèi)容,通過布局文件的方式就可以實(shí)現(xiàn)??梢宰鳛镽ecyclerView的item使用,也可以單獨(dú)添加到布局中使用。


slide_slip.gif

實(shí)現(xiàn)方案

重寫ViewGroup的onLayout方法,對每一個子view合理布局、利用View的事件分發(fā)機(jī)制判斷每一個事件到來時需要做的功能(核心模塊)、通過Scroller和VelocityTracker配合使用達(dá)到自動滑動跟慣性滑動的效果。

使用指南

雖然本控件實(shí)現(xiàn)了想要的效果,但是多少還是有一些不太完美的地方需要大家在使用時注意一下。

  1. 該組件繼承自FrameLayout,所以在使用的時候?qū)⒆觱iew包裹到該組件內(nèi)就可以了,但是子view的布局方式是按照橫向順序排布的,跟橫向的LinearLayout效果是一樣的。
  2. 為了區(qū)分子view屬于折疊內(nèi)容還是屬于非折疊內(nèi)容,每一個子view都需要添加android:tag="1"屬性, 屬性值必須是整型變量,[0-100]代表是非折疊內(nèi)容,[101-200]代表折疊內(nèi)容。
  3. 本組件(不是指子view)的寬度只支持match_parent、固定值,不支持wrap_content;高度則沒有限制。
  4. 建議大家使用的時候?qū)⒄郫B內(nèi)容跟非折疊內(nèi)容分別用ViewGroup(比如LinearLayout、RelativeLayout、ConstraintLayout等等)包裹起來再放到該控件中,一方面可以減少tag的使用,另一方面可以實(shí)現(xiàn)更復(fù)雜的布局效果。
  5. 為了解決多指觸摸RecyclerView導(dǎo)致多個item同時發(fā)生側(cè)滑的問題,在使用該組件的時候最好跟TouchRecyclerview配合使用。

注:非折疊內(nèi)容是指組件沒有發(fā)生側(cè)滑時用戶看到的view構(gòu)成的部分,折疊內(nèi)容是指組件發(fā)生側(cè)滑時用戶看到的之前隱藏起來的部分。

實(shí)現(xiàn)代碼

雖然代碼很多,但是核心功能代碼都在onInterceptTouchEvent、onTouchEvent方法中,其它的都是一些工具方法,基本上不用太關(guān)心,代碼中都添加了詳細(xì)的注釋,就不再這里繼續(xù)分析代碼了。另外TouchRecyclerview代碼很簡單就不在這里展示了,最后會貼出源碼地址,歡迎大家去git上start!

/**
 * CZL 自定義View模仿qq側(cè)滑刪除
 */
public class SlideSlipView extends FrameLayout {

    /**
     * 為了更好地分析代碼中的邏輯,下面一些概念在此聲明:
     * 折疊狀態(tài):置頂、標(biāo)記為未讀、刪除這部分隱藏起來的時候
     * 非折疊狀態(tài):置頂、標(biāo)記為未讀、刪除這部分顯示出來的時候
     * 原始內(nèi)容:折疊狀態(tài)下item可見內(nèi)容部分
     * 隱藏內(nèi)容:置頂、標(biāo)記為未讀、刪除這部分構(gòu)成的內(nèi)容部分
     */

    private final String TAG = SlideSlipView.this.getClass().getSimpleName();
    private Scroller scroller;//輔助滾動工具
    private int mTouchSlop;
    private final float VELOCITY_SLOP = 600;//慣性滑動最小速度值
    private float mLastX;//記錄上次觸摸點(diǎn)x坐標(biāo)
    private float mLastY;//記錄上次觸摸點(diǎn)y坐標(biāo)
    //特殊觸摸標(biāo)記,含義:當(dāng)前被點(diǎn)擊的item之外是否還有其它item是展開的,true:是   false:否
    // 當(dāng)為true的時候會進(jìn)行‘?dāng)r截一切’的舉動,也就是對事件只攔截但是什么滑動都不處理(這參考了qq的實(shí)現(xiàn)方式)
    private boolean specialTouch = false;
    //特殊觸摸標(biāo)記,含義:是否需要將當(dāng)前view變成‘折疊狀態(tài)’,true:是   false:否
    //當(dāng)前view為'非折疊狀態(tài)'時,手指點(diǎn)擊‘原始內(nèi)容’區(qū)域并立即抬起后將自動折疊view
    private boolean specialTouch2 = false;
    private boolean fold = true;//折疊標(biāo)記,判斷當(dāng)前view是否是折疊狀態(tài),true:是  false:否
    private VelocityTracker velocityTracker;//速度模擬類
    private RecyclerView recyclerView;//如果在RV中使用當(dāng)前view,需要處理-滑動沖突、多個item同時展開的問題,這會用到RV

    public SlideSlipView(Context context) {
        this(context, (AttributeSet) null);
    }

    public SlideSlipView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public SlideSlipView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        scroller = new Scroller(context);
        ViewConfiguration configuration = ViewConfiguration.get(context);
        mTouchSlop = configuration.getScaledTouchSlop();
        setClipToPadding(false);//設(shè)置該屬性后,該view設(shè)置的padding部分可以隨內(nèi)容一起滑動
    }

    /**
     * 重寫布局方法,支持子view設(shè)置margin、padding等屬性
     * 子view必須設(shè)置tag屬性,否者直接拋出異常
     * 可見子view設(shè)置的tag取值范圍0-100;隱藏子view設(shè)置tag取值范圍100-Integer最大值。
     * 這里的子view是指直接子view,可見子view是指正常情況下可以看見的子view,隱藏子view是指正常情況下不可見的子view,當(dāng)滑動以后才能看到的view
     */
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        final int childCount = getChildCount();
        final int mPaddingLeft = Math.max(0, getPaddingLeft());
        final int mPaddingRight = Math.max(0, getPaddingRight());
        final int mPaddingTop = Math.max(0, getPaddingTop());
        int widthSum = mPaddingLeft;//可見內(nèi)容部分初始左邊界位置
        int widthSum2 = getMeasuredWidth();//隱藏內(nèi)容部分初始左邊界位置
        int heightSum = 0;
        for (int i = 0; i < childCount; i++) {
            View childView = getChildAt(i);
            int tag;
            try {
                tag = Integer.valueOf((String) childView.getTag());//這里設(shè)計通過tag區(qū)分可見子view跟隱藏子view,所以使用該控件必需設(shè)置子view的tag
            } catch (Exception e) {
                throw new IllegalArgumentException("SlideSlipView的子View需要設(shè)置tag,顯示View的tag 0-100,隱藏View的tag 101-Integer.MAX_VALUE");
            }

            heightSum = mPaddingTop + getMargin(2, childView);
            if (tag <= 100 && tag >= 0) {//可見子view,tag 0-100
                widthSum += getMargin(0, childView);//獲取childView左邊界的mleft位置
                if (widthSum >= (getMeasuredWidth() - mPaddingRight)) {//當(dāng)可見子View累計寬度大于可用寬度時,不再顯示剩余‘可見子view’
                    continue;
                }
                int rightPosition = Math.min(getMeasuredWidth() - mPaddingRight, widthSum + childView.getMeasuredWidth());//當(dāng)子view的右邊界大于
                childView.layout(widthSum, heightSum, rightPosition, heightSum + childView.getMeasuredHeight());
                widthSum += childView.getMeasuredWidth();//計算子view的橫向位置
                widthSum += getMargin(1, childView);//計算子view的橫向位置
            } else if (tag > 100) {//隱藏子view, tag 100-Integer.MAX_VALUE
                Log.e(TAG, "onLayout: currentViewWidth=" + getMeasuredWidth() + "\tchildWidth=" + childView.getMeasuredWidth() + "\ttag=" + tag);
                widthSum2 += getMargin(0, childView);//隱藏子view有多少就布局多少個
                childView.layout(widthSum2, heightSum, widthSum2 + childView.getMeasuredWidth(), heightSum + childView.getMeasuredHeight());
                widthSum2 += childView.getMeasuredWidth();//計算子view的橫向位置
                widthSum2 += getMargin(1, childView);//計算子view的橫向位置
            }
        }
    }

    /**
     * 側(cè)滑關(guān)閉策略:
     * 1、點(diǎn)擊的item是‘展開’狀態(tài),則將item折疊(包括當(dāng)前item及可能存在的其它item,因?yàn)镽V如果多個手指同時滑動多個item時,都會進(jìn)行側(cè)滑,這跟qq不一樣,是個小bug)
     * 具體實(shí)現(xiàn)方案:在onInterceptTouchEvent方法的ACTION_UP事件,并更新狀態(tài),同時攔截該事件(不允許調(diào)用item的onClick方法)
     * 2、被點(diǎn)擊的item是‘折疊’狀態(tài),如果其它item有‘展開’則‘折疊’其它item(在actiondown的時候就),如果其他item都是‘折疊’則正常處理點(diǎn)擊事件
     */
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        boolean intercepted;
        float currentX = ev.getX();
        float currentY = ev.getY();
        switch (ev.getActionMasked()) {
            case MotionEvent.ACTION_DOWN: {
                if (existExpandChildren()) {//這行代碼作用:當(dāng)有其它item展開的時候,點(diǎn)擊當(dāng)前item關(guān)閉其它展開的item,并將該事件停止下傳
//                    Log.e(TAG, "---------------------------onInterceptTouchEvent: 特殊事件ACTION_DOWN");
                    if (getRecyclerView() != null) {//xz
                        getRecyclerView().requestDisallowInterceptTouchEvent(true);//這個方法一旦調(diào)用,除非再調(diào)用一次,否則該view永遠(yuǎn)無法攔截事件
                    }
                    specialTouch = true;//特殊事件標(biāo)記,當(dāng)是true的時候攔截所有的事件,但是不對事件做處理(即rv不進(jìn)行滾動、slideview不進(jìn)行任何滑動響應(yīng))
                }
                if (!isFold()) {//如果當(dāng)前item是‘展開’狀態(tài)
                    specialTouch = false;//如果當(dāng)前item也是‘非折疊’狀態(tài)的話,停止‘?dāng)r截一切’的舉動
                    if (needFold(ev)) {
                        specialTouch2 = true;
                        intercepted = true;
                        break;
                    }
                }
                if (specialTouch) {//把特殊事件標(biāo)記放這里是為了讓第二個標(biāo)記的判斷也能走一遍
                    intercepted = true;
                    break;
                }
                //當(dāng)既沒有其它item展開,點(diǎn)擊的也不是‘原始內(nèi)容’時,那么將觸摸事件分發(fā)給slideslipview的子view
                mLastX = currentX;
                mLastY = currentY;
                intercepted = false;
                if (getRecyclerView() != null) {//xz
                    getRecyclerView().requestDisallowInterceptTouchEvent(true);
                }
            }
            break;
            case MotionEvent.ACTION_MOVE:
                if (specialTouch) {//特殊事件、特殊處理
//                    Log.e(TAG, "---------------------------onInterceptTouchEvent: 特殊事件ACTION_MOVE");
                    intercepted = true;
                    break;
                }
                //當(dāng)最開始點(diǎn)擊的是‘隱藏內(nèi)容’時會執(zhí)行到這里(點(diǎn)擊‘隱藏內(nèi)容’后slideview需要判斷后續(xù)的動作,如果是滑動的話那么對事件進(jìn)行攔截)
                if (Math.abs(currentX - mLastX) > Math.abs(currentY - mLastY)) {//橫向滑動
                    mLastX = currentX;
                    mLastY = currentY;
                    intercepted = true;//攔截事件
                    if (getRecyclerView() != null) {//xz
                        getRecyclerView().requestDisallowInterceptTouchEvent(true);//如果是橫向滑動的話,禁止RV攔截接下來的事件,否則RV會攔截接下來的事件,造成滑動沖突
                    }
                } else if (Math.abs(currentY - mLastY) > Math.abs(currentX - mLastX)) {//豎直滑動
                    intercepted = false;//不攔截事件
                    if (getRecyclerView() != null) {//xz
                        getRecyclerView().requestDisallowInterceptTouchEvent(false);//允許RV攔截滑動事件,RV一旦攔截事件的話接下來所有的事件都會交給RV處理
                    }
                } else {
                    intercepted = false;
                    if (getRecyclerView() != null) {//xz
                        getRecyclerView().requestDisallowInterceptTouchEvent(false);
                    }
                }
                break;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_POINTER_UP:
            case MotionEvent.ACTION_CANCEL:
                intercepted = false;//不對up事件進(jìn)行攔截,因?yàn)橐坏r截的話,那么子view的點(diǎn)擊(包括長按)功能就不管用了
                break;
            default:
                intercepted = false;
        }
//        Log.e(TAG, "onInterceptTouchEvent: intercept=" + intercepted);
        return intercepted;
    }

    /**
     * 邏輯方法
     * 這個方法用來判斷當(dāng)手指點(diǎn)擊‘非折疊狀態(tài)’下‘原始內(nèi)容’區(qū)域時是否需要自動折疊
     * true:自動折疊    false:不折疊
     */
    private boolean needFold(MotionEvent ev) {
        boolean result = false;
        for (int i = 0; i < getChildCount(); i++) {
            View childView = getChildAt(i);
            int tag = Integer.valueOf((String) childView.getTag());
            if (!(tag >= 0 && tag <= 100)) {//這里的判斷是過濾掉‘隱藏內(nèi)容’view
                continue;
            }
            Rect rect = new Rect();
            childView.getGlobalVisibleRect(rect);
//          Log.e(TAG, "onInterceptTouchEvent: x=" + ev.getX() + "\ty=" + ev.getY() + "\tleft=" + rect.left + "\ttop=" + rect.top + "\tright=" + rect.right + "\tbottom=" + rect.bottom);
            Rect rect1 = new Rect(rect.left, 0, rect.right, rect.bottom - rect.top);
            if (rect1.contains(Math.round(ev.getX()), Math.round(ev.getY()))) {//判斷點(diǎn)擊區(qū)域是否在‘原始內(nèi)容’部分內(nèi)
//              Log.e(TAG, "onInterceptTouchEvent: 點(diǎn)擊第一個子view");
                result = true;//特殊事件標(biāo)記,表示手指按下部分是否屬于‘原始內(nèi)容’,true:是   false:不是
                if (getRecyclerView() != null) {//xz
                    getRecyclerView().requestDisallowInterceptTouchEvent(true);
                }
                break;
            }
        }
        return result;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (specialTouch) {//特殊事件-是為了處理點(diǎn)擊折疊狀態(tài)的item,關(guān)閉展開狀態(tài)的item后RV不處理觸摸事件(也就是不滑動)
            boolean result;
            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    Log.e(TAG, "---------------------------onTouchEvent: 特殊事件ACTION_DOWN");
                    result = true;
                    break;
                case MotionEvent.ACTION_MOVE:
                    Log.e(TAG, "---------------------------onTouchEvent: 特殊事件ACTION_MOVE");
                    result = true;
                    break;
                case MotionEvent.ACTION_UP: {
                    Log.e(TAG, "---------------------------onTouchEvent: 特殊事件ACTION_UP");
                    specialTouch = false;
                    getRecyclerView().requestDisallowInterceptTouchEvent(false);
                    result = true;
                }
                break;
                case MotionEvent.ACTION_CANCEL: {
                    Log.e(TAG, "---------------------------onTouchEvent: 特殊事件ACTION_CANCEL");
                    specialTouch = false;
                    getRecyclerView().requestDisallowInterceptTouchEvent(false);
                    result = true;
                }
                break;
                default:
                    specialTouch = false;
                    getRecyclerView().requestDisallowInterceptTouchEvent(false);
                    result = false;
            }
            return result;
        }

        boolean result = false;//為了配合特殊事件2
        float currentX = event.getX();
        float currentY = event.getY();
        addVelocityTracker(event);
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN: {
                mLastX = currentX;
                mLastY = currentY;
                result = true;//為了配合特殊事件2
            }
            break;
            case MotionEvent.ACTION_MOVE:
                if (specialTouch2 && Math.abs(currentX - mLastX) > mTouchSlop && Math.abs(currentX - mLastX) > Math.abs(currentY - mLastY)) {
                    //這里的條件語句目的只有一個,判斷 “從用戶手指點(diǎn)擊‘原始內(nèi)容’部分到手指離開” 這是一個點(diǎn)擊事件還是滑動事件
                    //當(dāng)手指移動范圍較大的時候看成滑動事件,否者看成點(diǎn)擊事件;(當(dāng)手指按下觸摸屏幕的時候,保持不動,對用戶來說他認(rèn)為沒有移動手指所以沒有移動,
                    // 但是對系統(tǒng)來說即使再微小的移動也能捕獲,而且確實(shí)當(dāng)你手指從按下那一刻就在不停的移動,只是人很難察覺到)
                    //至于為什么要區(qū)分事件,是因?yàn)閷Σ煌录枰霾煌幚恚c(diǎn)擊事件:手指抬起時需要對展開的item折疊 ,滑動事件:手指抬起時需要對item進(jìn)行自動滑動(折疊起來還是展開)
                    //而作為區(qū)分的標(biāo)記就是specialTouch2,這個值最終會在action_up時用到,所以這里的設(shè)計當(dāng)時也是耗費(fèi)了一些時間才想到的。
                    specialTouch2 = false;//特殊事件2,一旦開始移動的話就不再算作特殊事件,也就是將特殊事件看做失效
                }
                //當(dāng)用戶手指滑動slideview的時候,讓內(nèi)容滑動起來
                if (Math.abs(currentX - mLastX) > Math.abs(currentY - mLastY)) {//注意點(diǎn):這里沒有使用mTouchSlop進(jìn)行判斷,是因?yàn)槭褂胢TouchSlop會讓滑動不流暢
                    if (currentX - mLastX > 0) {
                        //向右滑動
                        if (getScrollX() <= 0) {
                            //停止滑動,因?yàn)橐呀?jīng)滑動到邊界
                        } else {
                            scrollBy(-Math.min(getScrollX(), Math.round(currentX - mLastX)), 0);
                        }
                    } else {
                        //向左滑動
                        int childCount = getChildCount();
                        int rightBorder = getChildAt(childCount - 1).getRight();
                        Log.e(TAG, "onTouchEvent: rightBorder=" + rightBorder);
//                        if (getScrollX() >= rightBorder - getChildAt(0).getRight()) {
                        if (getScrollX() >= (rightBorder - getMeasuredWidth())) {//當(dāng)滾動距離大于‘隱藏子view’區(qū)域(間距+寬度)寬度的時候停止滑動
                            //停止滑動,因?yàn)橐呀?jīng)滑動到邊界
                        } else {//當(dāng)滾動距離小于‘隱藏子view’區(qū)域的寬度時,繼續(xù)進(jìn)行滑動(這里進(jìn)行了滑動判斷,防止手指移動距離大于內(nèi)容可滑動距離)
//                            scrollBy(Math.min(rightBorder - getChildAt(0).getRight() - getScrollX(), Math.round(mLastX - currentX)), 0);
                            scrollBy(Math.min(rightBorder - getMeasuredWidth() - getScrollX(), Math.round(mLastX - currentX)), 0);
                        }
                    }
                    mLastX = currentX;
                    mLastY = currentY;
                }
                break;
            case MotionEvent.ACTION_UP: {
                if (specialTouch2) {
                    //如果用戶點(diǎn)擊的是‘原始內(nèi)容’,折疊item
                    Log.e(TAG, "---------------------------------onTouchEvent: 特殊事件2");
                    setFold(true);
                    smoothScrollToStart();
                    if (getRecyclerView() != null) {
                        getRecyclerView().requestDisallowInterceptTouchEvent(false);
                    }
                    break;
                }
                //如果用戶滑動的item,手指離開的時候讓item自動滑動(這里面有用到慣性滑動)
                int childCount = getChildCount();
                int rightBorder = getChildAt(childCount - 1).getRight();
                float x_velocity = getXVelocity();
                if (x_velocity > VELOCITY_SLOP) {//向右滑動(慣性滑動)
                    Log.e(TAG, "---------------------------------onTouchEvent: 快速向右滑動");
                    setFold(true);
                    scroller.startScroll(getScrollX(), 0, -getScrollX(), 0, 200);
                    invalidate();//遺忘點(diǎn),這句代碼如果不加的話導(dǎo)致view不會慣性滑動
                } else if (x_velocity < -VELOCITY_SLOP) {//左滑動(慣性滑動)
                    Log.e(TAG, "---------------------------------onTouchEvent: 快速向左滑動");
                    setFold(false);
//                    scroller.startScroll(getScrollX(), 0, rightBorder - getChildAt(0).getRight() - getScrollX(), 0, 200);
                    scroller.startScroll(getScrollX(), 0, rightBorder - getMeasuredWidth() - getScrollX(), 0, 200);
                    invalidate();//遺忘點(diǎn),這句代碼如果不加的話導(dǎo)致view不會慣性滑動
                } else {//根據(jù)滑動距離判斷是折疊還是展開
                    Log.e(TAG, "---------------------------------onTouchEvent: 自由滑動");
//                    if (getScrollX() > (rightBorder - getChildAt(0).getRight()) / 2) {
                    if (getScrollX() > (rightBorder - getMeasuredWidth()) / 2) {//當(dāng)滑動距離超過‘隱藏內(nèi)容’一半寬度時,手指離開屏幕后隱藏內(nèi)容剩余部分自動展開
                        setFold(false);
//                        scroller.startScroll(getScrollX(), 0, rightBorder - getChildAt(0).getRight() - getScrollX(), 0, 500);
                        scroller.startScroll(getScrollX(), 0, rightBorder - getMeasuredWidth() - getScrollX(), 0, 500);
                        invalidate();
                    } else {//當(dāng)滑動距離小于‘隱藏內(nèi)容’一半寬度時,手指離開屏幕后隱藏內(nèi)容已展開部分自動折疊
                        setFold(true);
                        scroller.startScroll(getScrollX(), 0, -getScrollX(), 0, 500);
                        invalidate();
                    }
                }
                recycleVelocityTracker();
                releaseRecyclerView();//XZ
            }
            break;
        }
        return super.onTouchEvent(event) || result;
    }

    /**
     * 工具方法(View自帶方法,所有view都有該方法)
     * 頁面每次重繪都會調(diào)用該方法,默認(rèn)是空實(shí)現(xiàn),一般都是跟scroller配合使用
     */
    @Override
    public void computeScroll() {
        super.computeScroll();
        if (scroller.computeScrollOffset()) {
            scrollTo(scroller.getCurrX(), scroller.getCurrY());
            postInvalidate();
        }
    }

    /**
     * 工具方法
     * 獲取慣性滑動的豎直速度
     */
    private void addVelocityTracker(MotionEvent event) {
        if (velocityTracker == null) {
            velocityTracker = VelocityTracker.obtain();
        }
        velocityTracker.addMovement(event);
    }

    /**
     * 工具方法
     * 獲取慣性滑動的橫向速度
     */
    private float getXVelocity() {
        if (velocityTracker != null) {
            velocityTracker.computeCurrentVelocity(1000);
            return velocityTracker.getXVelocity();
        }
        return 0;
    }

    /**
     * 工具方法
     * VelocityTracker常規(guī)使用
     */
    private void recycleVelocityTracker() {
        if (velocityTracker != null) {
            velocityTracker.recycle();//出錯點(diǎn),這里不需要再添加clear代碼,否者會報錯
        }
        velocityTracker = null;
    }

    /**
     * 工具方法
     * 獲取該view所在列表的recyclerview對象
     */
    private RecyclerView getRecyclerView() {
        if (recyclerView != null)
            return recyclerView;
        ViewParent viewParent = this;
        while (!(viewParent.getParent() instanceof RecyclerView)) {
            viewParent = viewParent.getParent();
            if (viewParent == null) {
                Log.e(TAG, "getRecyclerView: 沒有找到recyclerview");
                break;
            }
        }
        recyclerView = (viewParent == null ? null : (RecyclerView) viewParent.getParent());
        if (recyclerView != null) {
            Log.e(TAG, "getRecyclerView: 找到了recyclerview");
        }
        return recyclerView;
    }

    private void releaseRecyclerView() {
        recyclerView = null;
    }

    /**
     * view折疊狀態(tài)
     * 默認(rèn)為true
     * 當(dāng)刪除、置頂部分隱藏的時候是折疊狀態(tài),顯示的時候是非折疊狀態(tài)
     */
    public boolean isFold() {
        return fold;
    }

    public void setFold(boolean fold) {
        this.fold = fold;
    }

    /**
     * 工具方法
     * 遍歷RV可見范圍內(nèi)是否有其它item是非折疊狀態(tài)
     */
    private boolean existExpandChildren() {
        boolean result = false;
        if (getRecyclerView() != null) {
            int childCount = getRecyclerView().getChildCount();
            for (int i = 0; i < childCount; i++) {
                View itemView = getRecyclerView().getChildAt(i);
                SlideSlipView slideSlipView = getExpandSlideSlipView(itemView);
                if (slideSlipView != null) {
                    slideSlipView.smoothScrollToStart();
                    slideSlipView.setFold(true);
                    result = true;
                }
            }
        }
        return result;
    }

    /**
     * 工具方法
     * scroller滑動常規(guī)使用
     */
    private void smoothScrollToStart() {
        scroller.startScroll(getScrollX(), 0, -getScrollX(), 0, 500);
        postInvalidate();
    }

    /**
     * 工具方法
     * 遞歸獲取slideslipview對象
     */
    private SlideSlipView getExpandSlideSlipView(View parentView) {
        if (parentView == null) {
            return null;
        }
        if (parentView == this) {
            return null;
        }
        if (parentView instanceof SlideSlipView) {
            if (((SlideSlipView) parentView).isFold()) {
                return null;
            } else {
                return (SlideSlipView) parentView;
            }
        }
        if (!(parentView instanceof ViewGroup)) {
            return null;
        }
        final int childCount = ((ViewGroup) parentView).getChildCount();
        for (int i = 0; i < childCount; i++) {
            SlideSlipView slipView = getExpandSlideSlipView(((ViewGroup) parentView).getChildAt(i));
            if (slipView != null) {
                if (slipView.isFold()) {
                    return null;
                } else {
                    return slipView;
                }
            }
        }
        return null;
    }

    /**
     * 工具方法
     * 獲取子view的間距
     */
    private int getMargin(int type, View childView) {
        ViewGroup.LayoutParams layoutParams = childView.getLayoutParams();
        if (!(layoutParams instanceof MarginLayoutParams)) {
            return 0;
        }
        int result;
        switch (type) {
            case 0:
                result = Math.max(0, ((MarginLayoutParams) layoutParams).leftMargin);
                break;
            case 1:
                result = Math.max(0, ((MarginLayoutParams) layoutParams).rightMargin);
                break;
            case 2:
                result = Math.max(0, ((MarginLayoutParams) layoutParams).topMargin);
                break;
            default:
                result = 0;
        }
        return result;
    }
}

源碼地址:https://github.com/AndroidFirstDeveloper/DevelopProject

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容