Android中實(shí)現(xiàn)滑動(dòng)的七種方式

在Android中想要實(shí)現(xiàn)實(shí)現(xiàn)滑動(dòng)有很多方法觅彰,這篇博客將提供一些實(shí)現(xiàn)滑動(dòng)的思路,希望可以幫助到有需要的人烛芬。

一飒责、Android坐標(biāo)體系

在講解滑動(dòng)之前宏蛉,我們有必要簡(jiǎn)單提一下Android的坐標(biāo)體系,因?yàn)榛瑒?dòng)的實(shí)質(zhì)就是坐標(biāo)的不斷改變揍堰,所以我們先來(lái)了解一下Android坐標(biāo)系視圖坐標(biāo)系兩個(gè)概念屏歹。直接放上兩張圖片吧,一目了然季希。

Android坐標(biāo)系
視圖坐標(biāo)系

從上面的兩張圖可以看出,Android坐標(biāo)系的坐標(biāo)原點(diǎn)位于屏幕的左上角霹崎,而視圖坐標(biāo)系的原點(diǎn)位于父視圖的左上角尾菇,既然提供了兩種不同的坐標(biāo)系派诬,那么我們?nèi)绾蝸?lái)獲取坐標(biāo)呢,Android已經(jīng)給我們提供了一些方法用于獲取這些坐標(biāo)沛鸵,看下面的圖便一目了然缆八。

Android獲取坐標(biāo)的各種方法

二奈辰、layout方法

在View進(jìn)行繪制時(shí),是調(diào)用onLayout()方法來(lái)確定View的位置的论泛,同樣我們也可以調(diào)用layout()方法來(lái)傳入我們滑動(dòng)后的坐標(biāo)便可以實(shí)現(xiàn)View的滑動(dòng)蛹屿,當(dāng)然坐標(biāo)的獲取我們可以在觸控事件中進(jìn)行獲取蜡峰,下面我們做一個(gè)View隨手指進(jìn)行滑動(dòng)的小例子來(lái)進(jìn)行說(shuō)明。

public class DragView extends View {
    private int mLastX;
    private int mLastY;
    public DragView(Context context) {
        this(context, null);
    }

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

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

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        int x = (int) event.getX();
        int y = (int) event.getY();
        int lastX = 0, lastY = 0;
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                mLastX = x;
                mLastY = y;
                break;
            case MotionEvent.ACTION_MOVE:
                int offsetX = x - mLastX;
                int offsetY = y - mLastY;
                layout(getLeft() + offsetX, getTop() + offsetY,
                        getRight() + offsetX, getBottom() + offsetY);
                break;
        }
        return true;
    }
}

上面我們?cè)谟|控事件中獲取到獲取到手指按下時(shí)的坐標(biāo)(lastX, lastY)粥诫,然后在手指移動(dòng)時(shí)不斷計(jì)算X和Y方向上的偏移量怀浆,然后再調(diào)用layout()方法來(lái)改變View的位置從而實(shí)現(xiàn)滑動(dòng)怕享。當(dāng)然上面我們是通過(guò)getX()getY()來(lái)獲取視圖坐標(biāo)來(lái)進(jìn)行修改函筋,我們也可以通過(guò)getRawX()getRawY()來(lái)獲取絕對(duì)坐標(biāo)來(lái)實(shí)現(xiàn)上面的效果跌帐。代碼如下:

private int mLastX;
private int mLastY;
@Override
public boolean onTouchEvent(MotionEvent event) {
    int x = (int) event.getRawX();
    int y = (int) event.getRawY();
    switch (event.getAction()){
        case MotionEvent.ACTION_DOWN:
            mLastX = x;
            mLastY = y;
            break;
        case MotionEvent.ACTION_MOVE:
            int offsetX = x - mLastX;
            int offsetY = y - mLastY;
            layout(getLeft() + offsetX, getTop() + offsetY,
                    getRight() + offsetX, getBottom() + offsetY);
            //重新設(shè)置初始坐標(biāo)
            mLastX = x;
            mLastY = y;
            break;
    }
    return true;
}

