Android Scroll滑動(dòng)

一、為什么要學(xué)會(huì)Scroll

我們?cè)趯懽远xView的時(shí)候,避免不了要讓用戶能夠和這個(gè)View進(jìn)行交互,而因?yàn)锳ndroid以觸摸作為主要的操作手段,那么明白如何去自作一個(gè)可滑動(dòng)的的View就非常有必要了。
例如ListView、ViewPager等官方提供的View均實(shí)現(xiàn)了這以交互方式。


二、坐標(biāo)

要制作一個(gè)可滑動(dòng)的View,我們首先要明白Android系統(tǒng)中的坐標(biāo)系是怎樣的一個(gè)定義。

1、Android坐標(biāo)系(絕對(duì)坐標(biāo))

Android的坐標(biāo)系分為兩種,其中一種叫做Android坐標(biāo)系,或者稱為絕對(duì)坐標(biāo)可能更為合適。它以手機(jī)屏幕的左上角的頂點(diǎn)作為坐標(biāo)系的原點(diǎn)。該點(diǎn)向右是X軸,向下既是Y軸。在onTouchEvent事件中可以通過getRawX()和getRawY()來分別獲得觸發(fā)該事件的View相對(duì)于絕對(duì)坐標(biāo)系的X軸和Y軸坐標(biāo)。

2、視圖坐標(biāo)系(相對(duì)坐標(biāo))

Android坐標(biāo)系的另一種叫視圖坐標(biāo)系,它的坐標(biāo)原點(diǎn)永遠(yuǎn)是直接父節(jié)點(diǎn)(parentView)的左上角。所以也可以稱它為相對(duì)坐標(biāo)。onTouchEvent中可通過getX()和getY()分別獲得X軸和Y軸坐標(biāo)。


三、獲取坐標(biāo)

獲取坐標(biāo)的方法可以分為兩類:

1、View提供的獲取坐標(biāo)的方法。

getTop():獲取到的是View自身的頂邊到父節(jié)點(diǎn)頂邊的距離。
getBootom():獲取到的是View自身的底邊到父節(jié)點(diǎn)頂邊的距離。
getLeft():獲取到的是View自身的左邊到父節(jié)點(diǎn)左邊的距離。
getRight():獲取到的是View自身的右邊到父節(jié)點(diǎn)左邊的距離。

2、MotionEvent提供的方法(既觸摸事件中)

getX():獲取到的是觸摸點(diǎn)相對(duì)父節(jié)點(diǎn)的X軸相對(duì)坐標(biāo)。
getY():獲取到的是觸摸點(diǎn)相對(duì)父節(jié)點(diǎn)的Y軸相對(duì)坐標(biāo)。
getRawX():獲取到的是觸摸點(diǎn)相對(duì)屏幕的X軸絕對(duì)坐標(biāo)。
getRawY():獲取到的是觸摸點(diǎn)相對(duì)屏幕的Y軸絕對(duì)坐標(biāo)。


四、實(shí)現(xiàn)滑動(dòng)的幾種方法

1、layout方法

調(diào)用layout方法去重新布局這個(gè)View。layout的各個(gè)參數(shù)為當(dāng)前View的left、top、right、bottom加上我們收支滑動(dòng)的偏移量。下列代碼是一個(gè)View跟隨手指移動(dòng)的例子:

    @Override  
    public boolean onTouchEvent(MotionEvent event) {
    int x = (int) event.getX();
    int y = (int) event.getY();
    switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            downX = x;
            downY = y;
            break;
        case MotionEvent.ACTION_MOVE:
            int offsetX = x - downX;
            int offsetY = y - downY;
            layout(getLeft() + offsetX, getTop() + offsetY, getRight() + offsetX, getBottom() + offsetY);
            break;
        case MotionEvent.ACTION_UP:
            break;
    }

    return true;
}  

2、使用offsetLeftAndRight與offsetTopAndBottom兩個(gè)方法

