Android 自定義側(cè)滑菜單效果(ViewDragHelper)

吹牛皮

忙里偷閑在研究自定義View這一塊的東西,單純的使用觸摸事件加攔截事件等等的側(cè)滑功能還是寫過,還沒用過ViewDragHelper來完成這個(gè)功能,所以就嘗試一下!

ToolBar+DrawerLayout使用

一般來講,如果要完成一個(gè)具有拖拽側(cè)滑的功能就必需要處理各種事件,比如onInterceptTouchEventOnTouchEvent,處理起來也不是很得心應(yīng)手,出各種亂子的可能性都有!這個(gè)時(shí)候可以使用ViewDragHelper來輔助我們完成這些操作,Google用ViewDragHelper 封裝了對(duì)onInterceptTouchEventOnTouchEvent的處理,也就是說Google已經(jīng)替我們寫好了邏輯,我們只需要設(shè)定好條條框框(比如邊界判斷等)就行了。


一、不扯有的沒的 回歸正題

UI什么的都是臨時(shí)搭的,很丑啊,但是很溫柔!
↓↓↓先上效果圖↓↓↓

側(cè)滑.png


效果圖看完了繼續(xù)往下看。

二、自定義View

/**
 * Created by Leogh on 2017/8/25.
 */

public class SwipeLayout1 extends LinearLayout {

    private ViewDragHelper mDragHelper = null;
    private View mDragView;
    private View mHideView;
    private int mDragSlop;//移動(dòng)距離 小于這個(gè)距離就不觸發(fā)移動(dòng)控件 恢復(fù)到當(dāng)前位置
    private int mWidth;
    private int mHeight;
    private int mDragDistance;
    private final int STATE_CLOSE = 1001;
    private final int STATE_OPEN = 1002;
    private int mState = STATE_CLOSE;

    public SwipeLayout1(Context context) {
        this(context, null);
    }

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

    public SwipeLayout1(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initView();
    }

    private void initView() {
        //其中1.0f是敏感度參數(shù)參數(shù)越大越敏感。第一個(gè)參數(shù)為this,表示該類生成的對(duì)象,
        // 他是ViewDragHelper的拖動(dòng)處理對(duì)象,必須為ViewGroup。
        mDragHelper = ViewDragHelper.create(this, 1.0f, new CallBack());
        //48dp  是一個(gè)距離,表示滑動(dòng)的時(shí)候,手的移動(dòng)要大于這個(gè)距離才開始移動(dòng)控件。如果小于這個(gè)距離就不觸發(fā)移動(dòng)控件,
        // 如viewpager就是用這個(gè)距離來判斷用戶是否翻頁(yè)
        mDragSlop = ViewConfigurationCompat.getScaledPagingTouchSlop(ViewConfiguration.get(getContext()));
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        mDragView.layout(getPaddingLeft(), getPaddingTop(), mWidth - getPaddingRight(), mHeight - getPaddingBottom());
        mHideView.layout(mWidth - getPaddingRight(), getPaddingTop(), mWidth - getPaddingRight() + mDragDistance, mHeight - getPaddingBottom());
    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        try {
            mDragView = getChildAt(0);
            mHideView = getChildAt(1);
        } catch (Exception e) {
            throw new NullPointerException("必須有兩個(gè)子view");
        }
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        mWidth = w;
        mHeight = h;
        mDragDistance = mHideView.getMeasuredWidth();
    }

