Android View體系(二)實(shí)現(xiàn)View滑動(dòng)的六種方法

相關(guān)文章:
Android View體系(一)視圖坐標(biāo)系

1.View的滑動(dòng)簡(jiǎn)介

View的滑動(dòng)是Android實(shí)現(xiàn)自定義控件的基礎(chǔ)糊闽,同時(shí)在開發(fā)中我們也難免會(huì)遇到View的滑動(dòng)的處理。其實(shí)不管是那種滑動(dòng)的方式基本思想都是類似的:當(dāng)觸摸事件傳到View時(shí)谍珊,系統(tǒng)記下觸摸點(diǎn)的坐標(biāo)治宣,手指移動(dòng)時(shí)系統(tǒng)記下移動(dòng)后的觸摸的坐標(biāo)并算出偏移量,并通過偏移量來修改View的坐標(biāo)。
實(shí)現(xiàn)View滑動(dòng)有很多種方法侮邀,這篇文章主要講解六種滑動(dòng)的方法坏怪,分別是:layout()、offsetLeftAndRight()與offsetTopAndBottom()绊茧、LayoutParams铝宵、動(dòng)畫、scollTo與scollBy和Scroller华畏;在下一篇文章我們會(huì)詳細(xì)介紹屬性動(dòng)畫鹏秋。

2.實(shí)現(xiàn)View滑動(dòng)的六種方法

layout()

view進(jìn)行繪制的時(shí)候會(huì)調(diào)用onLayout()方法來設(shè)置顯示的位置,因此我們同樣也可以通過修改View的left亡笑、top侣夷、right、bottom這四種屬性來控制View的坐標(biāo)况芒。首先我們要自定義一個(gè)View惜纸,在onTouchEvent()方法中獲取觸摸點(diǎn)的坐標(biāo):

   public boolean onTouchEvent(MotionEvent event) {
        //獲取到手指處的橫坐標(biāo)和縱坐標(biāo)
        int x = (int) event.getX();
        int y = (int) event.getY();

        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                lastX = x;
                lastY = y;
                break;

...

接下來我們?cè)贏CTION_MOVE事件中計(jì)算偏移量,再調(diào)用layout()方法重新放置這個(gè)自定義View的位置就好了:

            case MotionEvent.ACTION_MOVE:
                //計(jì)算移動(dòng)的距離
                int offsetX = x - lastX;
                int offsetY = y - lastY;
                //調(diào)用layout方法來重新放置它的位置
                layout(getLeft()+offsetX, getTop()+offsetY,
                        getRight()+offsetX , getBottom()+offsetY);
                break;

當(dāng)我們每次移動(dòng)時(shí)都會(huì)調(diào)用layout()方法來對(duì)自己重新布局绝骚,從而達(dá)到移動(dòng)View的效果耐版。

自定義View的全部代碼(CustomView.java):

package com.example.liuwangshu.moonviewslide;
import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;

public class CustomView extends View {
    private int lastX;
    private int lastY;

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

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

    public boolean onTouchEvent(MotionEvent event) {
        //獲取到手指處的橫坐標(biāo)和縱坐標(biāo)
        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:
                //計(jì)算移動(dòng)的距離
                int offsetX = x - lastX;
                int offsetY = y - lastY;
                //調(diào)用layout方法來重新放置它的位置
                layout(getLeft()+offsetX, getTop()+offsetY,
                        getRight()+offsetX , getBottom()+offsetY);
                break;
        }

        return true;
    }
}

布局中引用自定義View:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <com.example.liuwangshu.moonviewslide.CustomView
        android:id="@+id/customview"
        android:layout_width="80dp"
        android:layout_height="80dp"
        android:layout_margin="50dp"
        android:background="@android:color/holo_red_light" />
</LinearLayout>

offsetLeftAndRight()與offsetTopAndBottom()

這兩種方法和layout()方法效果方法差不多,使用也差不多压汪,我們將ACTION_MOVE中的代碼替換成如下代碼:

            case MotionEvent.ACTION_MOVE:
                //計(jì)算移動(dòng)的距離
                int offsetX = x - lastX;
                int offsetY = y - lastY;
                //對(duì)left和right進(jìn)行偏移
                offsetLeftAndRight(offsetX);
                //對(duì)top和bottom進(jìn)行偏移
                offsetTopAndBottom(offsetY);
                break;

