Android中系統(tǒng)觸摸相關(guān)輔助類總結(jié)

Android中系統(tǒng)觸摸相關(guān)輔助類總結(jié)

Android中的觸摸事件,我們可以通過重寫View的OnTouchEvent()等事件,通過事件類型MotionEvent來進(jìn)行我們想要實(shí)現(xiàn)的邏輯操作坎弯,有時(shí)候一些簡(jiǎn)單的需求很容易實(shí)現(xiàn),但是有時(shí)候,一些很困難的需求,我們需要編寫大量的代碼來實(shí)現(xiàn)驯绎。好在Android官方就給我們提供了很豐富的觸摸相關(guān)的輔助類,今天谋旦,我就把我知道的分享給大家剩失。

1、GestureDetector(手勢(shì)探測(cè)器)

顧名思義册着,GestureDetector可以很方便的幫我們識(shí)別出我們當(dāng)前在屏幕上的手勢(shì)操作拴孤,比如按下、長(zhǎng)按甲捏、抬起演熟、雙擊、滾動(dòng)司顿、快速滑動(dòng)(慣性滑動(dòng))等芒粹,甚至它還能識(shí)別出是否是鼠標(biāo)右鍵點(diǎn)擊的動(dòng)作,省去了我們大量的手勢(shì)判斷語句免猾,非常的實(shí)用是辕。

1.1 相關(guān)的方法及接口簡(jiǎn)介

GestureDetector的手勢(shì)識(shí)別有三個(gè)接口來處理我們當(dāng)前的手勢(shì)動(dòng)作,還有一個(gè)集成了三個(gè)接口的實(shí)現(xiàn)類方便我們?nèi)ヌ幚矶喾N事件猎提。

1.1.1 GestureDetector.OnGestureListener

作用: 用來監(jiān)聽單擊、長(zhǎng)按旁蔼、滑動(dòng)等操作锨苏,我們可以在這些操作的回調(diào)方法中處理我們自己的邏輯。

具體實(shí)現(xiàn)方法:
見下面代碼棺聊,注釋已經(jīng)寫的很清楚了伞租,示例代碼中僅僅對(duì)滾動(dòng)進(jìn)行了處理。

    private static class MyGestureDetectorListener implements GestureDetector.OnGestureListener{

        private GestureDetectorView mView;

        public MyGestureDetectorListener(GestureDetectorView view){
            this.mView = view;
        }

        /**
         * 用戶按下屏幕的時(shí)候回調(diào)
         * @param motionEvent
         * @return
         */
        @Override
        public boolean onDown(MotionEvent motionEvent) {
            Log.e("ll", "MyGestureDetectorListener : onDown");
            return true;
        }

        /**
         * 用戶按下屏幕100ms后限佩,如果還沒有松手或者移動(dòng)就會(huì)回調(diào)
         * @param motionEvent
         */
        @Override
        public void onShowPress(MotionEvent motionEvent) {
            Log.e("ll", "MyGestureDetectorListener : onShowPress");
        }

        /**
         * 單純的點(diǎn)擊再抬手時(shí)調(diào)用葵诈。用戶手指松開(UP事件)的時(shí)候如果沒有執(zhí)行onScroll()和onLongPress()這兩個(gè)回調(diào)的話裸弦,就會(huì)回調(diào)
         * @param motionEvent
         * @return
         */
        @Override
        public boolean onSingleTapUp(MotionEvent motionEvent) {
            Log.e("ll", "MyGestureDetectorListener : onSingleTapUp");
            return false;
        }

        /**
         * 屏幕拖動(dòng)事件,如果按下的時(shí)間過長(zhǎng)作喘,調(diào)用了onLongPress理疙,再拖動(dòng)屏幕不會(huì)觸發(fā)onScroll。
         * 拖動(dòng)屏幕會(huì)多次觸發(fā)
         * @param motionEvent 開始拖動(dòng)的第一次按下down操作,也就是第一個(gè)ACTION_DOWN
         * @param motionEvent1 觸發(fā)當(dāng)前onScroll方法的ACTION_MOVE
         * @param distanceX 當(dāng)前的x坐標(biāo)與最后一次觸發(fā)scroll方法的x坐標(biāo)的差值泞坦。
         * @param distanceY 當(dāng)前的y坐標(biāo)與最后一次觸發(fā)scroll方法的y坐標(biāo)的差值窖贤。
         * @return
         */
        @Override
        public boolean onScroll(MotionEvent motionEvent, MotionEvent motionEvent1, float distanceX, float distanceY) {
            Log.e("ll", "MyGestureDetectorListener : onScroll : " + distanceY);
            mView.scrollBy(0, (int) distanceY);
            return false;
        }

        /**
         * 用戶長(zhǎng)按后(好像不同手機(jī)的時(shí)間不同,源碼里默認(rèn)是100ms+500ms)觸發(fā)贰锁,觸發(fā)之后不會(huì)觸發(fā)其他回調(diào)赃梧,直至松開(UP事件)。
         * @param motionEvent
         */
        @Override
        public void onLongPress(MotionEvent motionEvent) {
            Log.e("ll", "MyGestureDetectorListener : onLongPress");
        }

        /**
         * 按下屏幕豌熄,在屏幕上快速滑動(dòng)后松開授嘀,由一個(gè)down,多個(gè)move,一個(gè)up觸發(fā)
         * @param motionEvent 開始快速滑動(dòng)的第一次按下down操作,也就是第一個(gè)ACTION_DOWN
         * @param motionEvent1 觸發(fā)當(dāng)前onFling方法的move操作,也就是最后一個(gè)ACTION_MOVE
         * @param velocityX X軸上的移動(dòng)速度,像素/秒
         * @param velocityY Y軸上的移動(dòng)速度锣险,像素/秒
         * @return
         */
        @Override
        public boolean onFling(MotionEvent motionEvent, MotionEvent motionEvent1, float velocityX, float velocityY) {
            Log.e("ll", "MyGestureDetectorListener : onFling");
            return false;
        }
    }

