Android View相關(guān)(一)View的參數(shù)與滑動實現(xiàn)

主要總結(jié)了:

  1. View的基礎(chǔ)知識:
    • View的mTop、mLeft泌辫、mRight随夸、mBottom四個參數(shù)和對應(yīng)的四個get()。
    • View的getTanslationX() getTranslationY()震放、getX() getY()宾毒。
    • MotionEvent的典型事件和getX()、getY()殿遂、getRawX()诈铛、getRawY()。
    • TouchSlop最小滑動距離墨礁。
    • Velocity Tracker滑動速度幢竹。
    • GestureDetector和它的回調(diào)接口OnGestureListener、OnDoubleTapListener恩静。
  2. View的滑動:
    • scrollTo()焕毫、scrollBy()的使用和實現(xiàn),mScrollX驶乾、mScrollY參數(shù)邑飒。
    • View動畫和屬性動畫實現(xiàn)滑動。
    • 改變參數(shù)布局實現(xiàn)滑動级乐。
  3. View的彈性滑動:
    • Scroller實現(xiàn)彈性動畫和原理幸乒。
    • 利用動畫特性實現(xiàn)彈性動畫。
    • 其他方法實現(xiàn)彈性動畫唇牧。

View的基礎(chǔ)知識

View的位置參數(shù)

mTop mLeft mRight mBottom

View的位置主要通過它的四個頂點來決定罕扎,對應(yīng)View的四個屬性。

  • mTop 左上角縱坐標
  • mLeft 左上角橫坐標
  • mRight 右下角橫坐標
  • mBottom 右下角縱坐標

這四個參數(shù)指的是View的原始位置信息丐重,平移并不會改變這四個參數(shù)的值腔召。

看到View的源碼中,比如說mLeft扮惦,注釋中說mLeft是從父布局的左邊緣到這個View的左邊的像素臀蛛。

/**
 * The distance in pixels from the left edge of this view's parent
 * to the left edge of this view.
 * {@hide}
 */
@ViewDebug.ExportedProperty(category = "layout")
protected int mLeft;

這四個坐標是相對于這個View的父容器來說的,所以它是一種相對坐標。


View中提供了四個get()來獲得這四個參數(shù)浊仆,比如下面的getTop()客峭。

/**
 * Top position of this view relative to its parent.
 *
 * @return The top of this view, in pixels.
 */
@ViewDebug.CapturedViewProperty
public final int getTop() {
    return mTop;
}

可以從上面的四個參數(shù)計算出View的寬高。

width = right - left;
height = bottom - top;

getTanslationX() getTranslationY()

Android3.0之后提供的兩個方法抡柿,getTranslationX()和getTranslationY(),它們不同于上面的四個參數(shù)洲劣,這兩個參數(shù)會由于 View的平移而變化,表示View左上角坐標相對于left、top(原始左上角坐標)的偏移量。

/**
 * The horizontal location of this view relative to its {@link #getLeft() left} position.
 * This position is post-layout, in addition to wherever the object's
 * layout placed it.
 *
 * @return The horizontal position of this view relative to its left position, in pixels.
 */
@ViewDebug.ExportedProperty(category = "drawing")
public float getTranslationX() {
    return mRenderNode.getTranslationX();
}

getX() getY()

Android3.0之后提供了getX()和getY()兩個方法。

/**
 * The visual x position of this view, in pixels. This is equivalent to the
 * {@link #setTranslationX(float) translationX} property plus the current
 * {@link #getLeft() left} property.
 *
 * @return The visual x position of this view, in pixels.
 */
@ViewDebug.ExportedProperty(category = "drawing")
public float getX() {
    return mLeft + getTranslationX();
}

代碼是將mLeft加上translationX得到x的衫哥,可以看出來茎刚,x和y代表的就是當前View左上角相對于父布局的偏移量。

上面三組參數(shù)可以得到兩組等式撤逢。

x = left + translationX;
y = top + translationY

MotionEvent