LayoutParams(改變布局參數(shù))

LayoutParams主要保存了一個(gè)View的布局參數(shù)粪牲,因此我們可以通過LayoutParams來改變View的布局的參數(shù)從而達(dá)到了改變View的位置的效果。同樣的我們將ACTION_MOVE中的代碼替換成如下代碼:

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

因?yàn)楦缚丶荓inearLayout止剖,所以我們用了LinearLayout.LayoutParams腺阳,如果父控件是RelativeLayout則要使用RelativeLayout.LayoutParams。除了使用布局的LayoutParams外穿香,我們還可以用ViewGroup.MarginLayoutParams來實(shí)現(xiàn):

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

動(dòng)畫

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

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <translate android:fromXDelta="0" android:toXDelta="300" android:duration="1000"/>
</set>

在Java代碼中引用:

  mCustomView.setAnimation(AnimationUtils.loadAnimation(this, R.anim.translate));

當(dāng)然使用屬性動(dòng)畫移動(dòng)那就更簡(jiǎn)單了,我們讓CustomView在1000毫秒內(nèi)沿著X軸像右平移300像素:

ObjectAnimator.ofFloat(mCustomView,"translationX",0,300).setDuration(1000).start();

scollTo與scollBy

scollTo(x,y)表示移動(dòng)到一個(gè)具體的坐標(biāo)點(diǎn)皮获,而scollBy(dx,dy)則表示移動(dòng)的增量為dx焙蚓、dy。其中scollBy最終也是要調(diào)用scollTo的洒宝。scollTo购公、scollBy移動(dòng)的是View的內(nèi)容,如果在ViewGroup中使用則是移動(dòng)他所有的子View雁歌。我們將ACTION_MOVE中的代碼替換成如下代碼:

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

這里要實(shí)現(xiàn)CustomView隨著我們手指移動(dòng)的效果的話宏浩,我們就需要將偏移量設(shè)置為負(fù)值。

Scroller

我們用scollTo/scollBy方法來進(jìn)行滑動(dòng)時(shí)靠瞎,這個(gè)過程是瞬間完成的比庄,所以用戶體驗(yàn)不大好求妹。這里我們可以使用Scroller來實(shí)現(xiàn)有過度效果的滑動(dòng),這個(gè)過程不是瞬間完成的印蔗,而是在一定的時(shí)間間隔完成的扒最。Scroller本身是不能實(shí)現(xiàn)View的滑動(dòng)的,它需要配合View的computeScroll()方法才能彈性滑動(dòng)的效果华嘹。
在這里我們實(shí)現(xiàn)CustomView平滑的向右移動(dòng)吧趣。

  • 首先我們要初始化Scroller:
  public CustomView(Context context, AttributeSet attrs) {
        super(context, attrs);
        mScroller = new Scroller(context);
    }
  • 接下來重寫computeScroll()方法,系統(tǒng)會(huì)在繪制View的時(shí)候在draw()方法中調(diào)用該方法,這個(gè)方法中我們調(diào)用父類的scrollTo()方法并通過Scroller來不斷獲取當(dāng)前的滾動(dòng)值,每滑動(dòng)一小段距離我們就調(diào)用invalidate()方法不斷的進(jìn)行重繪瓢颅,重繪就會(huì)調(diào)用computeScroll()方法劝贸,這樣我們就通過不斷的移動(dòng)一個(gè)小的距離并連貫起來就實(shí)現(xiàn)了平滑移動(dòng)的效果:
    @Override
    public void computeScroll() {
        super.computeScroll();
        if(mScroller.computeScrollOffset()){
            ((View) getParent()).scrollTo(mScroller.getCurrX(),mScroller.getCurrY());
             //通過不斷的重繪不斷的調(diào)用computeScroll方法
             invalidate();
        }  
    }
  • 調(diào)用Scroller.startScroll()方法。我們?cè)贑ustomView中寫一個(gè)smoothScrollTo()方法,調(diào)用Scroller.startScroll()方法,在2000毫秒內(nèi)沿X軸平移delta像素:
  public void smoothScrollTo(int destX,int destY){
        int scrollX=getScrollX();
        int delta=destX-scrollX;
        //1000秒內(nèi)滑向destX
        mScroller.startScroll(scrollX,0,delta,0,2000);
        invalidate();
    }
  • 最后我們?cè)赩iewSlideActivity.java中調(diào)用CustomView的smoothScrollTo()方法:
          //使用Scroll來進(jìn)行平滑移動(dòng)
          mCustomView.smoothScrollTo(-400,0);

