View 的滑動學習

目的

這篇文章主要對 View 的滑動進行學習倔既,主要包括滑動的實現(xiàn)方式和彈性滑動提高用戶體驗


(一)View 的滑動

滑動的方式基本思想都是類似的:
當觸摸事件傳到View時,系統(tǒng)記下觸摸點的坐標威恼,手指移動時系統(tǒng)記下移動后的觸摸的坐標并算出偏移量摧茴,并通過偏移量來修改View的坐標印机。

View 實現(xiàn)滑動的幾種方式:

  • 第一種,layout()方法
  • 第二種瓶殃,offsetLeftAndRight() 與 offsetTopAndBottom()
  • 第三種,通過改變 View 的 LayoutParams 使得 View 重新布局從而實現(xiàn)滑動
  • 第四種副签,通過動畫給 View 施加平移效果來實現(xiàn)滑動
  • 第五種遥椿,通過 View 本身提供的 scrollTo/scrollBy 方法來實現(xiàn)滑動
1. layout() 方法

View 進行繪制的時候會調(diào)用 onLayout() 方法來設(shè)置顯示的位置基矮,因此我們同樣也可以通過修改 View 的屬性來控制 View 的坐標

public class RedView extends View {

    int lastX, lastY;

    public RedView(Context context) {
        super(context);
    }

    public RedView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

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

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        //獲取手指觸摸點的橫坐標和縱坐標
        int x = (int) event.getX();
        int y = (int) event.getY();

        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                lastX = x;
                lastY = y;
                break;
            case MotionEvent.ACTION_MOVE:
                //計算移動的距離
                int offsetX = x - lastX;
                int offsetY = y - lastY;
                //調(diào)用layout方法重新放置它的位置
                layout(offsetX + getLeft(), offsetY + getTop(), offsetX + getRight(), offsetY + getBottom());
                break;
        }
        return true;
    }
}
2. offsetLeftAndRight() 與 offsetTopAndBottom()

將 ACTION_MOVE 中的代碼替換

    case MotionEvent.ACTION_MOVE:
        //計算移動的距離
        int offsetX = x - lastX;
        int offsetY = y - lastY;
        //對 left 和 right 進行偏移
        offsetLeftAndRight(offsetX);
        //對 top 和 bottom 進行偏移
        offsetTopAndBottom(offsetY);
        break;
3. LaytouParams(改變布局參數(shù))

LayoutParams 主要保存了一個 View 的布局參數(shù),因此我們可以通過改變參數(shù)從而達到改變 View 位置的效果

    case MotionEvent.ACTION_MOVE:
        //計算偏移量
        int offsetX = x - lastX;
        int offsetY = y - lastY;
        
        LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) getLayoutParams();
        layoutParams.leftMargin = getLeft() + offsetX;
        layoutParams.topMargin = getTop() + offsetY;
        setLayoutParams(layoutParams);
        break;

需要注意的是 LinearLayout 是 View 的父容器修壕,如果父容器是 RelativeLayout愈捅,則要使用 RelativeLayout.LayoutParams
當然我們可以使用 ViewGroup.MarginLayoutParams 來實現(xiàn),一勞永逸

    case MotionEvent.ACTION_MOVE:
        //計算偏移量
        int offsetX = x - lastX;
        int offsetY = y - lastY;
        
        ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams) getLayoutParams();
        layoutParams.leftMargin = getLeft() + offsetX;
        layoutParams.topMargin = getTop() + offsetY;
        setLayoutParams(layoutParams);
        break;
4. 動畫

可以采用 View 動畫來移動慈鸠,在 res 目錄新建 anim 文件夾并創(chuàng)建 translate.xml

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:fillAfter="true">
    <translate
        android:duration="1000"
        android:fromXDelta="0"
        android:toXDelta="300" />
</set>
  • android:fillAfter="true" View 平移后停留在最后的位置蓝谨,如果為 false 則會返回起始位置
  • android:duration="1000" 動畫的時間為 1000 ms
  • android:toXDelta="300" 平移的距離,300 像素
mView.setAnimation(AnimationUtils.loadAnimation(this, R.anim.translate));

在 Java 代碼中執(zhí)行動畫青团,這里所用的是補間動畫譬巫,雖然 View 位置發(fā)生改變,但是對系統(tǒng)來說督笆,View 并沒有發(fā)生改變芦昔,點擊事件并不會發(fā)生。Android3.0 之后娃肿,可以通過屬性動畫來解決這個問題