手指接觸屏幕后產(chǎn)生的一系列事件中膛锭,典型的事件如下:

  • ACTION_DOWN——手指剛接觸屏幕。
  • ACTION_MOVE——在屏幕上移動蚊荣。
  • ACTION_DOWN——從屏幕上松開初狰。

這些事件對應(yīng)MotionEvent類中的幾個靜態(tài)常量。

public static final int ACTION_DOWN = 0;
public static final int ACTION_UP   = 1;
public static final int ACTION_MOVE = 2;

正常情況下的一些列點擊事件:

  • 點擊屏幕后立即松開互例,ACTION_DOWN->ACTION_UP
  • 點擊屏幕滑動后再松開奢入,ACTION_DOWN->ACTION_MOVE->......->ACTION_MOVE->ACTION_UP

可以通過MotionEvent對象調(diào)用getX()、getY()媳叨、getRawX()腥光、getRawY()獲取觸碰點的位置參數(shù)。

  • getX()糊秆、getY() 相對于當前View左上角的x武福、y值。
  • getRawX()痘番、getRawY() 相對于手機屏幕左上角的x捉片、y值平痰。

這四個方法都是去調(diào)用native方法。

public final float getRawX() {
    return nativeGetRawAxisValue(mNativePtr, AXIS_X, 0, HISTORY_CURRENT);
}

@FastNative
private static native float nativeGetRawAxisValue(long nativePtr,
        int axis, int pointerIndex, int historyPos);

TouchSlop

TouchSlop是系統(tǒng)能識別的最小滑動距離伍纫,如果小于這個值宗雇,則不認為是滑動。這是一個常量和設(shè)備有關(guān)莹规,可以通過以下方式獲得赔蒲。

ViewConfiguration.get(getContext()).getScaledTouchSlop();
public int getScaledTouchSlop() {
    return mTouchSlop;
}

這個mTouchSlop在ViewConfiguration的無參構(gòu)造器中用一個常量賦了初始值為8。

private static final int TOUCH_SLOP = 8;
@Deprecated
public ViewConfiguration() {
    //...
    mTouchSlop = TOUCH_SLOP;
    //...
}

有參構(gòu)造器中初始化為資源文件的一個值,這個值也是8。

<!-- Base "touch slop" value used by ViewConfiguration as a
     movement threshold where scrolling should begin. -->
<dimen name="config_viewConfigurationTouchSlop">8dp</dimen>

private ViewConfiguration(Context context) {
    //...
    mTouchSlop = res.getDimensionPixelSize(com.android.internal.R.dimen.config_viewConfigurationTouchSlop);
    //...
}

在處理滑動的時候可以使用這個值來做一些過濾舶沿,過濾掉滑動距離小于這個值,會有更好的用戶體驗。

Velocity Tracker

用來獲取手指滑動過程中的速度寸潦,包括水平速度和垂直速度。

用法

在onTouchEvent()中追蹤當前單擊事件的速度。

  1. 首先獲得一個VelocityTracker對象,再將當前時間加入進去。
VelocityTracker velocityTracker = VelocityTracker.obtain();
velocityTracker.addMovement(event);
  1. 計算自定義時間內(nèi)的速度,再調(diào)用get獲得定義時間內(nèi)劃過的像素點。
velocityTracker.computeCurrentVelocity(1000);
int xVelocity = (int) velocityTracker.getXVelocity();
int yVelocity = (int) velocityTracker.getYVelocity();
  1. 計算真正的速度蛹锰。
int xV = xVelocity / 1;//這里的1是上面計算時間時定義的時間間隔1000ms
int yV = yVelocity / 1;
  1. 回收資源。
velocityTracker.clear();
velocityTracker.recycle();

注意

  • 獲取速度之前必須要調(diào)用computeCurrentVelocity()計算速度。
  • getXVelocity()\getYVelocity()獲取到的是計算單位時間內(nèi)滑過的像素值像捶,并不是速度。

GestureDetector

GestureDetector用于檢測用戶的單擊沉删、滑動殴穴、長按震桶、雙擊等行為。

GestureDetector內(nèi)部有兩個監(jiān)聽接口,OnGestureListener和OnDoubleTapListener婶芭,里面的方法可以根據(jù)需求去實現(xiàn)轨奄。