    /**
     * onInterceptTouchEvent中通過使用mDragger.shouldInterceptTouchEvent(event)來決定我們是否應(yīng)該攔截當(dāng)前的事件。
     *
     * @param ev
     * @return
     */
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        return mDragHelper.shouldInterceptTouchEvent(ev);
    }

    /**
     * onTouchEvent中通過mDragger.processTouchEvent(event)處理事件。
     *
     * @param event
     * @return true 時(shí)間已消費(fèi)(交給了mDragHelper)不往下傳遞
     */
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        mDragHelper.processTouchEvent(event);
        return true;
    }

    /**
     * 這個(gè)計(jì)算滑動(dòng)的函數(shù)computeScroll(),就是用于判斷滾動(dòng)是否完成的。
     * 在computeScroll方法中判斷smoothSlideViewTo觸發(fā)的continueSettling(boolean)的返回值,來動(dòng)態(tài)刷新界面
     */
    @Override
    public void computeScroll() {
        super.computeScroll();
        if (mDragHelper.continueSettling(true)) {
            ViewCompat.postInvalidateOnAnimation(this);
//            postInvalidate();
        }
    }

    class CallBack extends ViewDragHelper.Callback {
        @Override
        public boolean tryCaptureView(View child, int pointerId) {
            return child == mDragView;
        }

        //拖拽的子View在所屬方向上移動(dòng)的位置(這里是水平方向),child為拖拽的子View,left為子view應(yīng)該到達(dá)的x坐標(biāo),dx為挪動(dòng)差值
        //return left
        @Override
        public int clampViewPositionHorizontal(View child, int left, int dx) {
            Log.e(TAG + "clampViewPositionHorizontal", left + "");
            //以下兩個(gè)判斷是防止越界(部分View被遮?。?            if (left > getPaddingLeft()) {//向右滑動(dòng)時(shí) 超過了paddingLeft都返回這個(gè)值(保持原位)
                return getPaddingLeft();
            }
            if (left < getPaddingLeft() - mDragDistance) {//向左滑動(dòng) 整個(gè)隱藏的View都滑出來了 超過了getPaddingLeft() - mDragDistance都返回這個(gè)值(保持原位)
                return getPaddingLeft() - mDragDistance;
            }
            return left;
        }

        @Override
        public int clampViewPositionVertical(View child, int top, int dy) {
            return getPaddingTop();
        }

        //返回拖拽子View在相應(yīng)方向上可以被拖動(dòng)的最遠(yuǎn)距離,默認(rèn)為0
        @Override
        public int getViewHorizontalDragRange(View child) {
            Log.e(TAG + "getViewHorizontalDragRange", mDragDistance + "");
            return mDragDistance;
        }

        //當(dāng)前拖拽的view松手或者ACTION_CANCEL時(shí)調(diào)用,xvel、yvel為離開屏幕時(shí)的速率
        @Override
        public void onViewReleased(View releasedChild, float xvel, float yvel) {
            //getPaddingLeft() - mDragView.getLeft() → 控件不動(dòng)時(shí)為0
            int getPaddingLeft = getPaddingLeft();
            int getmDragViewLeft = mDragView.getLeft();
            int temp = getPaddingLeft() - mDragView.getLeft();
            int tempdragSlop = mDragSlop;
            if (getPaddingLeft() - mDragView.getLeft() < mDragSlop) {//最終位置的判斷
                smoothSlideHide();
            } else {
                smoothSlideOpen();
            }
            ViewCompat.postInvalidateOnAnimation(SwipeLayout1.this);
//            postInvalidate();
        }

        //被拖拽的View位置變化時(shí)回調(diào),changedView為位置變化的view,left、top變化后的x、y坐標(biāo),dx、dy為新位置與舊位置的偏移量
        @Override
        public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
            Log.e(TAG + "onViewPositionChanged", dx + "");
            mHideView.layout(mHideView.getLeft() + dx, mHideView.getTop(), mHideView.getRight() + dx, mHideView.getBottom());
            ViewCompat.postInvalidateOnAnimation(SwipeLayout1.this);
//            postInvalidate();
        }
    }

    private void smoothSlideHide(){
        //smoothSlideViewTo方法某個(gè)View自動(dòng)滾動(dòng)到指定的位置,如果這個(gè)方法返回true,那么在接下來動(dòng)畫移動(dòng)的每一幀中都會(huì)回調(diào)continueSettling(boolean)方法,直到結(jié)束
        mDragHelper.smoothSlideViewTo(getHideView(), mWidth - getPaddingRight(), getPaddingTop());
        mDragHelper.smoothSlideViewTo(getDragView(), getPaddingLeft(), getPaddingTop());
        mState = STATE_CLOSE;
    }

    private void smoothSlideOpen(){
        mDragHelper.smoothSlideViewTo(getHideView(), mWidth - getPaddingRight() - mDragDistance, getPaddingTop());
        mDragHelper.smoothSlideViewTo(getDragView(), getPaddingLeft() - mDragDistance, getPaddingTop());
        mState = STATE_OPEN;
    }

    public View getDragView() {
        if (getChildCount() == 0) return null;
        return getChildAt(0);
    }

    public View getHideView() {
        if (getChildCount() == 1) return null;
        return getChildAt(1);
    }

    private void setState(int state){
        this.mState = state;
    }

    private int getState(){
        return mState;
    }

    /**
     * 關(guān)閉滑動(dòng)
     */
    public void close(){
        if (mState == STATE_OPEN){
            smoothSlideHide();
            ViewCompat.postInvalidateOnAnimation(SwipeLayout1.this);
        }
    }
}

