【Android View事件(四)】View滑動與實(shí)現(xiàn)滑動的幾種方法

【大圣代的技術(shù)專欄 http://blog.csdn.net/qq_23191031 轉(zhuǎn)載煩請注明出處,尊重他人勞動成功就是對您自己的尊重】

相關(guān)文章
詳解Android控件體系與常用坐標(biāo)系
Android常用觸控類分析:MotionEvent 、 ViewConfiguration、VelocityTracker
Android View事件(二)詳解事件分發(fā)機(jī)制

一, 前言

在前面的幾篇文章,我向大家介紹的都是單一View事件,而在這篇文章中,我將向大家介紹連續(xù)的事件 —— 滑動。
在安卓設(shè)備上滑動幾乎是應(yīng)用的標(biāo)配,由于安卓手機(jī)屏幕較小,為了給用戶呈現(xiàn)更多的內(nèi)容,就需要使用滑動來隱藏和顯示一些內(nèi)容。學(xué)習(xí)View的滑動對于自定義控件的掌握、日常滑動沖突的處理都有很多裨益。為了很好的理解滑動事件,掌握Android坐標(biāo)系與觸控事件就變得格外重要,在此強(qiáng)烈建議先閱讀上面的提到的幾篇相關(guān)文章打好基礎(chǔ)。

二, View滑動產(chǎn)生的原理

從原理上講View滑動的本質(zhì)就是隨著手指的運(yùn)動不斷地改變坐標(biāo)。當(dāng)觸摸事件傳到View時,系統(tǒng)記下觸摸點(diǎn)的坐標(biāo),手指移動時系統(tǒng)記下移動后的觸摸的坐標(biāo)并算出偏移量,并通過偏移量來修改View的坐標(biāo),不斷的重復(fù)這樣的過程,從而實(shí)現(xiàn)滑動過程。

三,實(shí)現(xiàn)滑動的7種方法

在學(xué)習(xí)Android坐標(biāo)系和觸控事件之后我們就可以看看系統(tǒng)為開發(fā)者提供了那些方法來實(shí)現(xiàn)滑動效果吧!

3.0 代碼實(shí)例介紹

為了讓大家更好的理解過程,設(shè)計(jì)如下代碼實(shí)例

代碼結(jié)構(gòu)
主要布局
CustomView具體結(jié)構(gòu)

MainActivity中沒有任何改動,而CustomView只是繼承了View,并重寫了 onToucnEvent()方法。

可以看到代碼很簡單,這篇文章中我就不提供項(xiàng)目地址了。

3.1 layout 方法