public interface OnGestureListener {
    boolean onDown(MotionEvent e);//手指輕輕觸摸屏幕的一瞬間,一個ACTION_DOWN觸發(fā)
    void onShowPress(MotionEvent e);//手指輕觸屏幕挪拟,沒有松開或挪動
    boolean onSingleTapUp(MotionEvent e);//輕觸后松開挨务,單擊行為,伴隨一個ACTION_UP觸發(fā)
    boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY);//拖動行為玉组,由一個ACTION_DOWN和一系列ACTION_MOVE觸發(fā)
    void onLongPress(MotionEvent e);//長按
    boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY);//按下快速滑動后松開谎柄,一個ACTION_DOWN、多個ACTION_MOVE和一個ACTION_UP觸發(fā)
}
public interface OnDoubleTapListener {
    boolean onSingleTapConfirmed(MotionEvent e);//嚴格的單擊行為惯雳,不能是雙擊中的其中一次單擊朝巫,onSingleTapUp可以是雙擊中的其中一次。
    boolean onDoubleTap(MotionEvent e);//雙擊石景,兩次單擊劈猿,不可能和onSingleTapConfirmed共存
    boolean onDoubleTapEvent(MotionEvent e);//雙擊行為拙吉,雙擊期間ACTION_DOWN ACTION_MOVE ACTION_UP都會觸發(fā)此回調(diào)。
}

使用

創(chuàng)建一個GestureDetector糙臼,根據(jù)需要實現(xiàn)接口并傳入GestureDetector庐镐。

gestureDetector = new GestureDetector(context, gestureListener);
gestureDetector.setOnDoubleTapListener(doubleTapListener);
gestureDetector.setIsLongpressEnabled(false);//解決長按屏幕后無法拖動的現(xiàn)象

接管View的onTouchEvent(),GestureDetector的onTouchEvent()中會根據(jù)event來回調(diào)上面說的兩個接口方法变逃。

@Override
public boolean onTouchEvent(MotionEvent event) {
    boolean consume = gestureDetector.onTouchEvent(event);
    return consume;
}

注意

并不是必須要用GestureDetector來實現(xiàn)所需的監(jiān)聽必逆,完全也可以直接在View的onTouchEvent()中做判斷并實現(xiàn)需求。所以揽乱,如果只需要監(jiān)聽簡單的單擊事件就可以直接使用View的onTouchEvent()名眉,如果需要監(jiān)聽復雜一點的一系列事件,就可以使用GestureDetector凰棉。

View的滑動

scrollTo()/scrollBy()

scrollTo和scrollBy可以改變View內(nèi)容的位置损拢,舉例來說就是如果對ViewGroup調(diào)用scrollTo只會改變其子View的位置,如果對View撒犀,比如TextView調(diào)用福压,那么只會改變這個TextView文字的位置。

1. 使用

tv.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        bt.scrollTo(100, 200);
        tv.scrollBy(-5, -5);
    }
});

直接使用View對象去調(diào)用兩個方法或舞,傳入位移像素值就可以了荆姆。scrollTo()是內(nèi)容的絕對移動,scrollBy()是內(nèi)容的相對移動映凳。

但是需要注意的是胆筒,這兩個方法在onCreate()中調(diào)用,可能不會成功诈豌,原因應(yīng)該是因為那時View還沒有完全加載完畢仆救,所以調(diào)用會不起作用。

2. scrollTo的實現(xiàn)

/**
 * Set the scrolled position of your view. This will cause a call to
 * {@link #onScrollChanged(int, int, int, int)} and the view will be
 * invalidated.
 * @param x the x position to scroll to
 * @param y the y position to scroll to
 */
public void scrollTo(int x, int y) {
    if (mScrollX != x || mScrollY != y) {
        int oldX = mScrollX;
        int oldY = mScrollY;
        mScrollX = x;
        mScrollY = y;
        invalidateParentCaches();
        onScrollChanged(mScrollX, mScrollY, oldX, oldY);
        if (!awakenScrollBars()) {
            postInvalidateOnAnimation();
        }
    }
}