上面一定要注意谨敛,我們?cè)诟淖兺?code>View的位置后必須調(diào)用設(shè)置初始坐標(biāo),這樣才能準(zhǔn)確獲取偏移量最仑。

三泥彤、offsetLeftAndRightoffsetTopAndBottom

這一種方法和上一種方法大部分步驟都是相同的卿啡,只是在移動(dòng)View上有所差別牵囤,代碼如下:

offsetLeftAndRight(offsetX);
offsetTopAndBottom(offsetY);

上面的這種方法只是多了一層封裝滞伟,可以實(shí)現(xiàn)比上面實(shí)現(xiàn)同樣的效果梆奈。

四亩钟、設(shè)置LayoutParams

LayoutParams可以通過(guò)改變的布局參數(shù)鳖轰,我們可以通過(guò)下面的代碼實(shí)現(xiàn)上面同樣的效果蕴侣。

LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) getLayoutParams();
layoutParams.leftMargin = getLeft() + offsetX;
layoutParams.topMargin = getTop() + offsetY;
setLayoutParams(layoutParams);

注意:我們的LayoutParams可以通過(guò)getLayoutParams()方法來(lái)獲取昆雀,但是要注意蝠筑,如果View的父布局是LinearLayout什乙,那么我們的LayoutParams就是LinearLayout.LayoutParams,如果View的父布局是RelativeLayout,則我們的LayoutParams就是RelativeLayout.LayoutParams辅愿。當(dāng)然我們還有一種簡(jiǎn)單的方法渠缕,不用再管父布局的布局方式褒繁。代碼如下:

ViewGroup.MarginLayoutParams marginLayoutParams = (ViewGroup.MarginLayoutParams) getLayoutParams();
marginLayoutParams.leftMargin = getLeft() + offsetX;
marginLayoutParams.topMargin = getTop() + offsetY;
setLayoutParams(marginLayoutParams);

上面的這種方法不用管父布局的類(lèi)型棒坏,使用起來(lái)更加方便坝冕。

五、scrollToscrollBy方法

關(guān)于這兩個(gè)方法我們需要仔細(xì)說(shuō)一下其中的一些注意事項(xiàng)

1 . scrollTo的參數(shù)是具體的一個(gè)坐標(biāo)點(diǎn)(x, y), 而scrollBy的參數(shù)是在x, y方向上的坐標(biāo)偏移

2 . scrollToscrollBy移動(dòng)的是View的內(nèi)容测暗。這一點(diǎn)很重要!!!!

如果我們對(duì)ViewGroup使用scrollToscrollBy則移動(dòng)的是內(nèi)部的所有子View碗啄, 如果對(duì)TextView使用scrollToscrollBy則移動(dòng)的是其中額文本稚字。

3 . 視圖移動(dòng)還有一個(gè)不太好理解的地方在于坐標(biāo),我們下面結(jié)合圖片來(lái)說(shuō)明一下:

視圖移動(dòng)1
視圖移動(dòng)2

我們可以這樣理解,我們的手機(jī)屏幕作為一個(gè)蓋板国夜,在手機(jī)屏幕下面是一個(gè)巨大的畫(huà)布剧蚣,我們的手機(jī)屏幕這個(gè)蓋板是透明的鸠按,導(dǎo)致只有和手機(jī)屏幕重合的畫(huà)布部分才會(huì)被我們看到目尖,我們調(diào)用scrollToscrollBy也可以理解為是在移動(dòng)手機(jī)上面的蓋板。如圖中所示饮戳,按鈕在ViewGroup中的坐標(biāo)是(20, 10),當(dāng)我們調(diào)用scrollBy(20, 10)之后扯罐,就相當(dāng)于移動(dòng)了屏幕上的蓋板烦衣,然后我們看到的按鈕就到了ViewGroup的左上角花吟。這樣如果我們想讓按鈕在水平和豎直方向上各移動(dòng)2010個(gè)單位,我們就必須調(diào)用scrollBy(-20, -10)