注意事項(xiàng):

1蹄皱、onDown()方法返回true時(shí),onScroll()onFling()方法才能回調(diào)囱持。
2夯接、onLongPress()若觸發(fā)了,則不會(huì)再觸發(fā)其他回調(diào)纷妆。
3盔几、事件回調(diào)順序:

  • 快速點(diǎn)擊:onDown() > onSingleTapUp()
  • 慢按屏幕:onDown() > onShowPress() > onSingleTapUp()
  • 長(zhǎng)按屏幕:onDown() > onShowPress() > onLongPress()
  • 滑動(dòng)屏幕:onDown() > onShowPress() > onScroll()(多個(gè)) > onFling()
  • 快速拖動(dòng)屏幕后松手:onDown() > onScroll()(多個(gè)) > onFling()

1.1.2 GestureDetector.OnDoubleTapListener

作用: 用來監(jiān)聽雙擊事件操作,我們可以在這些操作的回調(diào)方法中處理我們自己的邏輯掩幢。

具體實(shí)現(xiàn)方法:
見下面代碼逊拍,注釋已經(jīng)寫的很清楚了。

private static class MyGestureDetectorDoubleTapListener implements GestureDetector.OnDoubleTapListener{

    /**
     * 單擊事件际邻。用來判定該次點(diǎn)擊是單純的SingleTap而不是DoubleTap卒茬,如果連續(xù)點(diǎn)擊兩次就是DoubleTap手勢(shì),
     * 如果只點(diǎn)擊一次杯活,系統(tǒng)等待一段時(shí)間后沒有收到第二次點(diǎn)擊則判定該次點(diǎn)擊為SingleTap而不是DoubleTap霹抛,然后觸發(fā)SingleTapConfirmed事件。
     * @param motionEvent
     * @return
     */
    @Override
    public boolean onSingleTapConfirmed(MotionEvent motionEvent) {
        Log.e("ll","MyGestureDetectorDoubleTapListener : onSingleTapConfirmed");
        return false;
    }

    /**
     * 雙擊觸發(fā)
     * @param motionEvent
     * @return
     */
    @Override
    public boolean onDoubleTap(MotionEvent motionEvent) {
        Log.e("ll","MyGestureDetectorDoubleTapListener : onDoubleTap");
        return false;
    }

    /**
     * 雙擊間隔中發(fā)生的動(dòng)作轮听。指觸發(fā)onDoubleTap以后骗露,在雙擊之間發(fā)生的其它動(dòng)作,包含down血巍、up和move事件
     * @param motionEvent
     * @return
     */
    @Override
    public boolean onDoubleTapEvent(MotionEvent motionEvent) {
        Log.e("ll","MyGestureDetectorDoubleTapListener : onDoubleTapEvent");
        return false;
    }
}

注意事項(xiàng):
1萧锉、onSingleTapConfirmed()用來判斷是不是單點(diǎn)事件,雙擊不會(huì)執(zhí)行此方法述寡。
2柿隙、事件調(diào)用順序:

  • 單擊屏幕時(shí): OnDown() > OnsingleTapUp() > OnsingleTapConfirmed()
  • 雙擊屏幕時(shí): OnDown() > onSingleTapUp() > onDoubleTap() > onDoubleTapEvent() > onDown() > onDoubleTapEvent()

1.1.3 GestureDetector.OnContextClickListener

作用: 用來監(jiān)聽鼠標(biāo)右鍵按下的事件叶洞,我們可以在這些操作的回調(diào)方法中處理我們自己的邏輯。

具體實(shí)現(xiàn)方法:
見下面代碼禀崖,注釋已經(jīng)寫的很清楚了衩辟。

private static class MyGestureDetectorContextClickListener implements GestureDetector.OnContextClickListener{

    /**
     * 當(dāng)鼠標(biāo)/觸摸板,右鍵點(diǎn)擊時(shí)候的回調(diào)帆焕。
     * @param motionEvent
     * @return
     */
    @Override
    public boolean onContextClick(MotionEvent motionEvent) {
        return false;
    }
}

注意事項(xiàng):
1惭婿、OnContextClickListener接口最低支持的API是23。

1.1.4 GestureDetector.SimpleOnGestureListener(實(shí)現(xiàn)類)

SimpleOnGestureListener實(shí)現(xiàn)了上述三個(gè)接口叶雹,我們可以直接使用此類财饥,并重寫我們想要的方法就行。

GestureDetector.SimpleOnGestureListener simpleOnGestureListener = new GestureDetector.SimpleOnGestureListener(){

    @Override
    public boolean onDown(MotionEvent e) {
        return true;
    }
};

1.2 GestureDetector使用方法

GestureDetector可以作用于多個(gè)地方折晦,比如Activity的OnTouchEvent()方法钥星,View的onTouch()方法和onTouchEvent()方法上,其中满着,以 后兩者使用較多谦炒。
這里以View.onTouchEvent()方法舉例:

1.2.1 在View的初始化時(shí)初始化GestureDetector

public GestureDetectorView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    init(context);
}

private void init(Context context){
    //傳入View方便對(duì)View進(jìn)行操作
    mGestureDetector = new GestureDetector(context, new MyGestureDetectorListener(this));
    //設(shè)置雙擊事件監(jiān)聽
    mGestureDetector.setOnDoubleTapListener(new MyGestureDetectorDoubleTapListener());
}

1.2.1 在View的onTouchEvent()方法中,將觸摸事件交給GestureDetector去處理

@Override
public boolean onTouchEvent(MotionEvent event) {
    return mGestureDetector.onTouchEvent(event);
}

此處展示一個(gè)使用示例风喇,僅僅監(jiān)聽了OnGestureListeneronScroll()方法:

監(jiān)聽onScroll()

1.3 小結(jié)

GestureDetector可以很方便的為我們檢測(cè)各種觸摸事件宁改,我們只需要再相應(yīng)的回調(diào)方法中處理我們自己的邏輯即可,可以說是觸摸輔助類第一神器魂莫!

2还蹲、ViewDragHelper(子控件拖拽幫手)

顧名思義,ViewDragHelper肯定是用來幫助View進(jìn)行Drag(拖拽)的耙考。它主要是用來給ViewGroup處理它內(nèi)部子View的拖拽動(dòng)作使用的谜喊,有了它,我們可以省去寫ViewGroup中煩人的觸摸攔截方法(onInterceptTouchEvent())和子控件的onTouchEvent()方法了倦始!

2.1 相關(guān)方法及接口介紹

2.1.1 ViewDragHelper.Callback

ViewDragHelper的主要方法都在它內(nèi)部類ViewDragHelper.Callback中斗遏,這里,我寫了一個(gè)類MyViewDragHelperCallback實(shí)現(xiàn)了ViewDragHelper.Callback中的比較重要的方法鞋邑,每個(gè)方法的具體作用诵次,都已在注釋中寫明:

private static class MyViewDragHelperCallback extends ViewDragHelper.Callback{

    //傳入父ViewGroup和ViewDragHelper,便于對(duì)子View的操作進(jìn)行判斷
    private RelativeLayout mViewGroup;
    private ViewDragHelper mDragHelper;

    public MyViewDragHelperCallback(ViewDragHelper dragHelper, RelativeLayout view){
        this.mDragHelper = dragHelper;
        this.mViewGroup = view;
    }


    /**
     * 根據(jù)傳入的child確定是否需要捕獲此child(對(duì)它進(jìn)行操作)
     * @param child 被觸摸的子View
     * @param pointerId 按下手指的id枚碗,一般多點(diǎn)觸摸時(shí)會(huì)用來判斷是哪根手指觸摸
     * @return 返回true表示將要捕獲這個(gè)child
     */
    @Override
    public boolean tryCaptureView(View child, int pointerId) {
        return true;
    }

    /**
     * 返回被橫向移動(dòng)的子控件child的上坐標(biāo)top藻懒,和移動(dòng)距離dy,我們可以根據(jù)這些值來返回child的新的top视译。
     * @param child
     * @param top 拖拽動(dòng)作后系統(tǒng)建議的距離父布上側(cè)的距離
     * @param dy Y軸方向拖拽移動(dòng)的距離
     * @return 返回拖拽后child距離父布局上側(cè)的距離
     */
    @Override
    public int clampViewPositionVertical(View child, int top, int dy) {
        return top;
    }

    /**
     * 返回被橫向移動(dòng)的子控件child的左坐標(biāo)left,和移動(dòng)距離dx归敬,我們可以根據(jù)這些值來返回child的新的left酷含。
     * @param child
     * @param left 拖拽動(dòng)作后系統(tǒng)建議的距離父布局左側(cè)的距離
     * @param dx X軸方向拖拽移動(dòng)的距離
     * @return 返回拖拽后child距離父布局左側(cè)的距離
     */
    @Override
    public int clampViewPositionHorizontal(View child, int left, int dx) {
        return left;
    }

    /**
     * 這個(gè)用來控制垂直移動(dòng)的邊界范圍鄙早,單位是像素。
     * @param child
     * @return 返回值大于0時(shí)才能捕獲子View
     */
    @Override
    public int getViewVerticalDragRange(View child) {
        return super.getViewVerticalDragRange(child);
    }

    /**
     * 這個(gè)用來控制橫向移動(dòng)的邊界范圍椅亚,單位是像素限番。
     * @param child
     * @return 返回值大于0時(shí)才能捕獲子View
     */
    @Override
    public int getViewHorizontalDragRange(View child) {
        return super.getViewHorizontalDragRange(child);
    }

    /**
     * 當(dāng)releasedChild被釋放的時(shí)候回調(diào)
     * @param releasedChild
     * @param xvel x軸方向的加速度
     * @param yvel y軸方向的加速度
     */
    @Override
    public void onViewReleased(View releasedChild, float xvel, float yvel) {
        super.onViewReleased(releasedChild, xvel, yvel);
    }

    /**
     * 若ViewDragHelper設(shè)置了setEdgeTrackingEnabled()此方法,則調(diào)用此方法
     * @param edgeFlags 邊緣觸摸方向
     * @param pointerId
     */
    @Override
    public void onEdgeDragStarted(int edgeFlags, int pointerId) {
        super.onEdgeDragStarted(edgeFlags, pointerId);
    }
}

2.1.2 ViewDragHelper.captureChildView()

直接對(duì)子View進(jìn)行捕獲呀舔,可以繞開Callback.tryCaptureView()方法弥虐。

2.1.3 ViewDragHelper.setEdgeTrackingEnabled()

設(shè)置ViewGroup的邊緣可以被拖拽,可以設(shè)置左媚赖、上霜瘪、右、下四個(gè)方向或者任意幾個(gè)方向惧磺,一般配合Callback.onEdgeDragStarted()ViewDragHelper.captureChildView()使用(后面有示例)颖对。

2.1.4 ViewDragHelper.settleCapturedViewAt()

對(duì)ViewGroup的某子View進(jìn)行回彈處理,需要配合ViewGroup的computeScroll()方法和invalidate()方法磨隘。

2.2 ViewDragHelper使用方式

2.2.1 初始化ViewDragHelper

在ViewGroup的初始化時(shí)缤底,通過ViewDragHelper的靜態(tài)初始化方法create()進(jìn)行創(chuàng)建:

public ViewDragHelperView(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    init();
}

private void init(){
    mViewDragHelper = ViewDragHelper.create(this,1.0f, new MyViewDragHelperCallback(this));
}