這里有兩個量矫渔,mScrollX和mScrollY:

/**
 * The offset, in pixels, by which the content of this view is scrolled
 * horizontally.
 * {@hide}
 */
@ViewDebug.ExportedProperty(category = "scrolling")
protected int mScrollX;

mScrollX表示View內(nèi)容和View本身的橫向偏移量彤蔽,mScrollY就是縱向偏移的像素值了。

  1. scorllTo()首先比較內(nèi)容偏移量和傳入的x y是否相等庙洼,都不相等再操作顿痪。
  2. 它記錄了原始的兩個偏移量,之后將傳入的x y賦值給mScrollX和mScrollY送膳。
  3. 接著調(diào)用了invalidateParentCaches()员魏,方法注釋意思是當啟動了硬件加速時去通知此View的父容器清除緩存。
  4. 調(diào)用了onScrollChanged(mScrollX, mScrollY, oldX, oldY)叠聋,這個方法內(nèi)部會判斷我們是否有設(shè)置OnScrollChangeListener撕阎,如果有就調(diào)用它的回調(diào)方法。
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
    //......
    if (mListenerInfo != null && mListenerInfo.mOnScrollChangeListener != null) {
        mListenerInfo.mOnScrollChangeListener.onScrollChange(this, l, t, oldl, oldt);
    }
}
  1. awakenScrollBars()喚醒scrollbar去重新繪制碌补,如果失敗返回false虏束,就直接調(diào)用postInvalidateOnAnimation()重新繪制棉饶。所以不管怎么樣最終都會調(diào)用到postInvalidateOnAnimation()。
public void postInvalidateOnAnimation() {
    // We try only with the AttachInfo because there's no point in invalidating
    // if we are not attached to our window
    final AttachInfo attachInfo = mAttachInfo;
    if (attachInfo != null) {
        attachInfo.mViewRootImpl.dispatchInvalidateOnAnimation(this);
    }
}

判斷與Window的連接是否空镇匀,不空就調(diào)用ViewRootImpl的dispatchInvalidateOnAnimation()照藻。

public void dispatchInvalidateOnAnimation(View view) {
    mInvalidateOnAnimationRunnable.addView(view);
}

將這個View加入到了InvalidateOnAnimationRunnable這個Runnable中的集合中,在這個Runnable的run()中汗侵,遍歷了集合中的每個View幸缕,調(diào)用View的invalidate()后釋放。invalidate()就是去在UI線程中重繪View的晰韵,最后View就在新的位置顯示了发乔。

@Override
public void run() {
    //......
    for (int i = 0; i < viewCount; i++) {
        mTempViews[i].invalidate();
        mTempViews[i] = null;
    }
    //......
}

總結(jié)一下,簡單來說邏輯就是改變mScrollX和mScrollY的值雪猪,之后刷新UI栏尚,顯示在新位置。

3. scrollBy()的實現(xiàn)

public void scrollBy(int x, int y) {
    scrollTo(mScrollX + x, mScrollY + y);
}

scrollBy()就是調(diào)用了scrollTo只恨,只不過參數(shù)加上了當前已有的偏移量译仗。所以可以猜到scrollBy()是相對于當前偏移的基礎(chǔ)上相對移動x y的像素值,而scrollTo()是相對于View的原始位置絕對移動官觅。

4. mScrollX 和 mScrollY的正負

如下圖所示纵菌,白色框是View自身的位置,灰色是View的內(nèi)容移動后的位置缰猴,那么假設(shè)偏移量都為100产艾,mScrollX的值就是100疤剑,mScrollY的值是100滑绒,單位是像素,都是正值隘膘。

下面View的內(nèi)容移動到了右下角疑故,此時mScrollX和mScrollY的值就是負的了。

動畫

使用動畫來移動View弯菊,可以使用View動畫纵势,也可以使用屬性動畫(3.0版本以下需要使用nineoldandroid)。

1. 使用View動畫