經(jīng)過(guò)了上面的知識(shí)準(zhǔn)備键菱,我們這里也使用scrollBy來(lái)實(shí)現(xiàn)前面實(shí)現(xiàn)的那個(gè)View隨手指移動(dòng)的小例子:

((View)getParent()).scrollBy(-offsetX, -offsetY);

六经备、使用Scroller

Scroller也是滑動(dòng)中很重要的一個(gè)角色,進(jìn)過(guò)前面的scrollToscrollBy大家也會(huì)發(fā)現(xiàn)弄喘,它們的移動(dòng)時(shí)瞬間完成的甩牺,滑動(dòng)顯得十分突兀累奈,Google為了改善用戶體驗(yàn),便給出了Scroller搞乏,它可以實(shí)現(xiàn)平滑的移動(dòng)请敦,從而使滑動(dòng)過(guò)程更加真實(shí)侍筛,用戶體驗(yàn)更好,下面我們先簡(jiǎn)單說(shuō)說(shuō)Scroller的實(shí)現(xiàn)原理裆熙。

Scroller的實(shí)現(xiàn)方式類(lèi)似于scrollToscrollBy入录,scrollToscrollBy的移動(dòng)都是從一個(gè)坐標(biāo)點(diǎn)瞬間移動(dòng)到另一個(gè)左邊點(diǎn)僚稿,而Scroller則是將移動(dòng)的這段距離切分成好幾段的微小的位移蟀伸,然后每一段調(diào)用scrollTo來(lái)不斷移動(dòng)這些微小的位移望蜡,由于人眼的視覺(jué)暫留效果脖律,就會(huì)給人平滑移動(dòng)的視覺(jué)效果。

下面我們?cè)谏弦徊降幕A(chǔ)上增加一個(gè)小功能芦疏,第一部分還是View隨手指移動(dòng)酸茴,但是當(dāng)我們松開(kāi)手指時(shí)兢交,讓View自己平滑移動(dòng)到最初始的位置(屏幕左上角),下面我們就來(lái)一步步介紹Scroller的用法

1 . 聲明Scroller變量,并在構(gòu)造方法中進(jìn)行初始化

2 . 在觸控事件的ACTION_UP(手指抬起)事件中傳入開(kāi)始滑動(dòng)的坐標(biāo)和需要滑動(dòng)的距離并觸發(fā)Scroller的滑動(dòng)事件

3 . 重寫(xiě)computeScroll(),實(shí)現(xiàn)真正的滑動(dòng)

下面是完整的代碼示例:

public class DragView extends View {
    private int mLastX;
    private int mLastY;
    //聲明Scroller變量
    private Scroller mScroller;
    public DragView(Context context) {
        this(context, null);
    }

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

    public DragView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        //在構(gòu)造方法中初始化Scroller變量
        mScroller = new Scroller(context);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        int x = (int) event.getRawX();
        int y = (int) event.getRawY();
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                mLastX = x;
                mLastY = y;
                break;
            case MotionEvent.ACTION_MOVE:
                int offsetX = x - mLastX;
                int offsetY = y - mLastY;
                //實(shí)現(xiàn)View跟隨手指移動(dòng)的效果
                ((View)getParent()).scrollBy(-offsetX, -offsetY);
                //重新設(shè)置初始坐標(biāo)
                mLastX = x;
                mLastY = y;
                break;
            case MotionEvent.ACTION_UP:
                //當(dāng)手指抬起時(shí)執(zhí)行滑動(dòng)過(guò)程
                View view = (View) getParent();
                mScroller.startScroll(view.getScrollX(), view.getScrollY(),
                        view.getScrollX(), view.getScrollY(), 5000);
                //調(diào)用重繪來(lái)間接調(diào)用computeScroll()方法
                invalidate();
                break;
        }
        return true;
    }

    @Override
    public void computeScroll() {
        super.computeScroll();
        //判斷滑動(dòng)過(guò)程是否完成
        if (mScroller.computeScrollOffset()){
            ((View)getParent()).scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
            //通過(guò)重繪來(lái)不斷調(diào)用computeScroll()方法
            invalidate();
        }
    }
}