這兩個(gè)方法為系統(tǒng)提供的一個(gè)專門用來移動(dòng)View的Api。只需要將計(jì)算出的偏移量作為參數(shù)即可。所以使用這兩個(gè)方法可將上面的例子改為如下:

@Override
public boolean onTouchEvent(MotionEvent event) {
    int x = (int) event.getX();
    int y = (int) event.getY();
    switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            downX = x;
            downY = y;
            break;
        case MotionEvent.ACTION_MOVE:
            int offsetX = x - downX;
            int offsetY = y - downY;
         offsetLeftAndRight(offsetX);
         offsetTopAndBottom(offsetY);
            break;
        case MotionEvent.ACTION_UP:
            break;
    }

    return true;
}  

3、LayoutParams(偏方,不建議使用,不夠精確)

我們知道LayoutParams里面封裝了一個(gè)View的布局參數(shù),而里面剛好有包含margin值。所以我們可以通過修改margin來達(dá)到移動(dòng)的目的。但是移動(dòng)的效果并不好,很不精確。代買如下:

   @Override
public boolean onTouchEvent(MotionEvent event) {
    int x = (int) event.getX();
    int y = (int) event.getY();
    switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            downX = x;
            downY = y;
            break;
        case MotionEvent.ACTION_MOVE:
            int offsetX = x - downX;
            int offsetY = y - downY;
         
            ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams) getLayoutParams();
            layoutParams.leftMargin = getLeft() + offsetX;
            layoutParams.topMargin = getTop() + offsetY;
            setLayoutParams(layoutParams);
            break;
        case MotionEvent.ACTION_UP:
            break;
    }

    return true;
}  

4、scrollTo和scrollBy

這種呢,和前面的都不太相同。首先我們要明白,這兩個(gè)方法的區(qū)別,以及他們所移動(dòng)的到底是什么東西。

(1)兩個(gè)方法間的區(qū)別

首先scrollTo(x,y),這個(gè)方法呢,兩個(gè)參數(shù)分別為準(zhǔn)確的x、y軸坐標(biāo),你給它什么坐標(biāo)它就移動(dòng)到哪。

而scrollBy(dx,dy)呢,看參數(shù)名就知道是距離,也就是說它會(huì)在原來的x、y的基礎(chǔ)值上去增量移動(dòng)。打個(gè)比方,一個(gè)View受到scrollTo的作用移動(dòng)到了(100,100)的一個(gè)點(diǎn)。之后再次被scrollBy作用增量移動(dòng)(20,20)。那么此時(shí)這個(gè)View處在一個(gè)什么位置呢?答案是(120,120),因?yàn)閷?shí)在(100,100)這個(gè)原值上增量移動(dòng)了(20,20)。

以上就是這兩個(gè)方法之間的區(qū)別

(2)這兩個(gè)方法所移動(dòng)的對(duì)象是什么。

前面有提到,這種移動(dòng)方式和前面的幾個(gè)都不相同,前面幾個(gè)都是直接移動(dòng)的View本身;
而這兩個(gè)方法所移動(dòng)實(shí)際上是View的顯示區(qū)域。

打個(gè)比方:有一個(gè)望遠(yuǎn)鏡,你可以通過望遠(yuǎn)鏡看到這個(gè)世界,但是世界是很大很大的,望遠(yuǎn)鏡只能讓你看到這有限一小部分,如果你想看右邊點(diǎn)的,就要把望遠(yuǎn)鏡往右移動(dòng),想看上邊點(diǎn)的,就往上移。其他方向同理。
那么這個(gè)望遠(yuǎn)鏡的可視區(qū)域就是我們的View或者ViewGroup的可視區(qū)域。
而世界呢,就是View或ViewGroup背后所藏著的那一整個(gè)顯示內(nèi)容,非常非常大,只是你要去移動(dòng)我們的可視區(qū)域才能夠看到。