首先可以在xml中定義一個動畫集合管钳。

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:fillAfter="true"
    android:zAdjustment="normal">

    <translate
        android:duration="100"
        android:fromXDelta="0"
        android:fromYDelta="0"
        android:toXDelta="100"
        android:toYDelta="100"
        android:interpolator="@android:anim/linear_interpolator"/>

</set>

這個動畫會讓View從原始位置向右下方平移100個像素钦铁。

再對View對象開始動畫,傳入加載進來的上面寫的動畫才漆。

tv.startAnimation(AnimationUtils.loadAnimation(MainActivity.this, R.anim.anim_view_event));

2. 使用屬性動畫

使用ObjectAnimator類去設(shè)置動畫牛曹。

ObjectAnimator.ofFloat(tv, "translationX", 0, 10).setDuration(100).start();

3. 注意

  • 使用View動畫其實并不是改變View的真正位置,而是移動View的影像醇滥,不會改變View的真實位置參數(shù)黎比。

這就會導致一個問題超营,如果View有點擊事件,新位置并不能觸發(fā)點擊事件阅虫,而是原位置仍能觸發(fā)演闭,盡管View看起來已經(jīng)不在原先的位置上了。

  • 屬性動畫改變View本身屬性只能兼容到Android3.0颓帝,所以如果需要兼容更低的版本米碰,就必須要使用開源動畫庫nineoldandroid。

改變布局參數(shù)

使用

MarginLayoutParams params = (MarginLayoutParams) tv.getLayoutParams();
params.leftMargin += 100;
tv.requestLayout();
//tv.setLayoutParams(params); 也可以使用這個重新設(shè)置參數(shù)

改變布局參數(shù)的方法可以通過更改margin來改變View的位置達到移動的效果购城,這種方法需要根據(jù)實際去做不同的處理见间。

滑動對比

滑動方式 優(yōu)點 缺點
scrollTo() / scrollBy() 簡單易使用,不影響點擊事件 只能移動View的內(nèi)容工猜,不能移動View本身
View動畫 能夠?qū)崿F(xiàn)復雜的效果 只能改變View的影像米诉,會影響View的點擊事件
屬性動畫 3.0以上移動View本身,能夠?qū)崿F(xiàn)復雜的效果 3.0以下不能改變View本身屬性篷帅,需要nineoldandroid來兼容
改變參數(shù) 不會影響點擊事件史侣,改變的是View自身的屬性 使用稍麻煩,需要根據(jù)需求來靈活應(yīng)用

再總結(jié)一下適用場景:

  • scrollTo() / scrollBy(): 操作簡單魏身,適合對于View的內(nèi)容的移動惊橱。
  • 動畫:操作簡單,主要適用于對沒有交互的移動和復雜的動畫效果箭昵。
  • 改變參數(shù):操作稍微復雜税朴,適用于有交互的移動。

彈性滑動

前面的方法其實只能叫做移動家制,并不能叫滑動正林。彈性滑動有一個共同的思想,在一段時間內(nèi)將一次大的滑動分成若干次小的滑動來完成颤殴。

Scoller

Scroller本身無法實現(xiàn)彈性滑動觅廓,需要和View的computeScroll()配合使用。在最后通過分析可以發(fā)現(xiàn)也是通過scrollTo()實現(xiàn)滑動的涵但,所以它也是View內(nèi)容的滑動杈绸,而不是View本身的滑動。

使用

自定義一個TextView矮瘟,實現(xiàn)TextView的文字向手指點擊的地方彈性滑動瞳脓。

public class MyTextView extends TextView {

    private Scroller mScroller;
    private int xDown;
    private int yDown;

    //...