上面的代碼View隨手指移動(dòng)的代碼部分是與前面相同的凳干,我們只說(shuō)說(shuō)Scroller的部分以及一些注意事項(xiàng)

1 . startScroll()方法各參數(shù)的意義,我們可以看看下面的源碼:

/**
 * Start scrolling by providing a starting point, the distance to travel,
 * and the duration of the scroll.
 *
 * @param startX Starting horizontal scroll offset in pixels. Positive
 *        numbers will scroll the content to the left.
 * @param startY Starting vertical scroll offset in pixels. Positive numbers
 *        will scroll the content up.
 * @param dx Horizontal distance to travel. Positive numbers will scroll the
 *        content to the left.
 * @param dy Vertical distance to travel. Positive numbers will scroll the
 *        content up.
 * @param duration Duration of the scroll in milliseconds.
 */
public void startScroll(int startX, int startY, int dx, int dy, int duration)

可以看出startXstartY參數(shù)就是開(kāi)始滾動(dòng)的(x, y)坐標(biāo)救赐,那么我們就可以通過(guò)ViewGroup(子View的父視圖)getScrollX()getScrollY()來(lái)獲取,這里一定要注意经磅,我們?cè)诨瑒?dòng)時(shí)的content就是子View预厌,所以我們通過(guò)子View的父視圖(ViewGroup)的getScrollX()getScrollY()獲取到的就是子View在X和Y方向上滑動(dòng)的距離畏陕,即就是我們需要的當(dāng)我們手指抬起時(shí)子View的(x, y)坐標(biāo)惠毁。而如果我們對(duì)子View調(diào)用getScrollX()getScrollY()方法鞠绰,則獲得的是子View內(nèi)部的視圖的滑動(dòng)距離及坐標(biāo)。

dxdy分別是在X和Y方向上的偏移量屿笼,而且注釋中說(shuō)了驴一,如果我們傳入的dxdy的值是正值灶壶,那么將會(huì)向上向左移動(dòng)這個(gè)content(其實(shí)就是我們這里的View)驰凛,即我們就可以讓子View回到左上角恰响,這里我們還是可以借助于上一小節(jié)中提到的視圖移動(dòng)的概念,我們想讓子View向坐上方移動(dòng)首有,其實(shí)就是想讓覆蓋在上面的蓋板向右下角移動(dòng)绞灼,我們可以將dxdy理解為父視圖(覆蓋在上面的蓋板)的偏移量低矮。

假設(shè)我們剛開(kāi)始是讓子View隨手指向右下方移動(dòng)被冒,那么相當(dāng)于覆蓋在上面的蓋板是向左上方移動(dòng)军掂,所以我們通過(guò)getScrollX()getScrollY()獲得的值是負(fù)值,我們現(xiàn)在松開(kāi)手指想讓子View向左上方移動(dòng)(即回到屏幕左上角)昨悼,那么就相當(dāng)于蓋板向右下角移動(dòng)蝗锥,所以我們的dxdy的值必須是-getScrollX()-getScrollY(),此時(shí)的兩個(gè)值都是正值率触。

2 . 由于我們的computeScroll()方法不會(huì)主動(dòng)調(diào)用终议,但是我們又需要它不斷調(diào)用從而不斷進(jìn)行微小移動(dòng)從而實(shí)現(xiàn)平滑的滑動(dòng),所以我們可以通過(guò)下面的方法葱蝗。