在View繪制的時候,系統(tǒng)都會調(diào)用layout(int l, int t, int r, int b)方法來確定View的具體位置。系統(tǒng)既然是這樣來設(shè)置View的位置的,那么我們也可以通過調(diào)用layout(int l, int t, int r, int b)`方法修改left,top,right,bottom這四個屬性來控制View的位置。

    // 視圖坐標(biāo)方式
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        int x = (int) event.getX();
        int y = (int) event.getY();
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                // 記錄觸摸點(diǎn)坐標(biāo)
                lastX = x;
                lastY = y;
                break;
            case MotionEvent.ACTION_MOVE:
                // 計(jì)算偏移量
                int offsetX = x - lastX;
                int offsetY = y - lastY;
                // 在當(dāng)前l(fā)eft、top、right、bottom的基礎(chǔ)上加上偏移量
                layout(getLeft() + offsetX,
                        getTop() + offsetY,
                        getRight() + offsetX,
                        getBottom() + offsetY);
                break;
        }
        return true;
    }

當(dāng)然使用 getX()、getY()方法和使用getRawX()geRawtY()的效果是一樣的,只不過前者使用的是相對位置,而后者使用的是絕對位置。
但是要注意,在使用絕對坐標(biāo)系的時候,每次執(zhí)行完 ACTION_MOVE的邏輯后,一定要重新設(shè)置初始坐標(biāo),這樣才能獲得準(zhǔn)確的偏移量。

     case MotionEvent.ACTION_MOVE:
                // 計(jì)算偏移量
              …………
  
              //重新設(shè)置初始坐標(biāo)
              x = (int)(event.getRawX());
              y = (int)(event.getRawY());
                break;

3.2 offsetLeftAndRight() 與 offsetTopAndroidBottom()

    // 視圖坐標(biāo)方式
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        int x = (int) event.getX();
        int y = (int) event.getY();
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                // 記錄觸摸點(diǎn)坐標(biāo)
                lastX = x;
                lastY = y;
                break;
            case MotionEvent.ACTION_MOVE:
                // 計(jì)算偏移量
                int offsetX = x - lastX;
                int offsetY = y - lastY
                //同時對于left 和 right進(jìn)行偏移
                offsetLeftAndRight(offsetX);
                //同時對于top 和 bottom進(jìn)行偏移
                offsetTopAndBottom(offsetY);
                break;
        }
        return true;
    }

3.3 LayoutParams

LayoutParams類是子View向父View傳遞位置意圖的橋梁,告訴父View他想要變成的樣子。所以我們可以通過LayoutParams中的參數(shù)來改變View的位置。

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        int startX = (int) event.getX();
        int startY = (int) event.getY();
        int lastX = 0;
        int lastY = 0;

        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                lastX = startX;
                lastY = startY;
                Log.v(TAG, "startX   " + startX + "    startY      " + startY);
                break;
            case MotionEvent.ACTION_MOVE:
                Log.v(TAG, "offsetX ---  " + startX + "    offsetY ---     " + startY);
                int offsetX = startX - lastX;
                int offsetY = startY - lastY;
                LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) getLayoutParams();
                layoutParams.leftMargin = getLeft() + offsetX;
                layoutParams.topMargin = getTop() + offsetY;
                setLayoutParams(layoutParams);
                break;
        }
        //注明消費(fèi)此事件,不然無效果
        return true;
    }

注意:

在獲得 LayoutParams對象的時候,需要將其轉(zhuǎn)換成直接父View(這個View的上一層布局)的類型,不然會報(bào)錯。
例如,將

    LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) getLayoutParams();

改為錯誤的:

    RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) getLayoutParams();

報(bào)錯如下:

3.4 scrollTo與ScrollBy

在View中系統(tǒng)為開發(fā)者提供了兩個關(guān)于移動的方法: scrollTo 與 scroolBy。其實(shí)這兩個方法非常好理解,單從字面上的意思就知道

scrollTo(x,y) : 移動到 (x,y) 這個坐標(biāo)點(diǎn)
scrlollBy(dx,dy) : 移動的增量為 dx,dy。

我們對于原有代碼進(jìn)行如下更改

但是,當(dāng)我們拖動View的時候卻沒有移動?。?!這是為什么呢?
其實(shí)View是移動了地,只不過和我們設(shè)想的結(jié)果不同。scrollTo()、scrollBy()表示的是 移動當(dāng)前ViewGroup的所有子View,如果在View中使用,那么移動的就是View的內(nèi)容(content)。例如TextView它的content就是文本。這就解釋了為什么我們的代碼看不到效果了。

將原有代碼更改為如下所示:

  ((ViewGroup) getParent()).scrollBy(offsetX, offsetY);

這回的確是動了,但是他卻在亂動。并不是像我們想象中的那樣跟隨著手指的移動而移動。

導(dǎo)致這個的原因是什么呢? 答案請見《Scroll類源碼分析與應(yīng)用》第一節(jié) scrollTo與ScrollBy

3.5 Scroller

請見《Scroll類源碼分析與應(yīng)用》 第二節(jié)Scroller

關(guān)于屬性動畫、ViewDragHelper

二者也是實(shí)現(xiàn)View滑動的良好辦法,但是他們都有一定的復(fù)雜性直接展開不僅顯得突兀、而且篇幅較大不利于學(xué)習(xí),所以屬性動畫與ViewDragHelper我都會以單獨(dú)的篇幅展開,方便同學(xué)們更好的理解與學(xué)習(xí),敬請期待

版權(quán)聲明:
禁止一切商業(yè)行為,轉(zhuǎn)載請著名出處 http://blog.csdn.net/qq_23191031。作者: 大圣代
Copyright (c) 2017 代圣達(dá). All rights reserved.

最后編輯于
?著作權(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ù)。

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

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