    public MyTextView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        mScroller = new Scroller(context);//初始化Scroller對象
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch(event.getAction()) {
            case MotionEvent.ACTION_DOWN://記錄點擊的相對坐標
                xDown = (int) event.getX();
                yDown = (int) event.getY();
                break;
            case MotionEvent.ACTION_UP:
                smoothScroll(-xDown, -yDown);//調(diào)用自定義的彈性滑動

        }
        return true;
    }

    //自定義的彈性滑動方法
    public void smoothScroll(int destX, int destY) {
        //畫的初始滑動偏移
        int scrollX = getScrollX();
        int scrollY = getScrollY();
        //計算需要滑動的兩個方向的大小
        int deltaX = -destX - scrollX;
        int deltaY = -destY - scrollY;
        調(diào)用Scroller對象的startScroll()
        mScroller.startScroll(scrollX, scrollY,  deltaX, deltaY, 1000);
        invalidate();//重繪
    }

    //固定的重寫compuuteScroll
    @Override
    public void computeScroll() {
        if (mScroller.computeScrollOffset()) {
            scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
            postInvalidate();
        }
    }
}
  1. 初始化Scroller對象。
  2. 實現(xiàn)computeScroll()澈侠。
  3. 自定義彈性滑動的方法劫侧,內(nèi)部調(diào)用Scroller對象的startScroll()、invalidate()埋涧。
  4. 就可以調(diào)用自定義的彈性滑動方法進行彈性滑動了板辽。

實現(xiàn)

1. startScroll()

public void startScroll(int startX, int startY, int dx, int dy, int duration) {
    mMode = SCROLL_MODE;
    mFinished = false;
    mDuration = duration;
    mStartTime = AnimationUtils.currentAnimationTimeMillis();
    mStartX = startX;
    mStartY = startY;
    mFinalX = startX + dx;
    mFinalY = startY + dy;
    mDeltaX = dx;
    mDeltaY = dy;
    mDurationReciprocal = 1.0f / (float) mDuration;
}

startScroll()只是進行了一些計算和參數(shù)的記錄奇瘦,并沒有進行真正的滑動工作。四個參數(shù)分別是其實位置的x劲弦、y坐標耳标,x、y方向的滑動距離邑跪,滑動的時間間隔次坡。

2. invalidate()

invalidate()會導致View的重繪調(diào)用View的draw(),View的draw()中又會去調(diào)用computeScroll()画畅,computeScroll()在View中是一個空實現(xiàn)砸琅,所以需要我們自己去實現(xiàn)。

3. computeScroll()

public void computeScroll() {
    if (mScroller.computeScrollOffset()) {
        scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
        postInvalidate();
    }
}
  • 如果想實現(xiàn)彈性滑動這樣的需求轴踱,其實computeScroll()的實現(xiàn)和上面寫成一樣就可以了症脂,不需要做其他的改動。發(fā)現(xiàn)在這個方法里淫僻,還是調(diào)用了scrollTo()诱篷,所以Scroller彈性滑動也是用scrollTo()實現(xiàn)的。

  • 就能猜到computeScrollOffset()是用來計算CurrX和CurY的雳灵,也就是最初提到的將一個大滑動拆分成小滑動棕所,computeScrollOffset()就是去計算每一次小滑動的坐標的。

  • 最后調(diào)用postInvalidate()進行下一次重繪悯辙,重復之前的操作琳省。

4. computeScrollOffset()

最后再來單獨看一下computeScrollOffset()的實現(xiàn)。

public boolean computeScrollOffset() {
    if (mFinished) {
        return false;
    }
    int timePassed = (int)(AnimationUtils.currentAnimationTimeMillis() - mStartTime);
    if (timePassed < mDuration) {
        switch (mMode) {
        case SCROLL_MODE:
            final float x = mInterpolator.getInterpolation(timePassed * mDurationReciprocal);
            mCurrX = mStartX + Math.round(x * mDeltaX);
            mCurrY = mStartY + Math.round(x * mDeltaY);
            break;
        //...
        }
    } else {
        mCurrX = mFinalX;
        mCurrY = mFinalY;
        mFinished = true;
    }
    return true;
}
  • 它首先判斷是否完成躲撰,如果已經(jīng)完成就直接返回false针贬。
  • 如果還沒完成,計算過去的時間茴肥,如果還有剩余坚踩,就根據(jù)時間百分比計算下一個滑動位置荡灾,返回true瓤狐。
  • 如果已經(jīng)超過時間,就賦值下一個滑動位置為目標位置批幌,并將mFinished變成true础锐,返回true。
  • 在調(diào)用computeScrollOffset()的地方荧缘,如果computeScrollOffset()返回了true就進行scrollTo()并重新繪制皆警。