這三個(gè)按照以下順序進(jìn)行調(diào)用 invalidate()--->onDraw()--->computeScroll()穴张,所以我們可以可以在ACTION_UP中調(diào)用完startScroll()方法后調(diào)用invalidate()方法,然后在computeScroll()方法中判斷滑動(dòng)是否結(jié)束,如果沒(méi)結(jié)束两曼,則通過(guò)getCurrX()getCurrY()來(lái)獲得當(dāng)前需要移動(dòng)的微小的位移的坐標(biāo)點(diǎn)皂甘,然后傳入scrollTo()方法中,這時(shí)候子View還只是移動(dòng)了一小段距離悼凑,然后我們?cè)俅握{(diào)用invalidate()方法偿枕,然后接著調(diào)用onDraw()方法,然后再次進(jìn)入computeScroll()中再次讓子View移動(dòng)一小段距離户辫,直到滑動(dòng)結(jié)束,computeScrollOffset()返回false渐夸,則這個(gè)循環(huán)調(diào)用的過(guò)程結(jié)束,從而完成平滑移動(dòng)的過(guò)程渔欢。

七墓塌、屬性動(dòng)畫(huà)

屬性動(dòng)畫(huà)一樣可以實(shí)現(xiàn)View的滑動(dòng),但是由于屬性動(dòng)畫(huà)涉及到的知識(shí)點(diǎn)也是眾多膘茎,這里不再展開(kāi)來(lái)寫(xiě)桃纯,只是提供一個(gè)思路,后續(xù)后專(zhuān)門(mén)寫(xiě)一篇博客來(lái)說(shuō)披坏。

八态坦、ViewDragHelper

ViewDragHelper可以幫助我們實(shí)現(xiàn)各種滑動(dòng)需求,但是它的使用也相對(duì)較復(fù)雜棒拂,所以準(zhǔn)備專(zhuān)門(mén)寫(xiě)一篇博客來(lái)介紹他伞梯,這里只是給出一個(gè)概念

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末玫氢,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子谜诫,更是在濱河造成了極大的恐慌漾峡,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,627評(píng)論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件喻旷,死亡現(xiàn)場(chǎng)離奇詭異生逸,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)且预,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,180評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén)槽袄,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人锋谐,你說(shuō)我怎么就攤上這事遍尺。” “怎么了涮拗?”我有些...
    開(kāi)封第一講書(shū)人閱讀 169,346評(píng)論 0 362
  • 文/不壞的土叔 我叫張陵乾戏,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我三热,道長(zhǎng)鼓择,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 60,097評(píng)論 1 300
  • 正文 為了忘掉前任康铭,我火速辦了婚禮惯退,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘从藤。我一直安慰自己催跪,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,100評(píng)論 6 398
  • 文/花漫 我一把揭開(kāi)白布夷野。 她就那樣靜靜地躺著懊蒸,像睡著了一般。 火紅的嫁衣襯著肌膚如雪悯搔。 梳的紋絲不亂的頭發(fā)上骑丸,一...
    開(kāi)封第一講書(shū)人閱讀 52,696評(píng)論 1 312
  • 那天,我揣著相機(jī)與錄音妒貌,去河邊找鬼通危。 笑死,一個(gè)胖子當(dāng)著我的面吹牛灌曙,可吹牛的內(nèi)容都是我干的菊碟。 我是一名探鬼主播,決...
    沈念sama閱讀 41,165評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼在刺,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼逆害!你這毒婦竟也來(lái)了头镊?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 40,108評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤魄幕,失蹤者是張志新(化名)和其女友劉穎相艇,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體纯陨,經(jīng)...
    沈念sama閱讀 46,646評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡坛芽,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,709評(píng)論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了翼抠。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片靡馁。...
    茶點(diǎn)故事閱讀 40,861評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖机久,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情赔嚎,我是刑警寧澤膘盖,帶...
    沈念sama閱讀 36,527評(píng)論 5 351
  • 正文 年R本政府宣布,位于F島的核電站尤误,受9級(jí)特大地震影響侠畔,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜损晤,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,196評(píng)論 3 336
  • 文/蒙蒙 一软棺、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧尤勋,春花似錦喘落、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,698評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至暖哨,卻和暖如春赌朋,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背篇裁。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,804評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工沛慢, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人达布。 一個(gè)月前我還...
    沈念sama閱讀 49,287評(píng)論 3 379
  • 正文 我出身青樓团甲,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親往枣。 傳聞我的和親對(duì)象是個(gè)殘疾皇子伐庭,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,860評(píng)論 2 361

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