創(chuàng)建時(shí)的三個(gè)參數(shù)的意義分別是:

  • forParent:給哪個(gè)ViewGroup使用的,一般傳入當(dāng)前的ViewGroup番捂;
  • sensitivity:拖拽靈敏度个唧,傳入值越大,靈敏度越小设预,一般傳入1.0f即可徙歼;
  • ViewDragHelperCallback:ViewDragHelper的實(shí)現(xiàn)類Callback,基本所有的拖拽后的回調(diào)都在此方法內(nèi)絮缅。

2.2.2 重寫onInterceptTouchEvent()

重寫ViewGroup的onInterceptTouchEvent()方法鲁沥,把它交給ViewDragHelper的shouldInterceptTouchEvent()來處理。

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
    return mViewDragHelper.shouldInterceptTouchEvent(ev);
}

注意:ViewDragHelper只對(duì)ViewGroup有效耕魄,并且它也不能直接操作子View画恰,而是借助的ViewGroup!

2.2.3 重寫ViewGroup的onTouchEvent()

重寫ViewGroup的onTouchEvent()吸奴,把它交給ViewDragHelper的processTouchEvent()來處理允扇,并返回true

@Override
public boolean onTouchEvent(MotionEvent event) {
    mViewDragHelper.processTouchEvent(event);
    return true;
}

使用方式是非常簡(jiǎn)單的则奥,最重要的是在Callback中實(shí)現(xiàn)自己的拖拽邏輯考润。

2.3 使用場(chǎng)景簡(jiǎn)介

使用場(chǎng)景都是在ViewDragHelper的實(shí)現(xiàn)類Callback中進(jìn)行處理的。

2.3.1 對(duì)子View進(jìn)行拖拽到任意位置

效果展示:

捕捉子View

通過重寫tryCaptureView()读处,clampViewPositionVertical()clampViewPositionHorizontal()對(duì)子View進(jìn)行拖拽處理糊治。
其中,tryCaptureView()用來判斷是否是需要拖拽的子View罚舱,clampViewPositionVertical()clampViewPositionHorizontal()用來返回子View拖拽后的位置井辜。

示例代碼表示绎谦,可以拖拽任意子View到任意位置,但是不能超過父ViewGroup的左側(cè)和右側(cè):

    @Override
    public boolean tryCaptureView(View child, int pointerId) {
        //直接返回true粥脚,表示可以拖拽任意子View
        return true;
    }

    @Override
    public int clampViewPositionVertical(View child, int top, int dy) {
        //直接返回top窃肠,不對(duì)垂直方向的拖拽進(jìn)行攔截,交給ViewDragHelper去處理
        return top;
    }

    @Override
    public int clampViewPositionHorizontal(View child, int left, int dx) {
        //不能超過父View左側(cè)
        if(left < mViewGroup.getPaddingLeft()){
            left = mViewGroup.getPaddingLeft();
        }

        //不能超過父View右側(cè)
        if(left + child.getWidth() > mViewGroup.getWidth() - mViewGroup.getPaddingRight()){
            left = mViewGroup.getWidth() - mViewGroup.getPaddingRight() - child.getWidth();
        }

        return left;
    }

2.3.2 對(duì)ViewGroup邊緣觸摸事件進(jìn)行捕獲子View的行為

效果展示:

邊緣捕獲

通過ViewDragHelper.setEdgeTrackingEnabled(ViewDragHelper.EDGE_LEFT)對(duì)左側(cè)邊緣進(jìn)行觸摸刷允,通過Callback.onEdgeDragStarted()ViewDragHelper.captureChildView()對(duì)需要捕獲的子View使用操作冤留。

示例代碼表示,可以在左側(cè)或者上側(cè)邊緣拖拽id為tv2的子View:

ViewDragHelper:

//對(duì)屏幕左側(cè)或上側(cè)進(jìn)行邊緣觸摸
mViewDragHelper.setEdgeTrackingEnabled(ViewDragHelper.EDGE_LEFT|ViewDragHelper.EDGE_TOP);

Callback:

@Override
public void onEdgeDragStarted(int edgeFlags, int pointerId) {
    super.onEdgeDragStarted(edgeFlags, pointerId);
    View childView = mViewGroup.findViewById(R.id.tv2);
    mDragHelper.captureChildView(childView,pointerId);
}

2.3.3 子View拖拽树灶,在松手后回到原位

效果展示:

回彈效果

這里需要重寫ViewGroup的computeScroll()方法纤怒,并且調(diào)用ViewDragHelper的continueSettling()方法對(duì)子View滾動(dòng)位置實(shí)時(shí)刷新。
除此之外破托,還需要在tryCaptureView()的時(shí)候記錄子View的初始lefttop肪跋,并且調(diào)用ViewDragHelpersettleCapturedViewAt()方法對(duì)子View進(jìn)行復(fù)位。

示例代碼是復(fù)位子View的id為tv1的View:

ViewGroup:
對(duì)子View的位置進(jìn)行判斷土砂,并不斷刷新州既。

@Override
public void computeScroll() {
    if(mViewDragHelper.continueSettling(true)){
        invalidate();
    }
    super.computeScroll();
}

Callback:

//捕獲View時(shí)記錄其left和top
@Override
public boolean tryCaptureView(View child, int pointerId) {
    if(child.getId() == R.id.tv1){
        originLeft = child.getLeft();
        originTop = child.getTop();
    }

    return true;
}

//松手時(shí)交給ViewDragHelper的settleCapturedViewAt()進(jìn)行復(fù)位。
@Override
public void onViewReleased(View releasedChild, float xvel, float yvel) {
    super.onViewReleased(releasedChild, xvel, yvel);
    if(releasedChild.getId() == R.id.tv1){
        mDragHelper.settleCapturedViewAt(originLeft,originTop);
        mViewGroup.invalidate();
    }
}