ObjectAnimator.ofFloat(mView, "translationX", 0, 300).setDuration(1000).start();
5. scrollTo 與 scrollBy

scollTo(x, y) 表示移動到一個具體的坐標點咕缎,而 scollBy(dx, dy) 則表示移動的增量為 dx、dy料扰。
兩個方法區(qū)別: scrollBy() 相對移動凭豪,scrollTo() 絕對移動

其中 scollBy 最終也是要調(diào)用 scollTo 的。scollTo晒杈、scollBy 移動的是View的內(nèi)容嫂伞,如果在 ViewGroup 中使用則是移動他所有的子 View。我們將 ACTION_MOVE 中的代碼替換成如下代碼:

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

這里要實現(xiàn) mView 隨著我們手指移動的效果的話拯钻,我們就需要將偏移量設(shè)置為負值帖努。

在View內(nèi)部有兩個屬性 mScrllX 和 mScrollY,分別可以通過 getScrollX() 和 getScrollY() 方法得到

在滑動過程中粪般,mScrollX 總是等于 View 左邊緣和 View 內(nèi)容左邊緣在水平方方向的距離拼余;mScrollY 總是等于 View 上邊緣和 View 中內(nèi)容上邊緣在豎直方向的距離。View 邊緣是指 View 的位置也就是 View 的四個頂點到父容器的距離亩歹,View 內(nèi)邊緣是內(nèi)容距離 View 四邊的距離姿搜。

無論是 scrollTo() 還是 scrollBy() 都只能改變 View 內(nèi)容的位置而不能改變 View 在布局中的位置

mScrollX/Y 單位為像素 px。當 View 左邊緣在 View 內(nèi)容左邊緣右邊時捆憎,mScrollX 為正值舅柜,反之為負值;同理躲惰,當 View 上邊緣在 View 內(nèi)容上邊緣下邊時致份,mScrollX 為正值,反之為負值础拨。也就是說氮块,View 從左向右滑動绍载,mScrollX 為負值,反之為正值滔蝉;從上往下滑動击儡,mScrollY 為負值,反之為正值


mScrollX 和 mScrollY 變換規(guī)律
各種滑動方式的對比
  • 改變布局參數(shù):操作復雜蝠引,適用于有交互的 View
  • 屬性動畫:操作簡單阳谍,適用于沒交互 View 和實現(xiàn)復雜的動畫效果
  • scrollTo/By:操作簡單,適合對 View 內(nèi)容的滑動

(二)彈性滑動

在我們進行滑動時螃概,這個過程是瞬間完成的矫夯,所以用戶體驗不大好,這里我們可以添加有過渡的滑動

View 彈性滑動的幾種方式

  • 第一種:Scroller
  • 第二種:通過動畫

這幾種的方式有一個共同的思想:將一次大的滑動分成若干次小的滑動并在一個時間段內(nèi)完成

1. Scroller

Scroller 本身是不能實現(xiàn) View 的滑動的吊洼,它需要與 View 的 computeScroll() 方法配合才能實現(xiàn)彈性滑動的效果

  1. 初始化Scroller對象
  2. 重寫View的computeScroll()方法
  3. 調(diào)用mScroller.startScroll()方法
    public RedView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        mScroller = new ScrollView(context);
    }

    @Override
    public void computeScroll() {
        super.computeScroll();
        if (mScroller.computeScrollOffset()) {  //判斷Scroller是否執(zhí)行完畢
            ((View) getParent()).scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
            invalidate();
        }
    }

    //緩慢的滑到指定位置
    public void smoothScrollTo(int destX, int destY) {
        int deltaX = destX - getScrollX();  
        int deltaY = destY - getScrollY();  
        //2000ms 內(nèi)滑到向(deltaY训貌,deltaY)的位置,效果就是慢慢滑動
        mScroller.startScroll(0, 0, deltaX, deltaY, 2000);
        invalidate();
    }

在Activity中調(diào)用:

mView.smoothScrollTo(-400, -400);