舉個(gè)栗子:
我有一個(gè)ViewGroup,這個(gè)ViewGroup里有一個(gè)TextView。我調(diào)用了這個(gè)ViewGroup的scrollBy方法移動(dòng)了(100,0)。但是呢這時(shí)并不是ViewGroup移動(dòng)了,而是這個(gè)ViewGroup所顯示出來的區(qū)域被移動(dòng)了。所以TextView應(yīng)該是到了更左邊的距離去了.
那同理,我去調(diào)用了TextView的scrollBy方法移動(dòng)(-20,0)。這時(shí)會(huì)是誰移動(dòng)了呢?是TextView中的顯示區(qū)域被移動(dòng)了(-20,0)。所以文字應(yīng)該是到了更右邊的位置去了。

(3)他們的移動(dòng)方向。

scrollTo和scrollBy的移動(dòng)方向我想大概是學(xué)習(xí)scroll最容易模糊的一個(gè)部分了,最初即便我在網(wǎng)上看了各種相關(guān)描述的圖之后我也是有迷迷糊糊。
實(shí)際上首先呢,你要記住前面所說的scrollTo和scrollBy這兩個(gè)方法所移動(dòng)的東西實(shí)際上是View的顯示區(qū)域,而不是當(dāng)前View本身。

舉個(gè)栗子:
我又有一個(gè)ViewGroup,里面有一個(gè)寫著helloword的TextView - - ,而這個(gè)ViewGroup會(huì)根據(jù)手指移動(dòng)的增量去調(diào)用scrollBy。我的手指向右移動(dòng)了30,此時(shí)TextView會(huì)顯示在什么位置呢?
我們分析一下,手指向右移動(dòng)了30,移動(dòng)的是顯示區(qū)域,那么此時(shí)顯示區(qū)域應(yīng)該是在(30,0)的位置,我們的TextView自然是往左偏了30啦。

ok!那如果手指向下移動(dòng)30呢?那么此時(shí)顯示區(qū)域向下移動(dòng)30,內(nèi)容當(dāng)然是向上偏了30啦。

(4)實(shí)現(xiàn)移動(dòng)。

下面就用scrollBy去實(shí)現(xiàn)跟隨手指移動(dòng)的效果。實(shí)際上知道了前面的原理之后,我們就知道了,要讓它跟隨手指移動(dòng)只要將偏移量改為負(fù)數(shù)即可。如下:

@Override
public boolean onTouchEvent(MotionEvent event) {
    int x = (int) event.getX();
    int y = (int) event.getY();
    switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            downX = x;
            downY = y;
            break;
        case MotionEvent.ACTION_MOVE:
            int offsetX = x - downX;
            int offsetY = y - downY; 
         
            ((View) getParent()).scrollBy(-offsetX, -offsetY);
            break;
        case MotionEvent.ACTION_UP:
            break;
    }

    return true;
}
最后編輯于
?著作權(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)容

  • 導(dǎo)語 滑動(dòng)算是Android比較常用的效果了,滑動(dòng)的操作具有很好的用戶體驗(yàn)性。 主要內(nèi)容 滑動(dòng)效果是如何產(chǎn)生的 實(shí)...
    一個(gè)有故事的程序員閱讀 6,562評(píng)論 3 11
  • 內(nèi)容是博主照著書敲出來的,博主碼字挺辛苦的,轉(zhuǎn)載請(qǐng)注明出處,后序內(nèi)容陸續(xù)會(huì)碼出。 當(dāng)了解了Android坐標(biāo)系和觸...
    Blankj閱讀 6,878評(píng)論 3 60
  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,234評(píng)論 25 708
  • 滑動(dòng)效果的產(chǎn)生 滑動(dòng)一個(gè) View ,其實(shí)就是移動(dòng)一個(gè) View,本質(zhì)上是對(duì) View 的坐標(biāo)位置進(jìn)行不停的改變。...
    希靈丶閱讀 5,965評(píng)論 7 20
  • 從媒體上我們可以了解到,現(xiàn)在國(guó)家正不斷加強(qiáng)知識(shí)產(chǎn)權(quán)的保護(hù),與這種保護(hù)相對(duì)應(yīng)的是不斷加大對(duì)各種非法出版物和侵權(quán)行為的...
    湖邊人老劉閱讀 3,625評(píng)論 1 1

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