2.4 注意事項(xiàng)

在我們的舉例中萝映,如果你將我們的子控件換成Button或者將子控件的clickable設(shè)置為true(子View能消費(fèi)觸摸事件)吴叶,你會(huì)發(fā)現(xiàn)子View無法被拖拽了!原因在于在ViewGroup的onInterceptTouchEvent()方法中序臂,我們返回的是ViewDragHelper.shouldInterceptTouchEvent(ev)蚌卤,這樣會(huì)導(dǎo)致ViewGroup的onTouchEvent()不被調(diào)用,這樣就導(dǎo)致我們的ViewDragHelper.processTouchEvent()不被調(diào)用奥秆!

如果將onInterceptTouchEvent()的返回值直接為true的話逊彭,又會(huì)導(dǎo)致子View的點(diǎn)擊事件被攔截不被觸發(fā)!

查看ViewDragHelper.shouldInterceptTouchEvent(ev)的源碼:

public boolean shouldInterceptTouchEvent(MotionEvent ev) {
    final int action = MotionEventCompat.getActionMasked(ev);
    switch (action) {
        ...
        case MotionEvent.ACTION_MOVE: {          
            final int pointerCount = ev.getPointerCount();
            for (int i = 0; i < pointerCount; i++) {           
                final int horizontalDragRange = mCallback.getViewHorizontalDragRange(
                            toCapture);
                final int verticalDragRange = mCallback.getViewVerticalDragRange(toCapture);
                // 如果getViewHorizontalDragRange和getViewVerticalDragRange的返回值都為0构订,則break
                if (horizontalDragRange == 0 && verticalDragRange == 0) {
                    break;
                }
                
                // tryCaptureViewForDrag方法中會(huì)設(shè)置mDragState=STATE_DRAGGING
                if (pastSlop && tryCaptureViewForDrag(toCapture, pointerId)) {
                    break;
                }
            }
            break;
        }
    }
    ...
    return mDragState == STATE_DRAGGING;
}

ViewDragHelper.shouldInterceptTouchEvent(ev)的返回為true的條件是ViewDragHelper.mDragState == STATE_DRAGGING侮叮,然而mDragState是在tryCaptureViewForDrag()方法中被設(shè)置為STATE_DRAGGING的。其中悼瘾,若horizontalDragRangeverticalDragRange一直為0囊榜,則mDragState無法設(shè)置為STATE_DRAGGING。并且horizontalDragRangeverticalDragRange是在ViewDragHelper.getViewVerticalDragRange()ViewDragHelper.getViewHorizontalDragRange()設(shè)置的亥宿,因此卸勺,只要這兩個(gè)方法的返回值大于0就可以正常捕獲了!

    @Override
    public int getViewVerticalDragRange(View child) {
        return 1;
    }

    /**
     * 這個(gè)用來控制橫向移動(dòng)的邊界范圍烫扼,單位是像素曙求。
     * @param child
     * @return 返回值大于0時(shí)才能捕獲子View
     */
    @Override
    public int getViewHorizontalDragRange(View child) {
        return 1;
    }

2.5 ViewDragHelper的一些其它回調(diào)及回調(diào)調(diào)用順序

2.5.1 其它回調(diào)方法

/**
 * mDragState改變時(shí)回調(diào)
 * STATE_IDLE:所有的View處于靜止空閑狀態(tài)
 * STATE_DRAGGING:某個(gè)View正在被用戶拖動(dòng)(用戶正在與設(shè)備交互)
 * STATE_SETTLING:某個(gè)View正在安置狀態(tài)中(用戶并沒有交互操作),就是自動(dòng)滾動(dòng)的過程中
 * @param state
 */
@Override
public void onViewDragStateChanged(int state) {
    super.onViewDragStateChanged(state);

}

/**
 * 當(dāng)捕獲的子View的位置發(fā)生改變時(shí)回調(diào)
 * @param changedView
 * @param left
 * @param top
 * @param dx
 * @param dy
 */
@Override
public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
    super.onViewPositionChanged(changedView, left, top, dx, dy);
}

/**
 * 當(dāng)子View被捕獲時(shí)回調(diào)
 * @param capturedChild
 * @param activePointerId
 */
@Override
public void onViewCaptured(View capturedChild, int activePointerId) {
    super.onViewCaptured(capturedChild, activePointerId);
}

/**
 * 當(dāng)觸摸到邊界時(shí)回調(diào)。
 * @param edgeFlags
 * @param pointerId
 */
@Override
public void onEdgeTouched(int edgeFlags, int pointerId) {
    super.onEdgeTouched(edgeFlags, pointerId);
}

/**
 *
 * @param edgeFlags
 * @return true的時(shí)候會(huì)鎖住當(dāng)前的邊界圆到,false則unLock怎抛。
 */
@Override
public boolean onEdgeLock(int edgeFlags) {
    return super.onEdgeLock(edgeFlags);
}

/**
 * 改變同一個(gè)坐標(biāo)(x,y)去尋找captureView位置的方法。(具體作用暫時(shí)未知)
 * @param index
 * @return
 */
@Override
public int getOrderedChildIndex(int index) {
    return super.getOrderedChildIndex(index);
}

2.5.2 回調(diào)調(diào)用順序:

shouldInterceptTouchEvent:

  • ACTION_DOWNgetOrderedChildIndex(findTopChildUnder) > onEdgeTouched()
  • ACTION_MOVEgetOrderedChildIndex(findTopChildUnder) > getViewHorizontalDragRange() & getViewVerticalDragRange(checkTouchSlop)(MOVE中可能不止一次) > clampViewPositionHorizontal() & clampViewPositionVertical() > onEdgeDragStarted() > tryCaptureView() > onViewCaptured() > onViewDragStateChanged()