好了,自定義view完成了,代碼中注釋已經(jīng)一目了然了,都是用比較淺顯的話來表達(dá)(片面),只能說話糙理不糙,看得懂才是王道。


二、大致的UI布局

創(chuàng)建在res\layout文件夾下創(chuàng)建一個(gè)xml文件,命名為item_swipelayout.xml。首先側(cè)滑我們不難看出只分為兩個(gè)部分,第一部分為內(nèi)容區(qū)域(可視部分),第二部分為菜單區(qū)域(隱藏部分)。
所以自定view類SwipeLayout1中就要求在布局時(shí)要包含兩個(gè)子view(即兩個(gè)部分),具體布局如下:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
                xmlns:tools="http://schemas.android.com/tools"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                tools:context=".MainActivity">

    <com.sobergh.soberghalltest.itemslideview.SwipeLayout1
        android:id="@+id/sl"
        android:layout_width="match_parent"
        android:layout_height="70dp">

        <TextView
            android:id="@+id/tv_content"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="@android:color/holo_blue_bright"
            android:gravity="center_vertical"
            android:text="老臘肉老臘肉老臘肉老臘肉老臘肉老臘肉"/>

        <LinearLayout
            android:layout_width="80dp"
            android:layout_height="match_parent"
            android:orientation="horizontal">

            <TextView
                android:id="@+id/tv_top"
                android:layout_width="0dp"
                android:layout_height="match_parent"
                android:layout_weight="1"
                android:background="@android:color/holo_orange_light"
                android:gravity="center"
                android:text="置頂"/>

            <TextView
                android:id="@+id/tv_delete"
                android:layout_width="0dp"
                android:layout_height="match_parent"
                android:layout_weight="1"
                android:background="@android:color/holo_green_light"
                android:gravity="center"
                android:text="刪除"/>

        </LinearLayout>
    </com.sobergh.soberghalltest.itemslideview.SwipeLayout1>
</RelativeLayout>

到這里,自定義效果就完成了,只需新建一個(gè)activty把布局文件item_swipelayout.xml加載一下就行了。好吧,還是寫一下,新建一個(gè)activity命名為SwipeLayoutActivity,如下:

public class SwipeLayoutActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.item_swipelayout);
    }
}

簡(jiǎn)單粗暴的就可以運(yùn)行了,如果你要調(diào)用自定義View里面的關(guān)閉滑動(dòng)的方法就需要進(jìn)行findViewById的操作了,然后調(diào)用即可。

還可以進(jìn)行很多擴(kuò)展,比如應(yīng)用到ListView中,這一部分后面應(yīng)該會(huì)加上去,應(yīng)該在自定義view中加回調(diào)方法就行

不能做伸手黨,所借鑒大神的地址:https://github.com/mzw1004


寫完,可以開始打坐了!啦啦啦啦啦啦啦啦啦啦啦啦,簡(jiǎn)單粗暴。

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

相關(guān)閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 178,954評(píng)論 25 709
  • DrawerLayout是android support包新增的側(cè)滑菜單控件,在Android Studio中可以...
    Ihesong閱讀 4,608評(píng)論 0 3
  • 又與女兒起沖突了,總是抑制不住自己的性情,著急,再急。都說好孩子是夸出來的,而我的確太吝嗇自己的表?yè)P(yáng)了,總是在苛求...
    綠塬閱讀 214評(píng)論 0 0
  • 今天5.8川藏線上第五天 昨天晚上之前腿傷那兩隊(duì)友大林和查派決定不去了 溫存哥 感冒還繼續(xù)待定 唉 沒辦法!今天就...
    jenffy閱讀 311評(píng)論 2 0
  • 曾經(jīng)年少愛追夢(mèng), 一心只想往前飛。 行遍千山和萬水, 一路走來不能回。 世間哪有永遠(yuǎn)的忘憂歲月。誰不曾渴望鮮衣怒馬...
    好風(fēng)如水yt閱讀 445評(píng)論 1 2

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