這里我們是設(shè)定CustomView沿著X軸向右平移400像素。

github源碼下載

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末八匠,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子趴酣,更是在濱河造成了極大的恐慌梨树,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,884評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件岖寞,死亡現(xiàn)場(chǎng)離奇詭異抡四,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)仗谆,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,755評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門指巡,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人隶垮,你說我怎么就攤上這事藻雪。” “怎么了狸吞?”我有些...
    開封第一講書人閱讀 158,369評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵勉耀,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我捷绒,道長(zhǎng)瑰排,這世上最難降的妖魔是什么贯要? 我笑而不...
    開封第一講書人閱讀 56,799評(píng)論 1 285
  • 正文 為了忘掉前任暖侨,我火速辦了婚禮,結(jié)果婚禮上崇渗,老公的妹妹穿的比我還像新娘字逗。我一直安慰自己京郑,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,910評(píng)論 6 386
  • 文/花漫 我一把揭開白布葫掉。 她就那樣靜靜地躺著些举,像睡著了一般。 火紅的嫁衣襯著肌膚如雪俭厚。 梳的紋絲不亂的頭發(fā)上户魏,一...
    開封第一講書人閱讀 50,096評(píng)論 1 291
  • 那天,我揣著相機(jī)與錄音挪挤,去河邊找鬼叼丑。 笑死,一個(gè)胖子當(dāng)著我的面吹牛扛门,可吹牛的內(nèi)容都是我干的鸠信。 我是一名探鬼主播,決...
    沈念sama閱讀 39,159評(píng)論 3 411
  • 文/蒼蘭香墨 我猛地睜開眼论寨,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼星立!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起葬凳,我...
    開封第一講書人閱讀 37,917評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤绰垂,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后沮明,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體辕坝,經(jīng)...
    沈念sama閱讀 44,360評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,673評(píng)論 2 327
  • 正文 我和宋清朗相戀三年荐健,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了酱畅。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,814評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡江场,死狀恐怖纺酸,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情址否,我是刑警寧澤餐蔬,帶...
    沈念sama閱讀 34,509評(píng)論 4 334
  • 正文 年R本政府宣布,位于F島的核電站佑附,受9級(jí)特大地震影響樊诺,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜音同,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,156評(píng)論 3 317
  • 文/蒙蒙 一词爬、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧权均,春花似錦顿膨、人聲如沸锅锨。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽必搞。三九已至,卻和暖如春囊咏,著一層夾襖步出監(jiān)牢的瞬間恕洲,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,123評(píng)論 1 267
  • 我被黑心中介騙來泰國打工梅割, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留研侣,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,641評(píng)論 2 362
  • 正文 我出身青樓炮捧,卻偏偏與公主長(zhǎng)得像庶诡,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子咆课,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,728評(píng)論 2 351

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

  • 內(nèi)容是博主照著書敲出來的末誓,博主碼字挺辛苦的,轉(zhuǎn)載請(qǐng)注明出處书蚪,后序內(nèi)容陸續(xù)會(huì)碼出喇澡。 當(dāng)了解了Android坐標(biāo)系和觸...
    Blankj閱讀 6,634評(píng)論 3 61
  • 什么是View View 是 Android 中所有控件的基類。 View的位置參數(shù) View 的位置由它的四個(gè)頂...
    acc8226閱讀 1,157評(píng)論 0 7
  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,871評(píng)論 25 707
  • 1殊校、初始化NSAttributedString時(shí)要校驗(yàn)string是否為空晴玖,為空會(huì)導(dǎo)致閃退。 2为流、下面的代碼是錯(cuò)誤...
    哆啦A夢(mèng)頻道閱讀 377評(píng)論 0 0
  • 作者/楊宏杰 任性一詞是土生土長(zhǎng)的國產(chǎn)貨呕屎,沒有沾半點(diǎn)泊來品的腥味。 最近兩年敬察,這個(gè)詞很暢銷秀睛,以至到了泛濫的地步。先...
    大秦之歌閱讀 266評(píng)論 0 0