processTouchEvent:

  • ACTION_DOWNgetOrderedChildIndex(findTopChildUnder) > tryCaptureView() > onViewCaptured() > onViewDragStateChanged() > onEdgeTouched()
  • ACTION_MOVESTATE == DRAGGING > STATE!=DRAGGING > onEdgeDragStarted > getOrderedChildIndex(findTopChildUnder) > getViewHorizontalDragRange& getViewVerticalDragRange(checkTouchSlop) > tryCaptureView() > onViewCaptured() > onViewDragStateChanged()

2.6 小結(jié)

ViewDragHelper是一個(gè)強(qiáng)大的拖拽子View的輔助神器芽淡,大家用好這個(gè)類,可以大大提高我們的觸摸手勢(shì)的效率豆赏!

3挣菲、ItemTouchHelper(item觸摸幫手)

ItemTouchHelper是一個(gè)觸摸item時(shí)對(duì)item進(jìn)行處理的類。既然涉及到條目掷邦,那肯定是和一些和item有關(guān)的控件有關(guān)了白胀,沒錯(cuò),這個(gè)類就是一個(gè)針對(duì)RecyclerView的工具類抚岗,用來處理RecyclerView內(nèi)部的條目拖拽和滑動(dòng)事件的或杠,它可以讓條目拖拽到一個(gè)新的位置,也可以讓條目滑動(dòng)出來刪除等等場(chǎng)景宣蔚!

3.1 相關(guān)方法和接口介紹

ItemTouchHelper類的具體實(shí)現(xiàn)方法都在它的內(nèi)部類ItemTouchHelper.Callback中向抢,ItemTouchHelper.Callback中有三個(gè)必須重寫的方法,也是核心方法胚委。
我寫了一個(gè)類MyItemTouchHelperCallback繼承ItemTouchHelper.Callback挟鸠,它們的意義都在注釋中已標(biāo)明:

public static class MyItemTouchHelperCallback extends ItemTouchHelper.Callback{

    //RecyclerView的Adapter
    private ItemTouchHelperAdapter mAdapter;

    public MyItemTouchHelperCallback(ItemTouchHelperAdapter adapter){
        this.mAdapter = adapter;
    }

    /**
     * 允許哪個(gè)方向的拖拽和滑動(dòng),一般配合makeMovementFlags(int,int)去使用
     * @param recyclerView
     * @param viewHolder
     * @return
     */
    @Override
    public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
        //允許上下拖拽亩冬,和從右向左滑動(dòng)
        int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
        int swipeFlags = ItemTouchHelper.LEFT;
        //計(jì)算拖拽和滑動(dòng)的方向
        return makeMovementFlags(dragFlags,swipeFlags);
    }

    /**
     * 拖拽一個(gè)item到新位置時(shí)會(huì)調(diào)用此方法艘希,一般配合Adapter的notifyItemMoved()方法來交換兩個(gè)ViewHolder的位置
     * @param recyclerView
     * @param viewHolder
     * @param target
     * @return true表示已經(jīng)到達(dá)移動(dòng)目的地
     */
    @Override
    public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
        //交換item位置
        Collections.swap(mAdapter.mDatas,viewHolder.getAdapterPosition(),target.getAdapterPosition());
        mAdapter.notifyItemMoved(viewHolder.getAdapterPosition(),target.getAdapterPosition());
        return true;
    }

    /**
     * 當(dāng)滑動(dòng)到一定程度時(shí),松手會(huì)繼續(xù)滑動(dòng)硅急,然后調(diào)用此方法覆享,反之item會(huì)回到原位,不調(diào)用此方法
     * @param viewHolder
     * @param direction 滑動(dòng)的方向
     */
    @Override
    public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
        Log.e("zw", "direction : " + direction);
        //刪除此item
        mAdapter.mDatas.remove(viewHolder.getAdapterPosition());
        mAdapter.notifyItemRemoved(viewHolder.getAdapterPosition());
    }
}

ItemTouchHelper.Callback中其它可以選擇重寫的方法:

    /**
     * 是否長(zhǎng)按時(shí)才使拖拽生效
     * @return 默認(rèn)返回true
     */
    @Override
    public boolean isLongPressDragEnabled() {
        return super.isLongPressDragEnabled();
    }

    /**
     * 是否可以進(jìn)行滑動(dòng)時(shí)的刪除動(dòng)作营袜,也就是是否能調(diào)用onSwiped()方法
     * @return 默認(rèn)返回true
     */
    @Override
    public boolean isItemViewSwipeEnabled() {
        return super.isItemViewSwipeEnabled();
    }

    /**
     * 靜止?fàn)顟B(tài)變?yōu)橥献Щ蛘呋瑒?dòng)的時(shí)候會(huì)回調(diào)
     * @param viewHolder
     * @param actionState 當(dāng)前的狀態(tài)
     */
    @Override
    public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) {
        super.onSelectedChanged(viewHolder, actionState);
    }

    /**
     * 當(dāng)用戶操作完畢某個(gè)item并且其動(dòng)畫也結(jié)束后會(huì)調(diào)用該方法撒顿,一般我們?cè)谠摲椒▋?nèi)恢復(fù)ItemView的初始狀態(tài),防止由于復(fù)用而產(chǎn)生的顯示錯(cuò)亂問題连茧。
     * @param recyclerView
     * @param viewHolder
     */
    @Override
    public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
        super.clearView(recyclerView, viewHolder);
    }

    /**
     * 實(shí)現(xiàn)我們自定義的交互規(guī)則或者自定義的動(dòng)畫效果
     * @param c
     * @param recyclerView
     * @param viewHolder
     * @param dX
     * @param dY
     * @param actionState
     * @param isCurrentlyActive
     */
    @Override
    public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {
        super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
    }

還有一些方法沒有做過多研究核蘸,這里就不列舉了。