Scroller 本身并不能實現(xiàn) View 的滑動冒窍,它需要配合 View 的 computeScroll 方法才能完成彈性滑動的效果递沪,它通過不斷地讓 View 重繪,而每一次重繪距滑動起始時間間隔综液,通過這個時間間隔 Scroller 就可以得出 View 當前的滑動位置款慨,知道了滑動位置就可以通過 scrollTo 方法來完成 View 的滑動。就這樣意乓,View 的每一次重繪都會導致 View 進行小幅度的滑動(彈性滑動的核心思想:將一次大的滑動分成若干次小的滑動并在一個時間段內(nèi)完成)樱调,而多次的小幅度滑動就組成了彈性滑動约素,這就是 Scroller 的工作機制届良。

2. 通過動畫

動畫本身就是一種漸進的過程,因此通過它來實現(xiàn)的滑動天然就是具有彈性效果:

ObjectAnimator.ofFloat(mView, "translationX", 0, 100).setDuration(1000).start();

總結(jié)

滑動做為 View 最基本的行為圣猎,一切的華麗的士葫、絢麗的 UI,歸根結(jié)底都是建立在其基礎(chǔ)上


最后

學習資料主要來源于《Android開發(fā)藝術(shù)探索》和《Android進階之光》送悔,對進階學習的一個記錄與總結(jié)慢显,如果有幸能對大家有所幫助,那將榮幸之至欠啤。

下一章荚藻,將對 View 的分發(fā)機制進行學習

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市洁段,隨后出現(xiàn)的幾起案子应狱,更是在濱河造成了極大的恐慌,老刑警劉巖祠丝,帶你破解...
    沈念sama閱讀 211,042評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件疾呻,死亡現(xiàn)場離奇詭異除嘹,居然都是意外死亡,警方通過查閱死者的電腦和手機岸蜗,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,996評論 2 384
  • 文/潘曉璐 我一進店門尉咕,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人璃岳,你說我怎么就攤上這事年缎。” “怎么了矾睦?”我有些...
    開封第一講書人閱讀 156,674評論 0 345
  • 文/不壞的土叔 我叫張陵晦款,是天一觀的道長。 經(jīng)常有香客問我枚冗,道長缓溅,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,340評論 1 283
  • 正文 為了忘掉前任赁温,我火速辦了婚禮坛怪,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘股囊。我一直安慰自己袜匿,他們只是感情好,可當我...
    茶點故事閱讀 65,404評論 5 384
  • 文/花漫 我一把揭開白布稚疹。 她就那樣靜靜地躺著居灯,像睡著了一般。 火紅的嫁衣襯著肌膚如雪内狗。 梳的紋絲不亂的頭發(fā)上怪嫌,一...
    開封第一講書人閱讀 49,749評論 1 289
  • 那天,我揣著相機與錄音柳沙,去河邊找鬼岩灭。 笑死,一個胖子當著我的面吹牛赂鲤,可吹牛的內(nèi)容都是我干的噪径。 我是一名探鬼主播,決...
    沈念sama閱讀 38,902評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼数初,長吁一口氣:“原來是場噩夢啊……” “哼找爱!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起泡孩,我...
    開封第一講書人閱讀 37,662評論 0 266
  • 序言:老撾萬榮一對情侶失蹤车摄,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體练般,經(jīng)...
    沈念sama閱讀 44,110評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡矗漾,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了薄料。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片敞贡。...
    茶點故事閱讀 38,577評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖摄职,靈堂內(nèi)的尸體忽然破棺而出誊役,到底是詐尸還是另有隱情,我是刑警寧澤谷市,帶...
    沈念sama閱讀 34,258評論 4 328
  • 正文 年R本政府宣布蛔垢,位于F島的核電站,受9級特大地震影響迫悠,放射性物質(zhì)發(fā)生泄漏鹏漆。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,848評論 3 312
  • 文/蒙蒙 一创泄、第九天 我趴在偏房一處隱蔽的房頂上張望艺玲。 院中可真熱鬧,春花似錦鞠抑、人聲如沸饭聚。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,726評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽秒梳。三九已至,卻和暖如春箕速,著一層夾襖步出監(jiān)牢的瞬間酪碘,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,952評論 1 264
  • 我被黑心中介騙來泰國打工弧满, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留婆跑,地道東北人此熬。 一個月前我還...
    沈念sama閱讀 46,271評論 2 360
  • 正文 我出身青樓庭呜,卻偏偏與公主長得像,于是被迫代替她去往敵國和親犀忱。 傳聞我的和親對象是個殘疾皇子募谎,可洞房花燭夜當晚...
    茶點故事閱讀 43,452評論 2 348