動畫屬性

除了利用Scroller的computeScrollOffset()來分成小份計算位移,還可以利用動畫屬性截粗。前面介紹的View動畫和屬性動畫都屬于彈性動畫信姓。除了直接使用動畫鸵隧,還可以利用動畫的特性。

使用

final int startX = 0;
final int deltaX = -100;
final ValueAnimator animator = ValueAnimator.ofInt(0, 1).setDuration(1000);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    @Override
    public void onAnimationUpdate(ValueAnimator animation) {
        float fraction = animation.getAnimatedFraction();
        tv.scrollTo(startX + (int)(deltaX * fraction), 0);
    }
});

tv.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        animator.start();
    }
});

利用動畫的回調(diào)意推,實現(xiàn)像Scroller類似的豆瘫,在動畫改變的時候通過onAnimationUpdate()監(jiān)聽,獲得百分比菊值,調(diào)用scrollTo()滑動一小步外驱,也是View內(nèi)容的滑動。

延時策略

通過發(fā)送延時消息從而達到漸近式的效果腻窒£怯睿可以使用Handler、View的postDelayed()儿子、Thread的sleep()瓦哎。具體的思路其實和上面是一樣的,只不過這里需要自己去實現(xiàn)延時柔逼,而上面的方法已經(jīng)內(nèi)部實現(xiàn)杭煎,只需要計算小段位移后進行小段滑動就可以了。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末卒落,一起剝皮案震驚了整個濱河市羡铲,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌儡毕,老刑警劉巖也切,帶你破解...
    沈念sama閱讀 218,941評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異腰湾,居然都是意外死亡雷恃,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評論 3 395
  • 文/潘曉璐 我一進店門费坊,熙熙樓的掌柜王于貴愁眉苦臉地迎上來倒槐,“玉大人,你說我怎么就攤上這事附井√衷剑” “怎么了?”我有些...
    開封第一講書人閱讀 165,345評論 0 356
  • 文/不壞的土叔 我叫張陵永毅,是天一觀的道長把跨。 經(jīng)常有香客問我,道長沼死,這世上最難降的妖魔是什么着逐? 我笑而不...
    開封第一講書人閱讀 58,851評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上耸别,老公的妹妹穿的比我還像新娘健芭。我一直安慰自己,他們只是感情好秀姐,可當我...
    茶點故事閱讀 67,868評論 6 392
  • 文/花漫 我一把揭開白布吟榴。 她就那樣靜靜地躺著,像睡著了一般囊扳。 火紅的嫁衣襯著肌膚如雪吩翻。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,688評論 1 305
  • 那天锥咸,我揣著相機與錄音狭瞎,去河邊找鬼。 笑死搏予,一個胖子當著我的面吹牛熊锭,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播雪侥,決...
    沈念sama閱讀 40,414評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼碗殷,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了速缨?” 一聲冷哼從身側(cè)響起锌妻,我...
    開封第一講書人閱讀 39,319評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎旬牲,沒想到半個月后仿粹,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,775評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡原茅,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年吭历,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片擂橘。...
    茶點故事閱讀 40,096評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡晌区,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出通贞,到底是詐尸還是另有隱情朗若,我是刑警寧澤,帶...
    沈念sama閱讀 35,789評論 5 346
  • 正文 年R本政府宣布滑频,位于F島的核電站捡偏,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏峡迷。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,437評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望绘搞。 院中可真熱鬧彤避,春花似錦、人聲如沸夯辖。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽蒿褂。三九已至圆米,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間啄栓,已是汗流浹背娄帖。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評論 1 271
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留昙楚,地道東北人近速。 一個月前我還...
    沈念sama閱讀 48,308評論 3 372
  • 正文 我出身青樓,卻偏偏與公主長得像堪旧,于是被迫代替她去往敵國和親削葱。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,037評論 2 355