注意事項(xiàng):調(diào)用onSwiped()方法時(shí)啸驯,如果沒有配合RecyclerViewAdapternotifyItemRemoved()方法則會(huì)讓Item的內(nèi)容給移除出去客扎,但是此Item還在原地,也就是會(huì)留下一個(gè)空白的view罚斗!

3.2 ItemTouchHelper的使用方式

3.2.1 RecyclerView初始化

實(shí)現(xiàn)RecyclerViewAdapterViewHolder類徙鱼,RecyclerView設(shè)置LayoutManager等,很常規(guī)的寫法,這里就不貼代碼了袱吆。

3.2.1 RecyclerView設(shè)置ItemTouchHelper

ItemTouchHelperAdapter.MyItemTouchHelperCallback touchHelperCallback
        = new ItemTouchHelperAdapter.MyItemTouchHelperCallback(adapter);

ItemTouchHelper itemTouchHelper = new ItemTouchHelper(touchHelperCallback);

itemTouchHelper.attachToRecyclerView(recyclerView);

3.3 ItemTouchHelper的使用場(chǎng)景簡(jiǎn)介

3.3.1 實(shí)現(xiàn)上下拖拽換位置功能和向左滑動(dòng)刪除功能

此功能需要重寫ItemTouchHelper.CallbackgetMovementFlags()方法厌衙、onMove()方法和onSwiped()方法,并且要配合RecyclerViewAdapternotifyItemMoved()notifyItemRemoved()方法使用绞绒。

ItemTouchHelper.Callback關(guān)鍵代碼:

    //RecyclerView的Adapter
    private ItemTouchHelperAdapter mAdapter;

    public MyItemTouchHelperCallback(ItemTouchHelperAdapter adapter){
        this.mAdapter = adapter;
    }

    /**
     * 允許哪個(gè)方向的拖拽和滑動(dòng)婶希,一般配合makeMovementFlags(int,int)去使用
     * @param recyclerView
     * @param viewHolder
     * @return
     */
    @Override
    public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
        //允許上下拖拽,和從右向左滑動(dòng)
        int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
        int swipeFlags = ItemTouchHelper.LEFT;
        //計(jì)算拖拽和滑動(dòng)的方向
        return makeMovementFlags(dragFlags,swipeFlags);
    }

    /**
     * 拖拽一個(gè)item到新位置時(shí)會(huì)調(diào)用此方法蓬衡,一般配合Adapter的notifyItemMoved()方法來交換兩個(gè)ViewHolder的位置
     * @param recyclerView
     * @param viewHolder
     * @param target
     * @return true表示已經(jīng)到達(dá)移動(dòng)目的地
     */
    @Override
    public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
        //交換item位置
        Collections.swap(mAdapter.mDatas,viewHolder.getAdapterPosition(),target.getAdapterPosition());
        mAdapter.notifyItemMoved(viewHolder.getAdapterPosition(),target.getAdapterPosition());
        return true;
    }

    /**
     * 當(dāng)滑動(dòng)到一定程度時(shí)喻杈,松手會(huì)繼續(xù)滑動(dòng),然后調(diào)用此方法狰晚,反之item會(huì)回到原位筒饰,不調(diào)用此方法
     * @param viewHolder
     * @param direction 滑動(dòng)的方向
     */
    @Override
    public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
        Log.e("zw", "direction : " + direction);
        //刪除此item
        mAdapter.mDatas.remove(viewHolder.getAdapterPosition());
        mAdapter.notifyItemRemoved(viewHolder.getAdapterPosition());
    }

實(shí)現(xiàn)效果:

拖拽換位置和滑動(dòng)刪除功能

3.3.1 向左滑動(dòng)刪除時(shí)帶有動(dòng)畫功能(透明度)

此示例為向左滑動(dòng)時(shí),先顯示黑色的View壁晒,當(dāng)黑色的TextView顯示完全時(shí)瓷们,再向右滑動(dòng)時(shí),更改黑色TextView的文字顏色透明度秒咐。

先上顯示效果:

帶動(dòng)畫的效果

更改我們的布局文件谬晕,在item布局的屏幕右邊放置我們的TextView,布局文件如下:

<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <TextView
        android:layout_width="0dp"
        android:layout_height="50dp"
        android:padding="10dp"
        android:gravity="center"
        android:id="@+id/tv_item"
        android:textStyle="bold"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintWidth_percent="0.5"
        />

    <TextView
        android:id="@+id/tv_item1"
        android:layout_width="wrap_content"
        android:layout_height="50dp"
        android:background="#000000"
        android:gravity="center"
        android:padding="10dp"
        android:text="繼續(xù)滑動(dòng)刪除"
        android:textColor="@android:color/white"
        android:textStyle="bold"
        app:layout_constraintLeft_toRightOf="parent"
         />

</android.support.constraint.ConstraintLayout>

其中用到了ConstraintLayout反镇,這個(gè)控件對(duì)于布局十分的方便固蚤,并且性能也很優(yōu)秀,關(guān)于此控件的更多使用方法可以參考我這篇博客內(nèi)容: ConstraintLayout——約束性布局學(xué)習(xí)

這時(shí)候我們會(huì)發(fā)現(xiàn)向右側(cè)滑動(dòng)時(shí)歹茶,第二個(gè)TextView根本出不來夕玩,系統(tǒng)調(diào)用的是當(dāng)前屏幕可見部分布局的setTranslationX()方法,因此無論如何惊豺,我們的第二個(gè)TextVeiw都不會(huì)顯示燎孟,因此我們要將setTranslationX()改變成scrollTo()方法。

另外尸昧,我們還要重寫clearView()方法揩页,來將有動(dòng)畫效果的條目給復(fù)位到初始化狀態(tài),以免復(fù)用的時(shí)候產(chǎn)生顯示誤差烹俗。

ItemTouchHelper.Callback關(guān)鍵代碼:

@Override
public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {
    //僅僅針對(duì)Item的滑動(dòng)事件
    if(actionState == ItemTouchHelper.ACTION_STATE_SWIPE){
        TextView alphaView = viewHolder.itemView.findViewById(R.id.tv_item1);
        int viewWidth = alphaView.getWidth();
        if(Math.abs(dX) < viewWidth){
            //滾動(dòng)
            viewHolder.itemView.scrollTo((int) -dX,0);

        } else if(Math.abs(dX) < recyclerView.getWidth() / 2){
            //滑動(dòng)的透明度
            float childAlpha = Math.abs(dX) - viewWidth;
            float fatherAlpha = recyclerView.getWidth() / 2 - viewWidth;
            float alpha = childAlpha / fatherAlpha;

            int color = Color.argb((int)(alpha * 255),255,255,255);
            alphaView.setTextColor(color);
        }
    } else {
        super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
    }
}

@Override
public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
    super.clearView(recyclerView, viewHolder);
    //復(fù)位
    viewHolder.itemView.setScrollX(0);
    TextView alphaView = viewHolder.itemView.findViewById(R.id.tv_item1);
    alphaView.setTextColor(Color.parseColor("#00FFFFFF"));
}

3.4 小結(jié)

ItemTouchHelper是RecyclerView自帶的一個(gè)幫助我們實(shí)現(xiàn)拖拽和滑動(dòng)的工具類爆侣,通過這個(gè)類我們可以很輕松的實(shí)現(xiàn)在RecyclerView上炫酷的拖拽和滑動(dòng)效果!

總結(jié)

上面介紹了三種和觸摸有關(guān)的輔助類:

  • GestureDetector:用來監(jiān)聽我們觸摸屏幕的手勢(shì)幢妄。

  • ViewDragHelper:幫助我們處理子控件的拖拽事件兔仰。

  • ItemTouchHelper:RecyclerView上幫助我們拖拽和滑動(dòng)子條目的利器。

相信通過上述的例子蕉鸳,你應(yīng)該也學(xué)會(huì)了怎么使用它們乎赴,在工作中用上這些輔助類忍法,可以讓我們的工作效率大大提高,實(shí)現(xiàn)事半功倍的效果榕吼!

最后饿序,本示例所有效果,見此GitHub Demo

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末羹蚣,一起剝皮案震驚了整個(gè)濱河市原探,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌度宦,老刑警劉巖踢匣,帶你破解...
    沈念sama閱讀 218,682評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異戈抄,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)后专,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門划鸽,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人戚哎,你說我怎么就攤上這事裸诽。” “怎么了型凳?”我有些...
    開封第一講書人閱讀 165,083評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵丈冬,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我甘畅,道長(zhǎng)埂蕊,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,763評(píng)論 1 295
  • 正文 為了忘掉前任疏唾,我火速辦了婚禮蓄氧,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘槐脏。我一直安慰自己喉童,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,785評(píng)論 6 392
  • 文/花漫 我一把揭開白布顿天。 她就那樣靜靜地躺著堂氯,像睡著了一般。 火紅的嫁衣襯著肌膚如雪牌废。 梳的紋絲不亂的頭發(fā)上咽白,一...
    開封第一講書人閱讀 51,624評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音畔规,去河邊找鬼局扶。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的三妈。 我是一名探鬼主播畜埋,決...
    沈念sama閱讀 40,358評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼畴蒲!你這毒婦竟也來了悠鞍?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,261評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤模燥,失蹤者是張志新(化名)和其女友劉穎咖祭,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體蔫骂,經(jīng)...
    沈念sama閱讀 45,722評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡么翰,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了辽旋。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片浩嫌。...
    茶點(diǎn)故事閱讀 40,030評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖补胚,靈堂內(nèi)的尸體忽然破棺而出码耐,到底是詐尸還是另有隱情,我是刑警寧澤溶其,帶...
    沈念sama閱讀 35,737評(píng)論 5 346
  • 正文 年R本政府宣布骚腥,位于F島的核電站,受9級(jí)特大地震影響瓶逃,放射性物質(zhì)發(fā)生泄漏束铭。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,360評(píng)論 3 330
  • 文/蒙蒙 一金闽、第九天 我趴在偏房一處隱蔽的房頂上張望纯露。 院中可真熱鬧,春花似錦代芜、人聲如沸埠褪。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,941評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽钞速。三九已至,卻和暖如春嫡秕,著一層夾襖步出監(jiān)牢的瞬間渴语,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,057評(píng)論 1 270
  • 我被黑心中介騙來泰國(guó)打工昆咽, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留驾凶,地道東北人牙甫。 一個(gè)月前我還...
    沈念sama閱讀 48,237評(píng)論 3 371
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像调违,于是被迫代替她去往敵國(guó)和親窟哺。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,976評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • ViewDragHelper實(shí)例的創(chuàng)建 ViewDragHelper重載了兩個(gè)create()靜態(tài)方法public...
    傀儡世界閱讀 661評(píng)論 0 3
  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,162評(píng)論 25 707
  • 前言 在自定義ViewGroup中技肩,很多效果都包含用戶手指去拖動(dòng)其內(nèi)部的某個(gè)View(eg:側(cè)滑菜單等)且轨,針對(duì)具體...
    浪人_天涯閱讀 884評(píng)論 0 3
  • 改變是從自己開始,你變了虚婿,一切就變了
    陽光創(chuàng)客敖偉偉閱讀 159評(píng)論 0 0
  • 關(guān)鍵詞:帶動(dòng)率旋奢、后背形態(tài) 一.時(shí)間步:1.初級(jí)到高級(jí)的進(jìn)階過程:膝蓋帶動(dòng)腳—跨位帶動(dòng)膝蓋---身體帶動(dòng)跨位;2.身...
    性感有才的郭郭閱讀 830評(píng)論 0 1