Android Scroller大揭秘
在學(xué)習(xí)使用Scroller之前,需要明白scrollTo()、scrollBy()方法。
一、View的scrollTo()、scrollBy()
scrollTo、scrollBy方法是View中的,因此任何的View都可以通過(guò)這兩種方法進(jìn)行移動(dòng)。首先要明白的是,scrollTo、scrollBy滑動(dòng)的是View中的內(nèi)容(而且還是整體滑動(dòng)),而不是View本身。我們的滑動(dòng)控件如SrollView可以限定寬、高大小,以及在布局中的位置,但是滑動(dòng)控件中的內(nèi)容(或者里面的childView)可以是無(wú)限長(zhǎng)、寬的,我們調(diào)用View的scrollTo、scrollBy方法,相當(dāng)于是移動(dòng)滑動(dòng)控件中的畫(huà)布Canvas,然后進(jìn)行重繪,屏幕上也就顯示相應(yīng)的內(nèi)容。如下:

1、getScrollX()、getScrollY()
在學(xué)習(xí)scrollTo()、scrollBy()之前,先來(lái)了解一下getScrollX()、getScrollY()方法。
getScrollX()、getScrollY()得到的是偏移量,是相對(duì)自己初始位置的滑動(dòng)偏移距離,只有當(dāng)有scroll事件發(fā)生時(shí),這兩個(gè)方法才能有值,否則getScrollX()、getScrollY()都是初始時(shí)的值0,而不管你這個(gè)滑動(dòng)控件在哪里。所謂自己初始位置是指,控件在剛開(kāi)始顯示時(shí)、沒(méi)有滑動(dòng)前的位置。以getScrollX()為例,其源碼如下:
public final int getScrollX() {
return mScrollX;
}
可以看到getScrollX()直接返回的就是mScrollX,代表水平方向上的偏移量,getScrollY()也類似。偏移量mScrollX的正、負(fù)代表著,滑動(dòng)控件中的內(nèi)容相對(duì)于初始位置在水平方向上偏移情況,mScrollX為正代表著當(dāng)前內(nèi)容相對(duì)于初始位置向左偏移了mScrollX的距離,mScrollX為負(fù)表示當(dāng)前內(nèi)容相對(duì)于初始位置向右偏移了mScrollX的距離。
這里的坐標(biāo)系和我們平常的認(rèn)知正好相反。為了以后更方便的處理滑動(dòng)相關(guān)坐標(biāo)和偏移,在處理偏移、滑動(dòng)相關(guān)的功能時(shí),我們就可以把坐標(biāo)反過(guò)來(lái)看,如下圖:

因?yàn)榛瑒?dòng)控件中的內(nèi)容是整體進(jìn)行滑動(dòng)的,同時(shí)也是相對(duì)于自己顯示時(shí)的初始位置的偏移,對(duì)于View中內(nèi)容在偏移時(shí)的參考坐標(biāo)原點(diǎn)(注意是內(nèi)容視圖的坐標(biāo)原點(diǎn),不是圖中說(shuō)的滑動(dòng)控件的原點(diǎn)),可以選擇初始位置的某一個(gè)地方,因?yàn)榛瑒?dòng)時(shí)整體行為,在進(jìn)行滑動(dòng)的時(shí)候從這個(gè)選擇的原點(diǎn)出進(jìn)行分析即可。
2、scrollTo()、scrollBy()
scrollTo(int x,int y)移動(dòng)的是View中的內(nèi)容,而滑動(dòng)控件中的內(nèi)容都是整體移動(dòng)的,scrollTo(int x,int y)中的參數(shù)表示View中的內(nèi)容要相對(duì)于內(nèi)容初始位置移動(dòng)x和y的距離,即將內(nèi)容移動(dòng)到距離內(nèi)容初始位置x和y的位置。正如前面所說(shuō),在處理偏移、滑動(dòng)問(wèn)題時(shí)坐標(biāo)系和平常認(rèn)知的坐標(biāo)系是相反的。以一個(gè)例子說(shuō)明scrollTo():
說(shuō)明:圖中黃色矩形區(qū)域表示的是一個(gè)可滑動(dòng)的View控件,綠色虛線矩形為滑動(dòng)控件中的滑動(dòng)內(nèi)容。注意這里的坐標(biāo)是相反的。(例子來(lái)源于:http://blog.csdn.net/bigconvience/article/details/26697645)
(1)調(diào)用scrollTo(100,0)表示將View中的內(nèi)容移動(dòng)到距離內(nèi)容初始顯示位置的x=100,y=0的地方,效果如下圖:

(2)調(diào)用scrollTo(0,100)效果如下圖:

(3)調(diào)用scrollTo(100,100)效果如下圖:

(4)調(diào)用scrollTo(-100,0)效果如下圖:

通過(guò)上面幾個(gè)圖,可以清楚看到scrollTo的作用和滑動(dòng)坐標(biāo)系的關(guān)系。在實(shí)際使用中,我們一般是在onTouchEvent()方法中處理滑動(dòng)事件,在MotionEvent.ACTION_MOVE時(shí)調(diào)用scrollTo(int x,int y)進(jìn)行滑動(dòng),在調(diào)用scrollTo(int x,int y)前,我們先要計(jì)算出兩個(gè)參數(shù)值,即水平和垂直方向需要滑動(dòng)的距離,如下:
@Override
public boolean onTouchEvent(MotionEvent event) {
int y = (int) event.getY();
int action = event.getAction();
switch (action){
case MotionEvent.ACTION_DOWN:
mLastY = y;
break;
case MotionEvent.ACTION_MOVE:
int dy = mLastY - y;//本次手勢(shì)滑動(dòng)了多大距離
int oldScrollY = getScrollY();//先計(jì)算之前已經(jīng)偏移了多少距離
int scrollY = oldScrollY + dy;//本次需要偏移的距離=之前已經(jīng)偏移的距離+本次手勢(shì)滑動(dòng)了多大距離
if(scrollY < 0){
scrollY = 0;
}
if(scrollY > getHeight() - mScreenHeight){
scrollY = getHeight() - mScreenHeight;
}
scrollTo(getScrollX(),scrollY);
mLastY = y;
break;
}
return true;
}
上面在計(jì)算參數(shù)時(shí),分為了三步。第一是,通過(guò)int dy = mLastY - y;得到本次手勢(shì)在屏幕上滑動(dòng)了多少距離,這里要特別注意這個(gè)相減順序,因?yàn)檫@里的坐標(biāo)與平常是相反的,因此,手勢(shì)滑動(dòng)距離是按下時(shí)的坐標(biāo)mLastY - 當(dāng)前的坐標(biāo)y;第二是,通過(guò)oldScrollY = getScrollY();獲得滑動(dòng)內(nèi)容之前已經(jīng)距初始位置便宜了多少;第三是,計(jì)算本次需要偏移的參數(shù)int scrollY = oldScrollY + dy; 后面通過(guò)兩個(gè)if條件進(jìn)行了邊界處理,然后調(diào)用scrollTo進(jìn)行滑動(dòng)。調(diào)用完scrollTo后,新的偏移量又重新產(chǎn)生了。從scrollTo源碼中可以看到:
public void scrollTo(int x, int y) {
if (mScrollX != x || mScrollY != y) {
int oldX = mScrollX;
int oldY = mScrollY;
mScrollX = x;//賦值新的x偏移量
mScrollY = y;//賦值新的y偏移量
invalidateParentCaches();
onScrollChanged(mScrollX, mScrollY, oldX, oldY);
if (!awakenScrollBars()) {
postInvalidateOnAnimation();
}
}
}
scrollTo是相對(duì)于初始位置來(lái)進(jìn)行移動(dòng)的,而scrollBy(int x ,int y)則是相對(duì)于上一次移動(dòng)的距離來(lái)進(jìn)行本次移動(dòng)。scrollBy其實(shí)還是依賴于scrollTo的,如下源碼:
public void scrollBy(int x, int y) {
scrollTo(mScrollX + x, mScrollY + y);
}
可以看到,使用scrollBy其實(shí)就是省略了我們?cè)谟?jì)算scrollTo參數(shù)時(shí)的第三步而已,因?yàn)閟crollBy內(nèi)部已經(jīng)自己幫我加上了第三步的計(jì)算。因此scrollBy的作用就是相當(dāng)于在上一次的偏移情況下進(jìn)行本次的偏移。
一個(gè)完整的水平方向滑動(dòng)的例子:
public class MyViewPager extends ViewGroup {
private int mLastX;
public MyViewPager(Context context) {
super(context);
init(context);
}
public MyViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public MyViewPager(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
private void init(Context context) {
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int count = getChildCount();
for(int i = 0; i < count; i++){
View child = getChildAt(i);
child.measure(widthMeasureSpec,heightMeasureSpec);
}
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int count = getChildCount();
Log.d("TAG","--l-->"+l+",--t-->"+t+",-->r-->"+r+",--b-->"+b);
for(int i = 0; i < count; i++){
View child = getChildAt(i);
child.layout(i * getWidth(), t, (i+1) * getWidth(), b);
}
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
int x = (int) ev.getX();
switch (ev.getAction()){
case MotionEvent.ACTION_DOWN:
mLastX = x;
break;
case MotionEvent.ACTION_MOVE:
int dx = mLastX - x;
int oldScrollX = getScrollX();//原來(lái)的偏移量
int preScrollX = oldScrollX + dx;//本次滑動(dòng)后形成的偏移量
if(preScrollX > (getChildCount() - 1) * getWidth()){
preScrollX = (getChildCount() - 1) * getWidth();
}
if(preScrollX < 0){
preScrollX = 0;
}
scrollTo(preScrollX,getScrollY());
mLastX = x;
break;
}
return true;
}
}
布局文件:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <com.scu.lly.viewtest.view.MyViewPager android:layout_width="match_parent" android:layout_height="300dp" > <ImageView android:layout_width="match_parent" android:layout_height="match_parent" android:scaleType="fitXY" android:src="@drawable/test1" /> <ImageView android:layout_width="match_parent" android:layout_height="match_parent" android:scaleType="fitXY" android:src="@drawable/test2" /> <ImageView android:layout_width="match_parent" android:layout_height="match_parent" android:scaleType="fitXY" android:src="@drawable/test3" /> <ImageView android:layout_width="match_parent" android:layout_height="match_parent" android:scaleType="fitXY" android:src="@drawable/test4" /> </com.scu.lly.viewtest.view.MyViewPager> </LinearLayout>
效果如圖:

二、Scroller滑動(dòng)輔助類
根據(jù)我們上面的分析,可知View的scrollTo()、scrollBy()是瞬間完成的,當(dāng)我們的手指在屏幕上移動(dòng)時(shí),內(nèi)容會(huì)跟著手指滑動(dòng),但是當(dāng)我們手指一抬起時(shí),滑動(dòng)就會(huì)停止,如果我們想要有一種慣性的滾動(dòng)過(guò)程效果和回彈效果,此時(shí)就需要使用Scroller輔助類。
但是注意的是,Scroller本身不會(huì)去移動(dòng)View,它只是一個(gè)移動(dòng)計(jì)算輔助類,用于跟蹤控件滑動(dòng)的軌跡,只相當(dāng)于一個(gè)滾動(dòng)軌跡記錄工具,最終還是通過(guò)View的scrollTo、scrollBy方法完成View的移動(dòng)的。
在使用Scroller類之前,先了解其重要的兩個(gè)方法:
(1)startScroll()
public void startScroll(int startX, int startY, int dx, int dy, int duration)
開(kāi)始一個(gè)動(dòng)畫(huà)控制,由(startX , startY)在duration時(shí)間內(nèi)前進(jìn)(dx,dy)個(gè)單位,即到達(dá)偏移坐標(biāo)為(startX+dx , startY+dy)處。
(2)computeScrollOffset()
public boolean computeScrollOffset()
滑動(dòng)過(guò)程中,根據(jù)當(dāng)前已經(jīng)消逝的時(shí)間計(jì)算當(dāng)前偏移的坐標(biāo)點(diǎn),保存在mCurrX和mCurrY值中。
上面兩個(gè)方法的源碼如下:
public class Scroller {
private int mStartX;//水平方向,滑動(dòng)時(shí)的起點(diǎn)偏移坐標(biāo)
private int mStartY;//垂直方向,滑動(dòng)時(shí)的起點(diǎn)偏移坐標(biāo)
private int mFinalX;//滑動(dòng)完成后的偏移坐標(biāo),水平方向
private int mFinalY;//滑動(dòng)完成后的偏移坐標(biāo),垂直方向
private int mCurrX;//滑動(dòng)過(guò)程中,根據(jù)消耗的時(shí)間計(jì)算出的當(dāng)前的滑動(dòng)偏移距離,水平方向
private int mCurrY;//滑動(dòng)過(guò)程中,根據(jù)消耗的時(shí)間計(jì)算出的當(dāng)前的滑動(dòng)偏移距離,垂直方向
private int mDuration; //本次滑動(dòng)的動(dòng)畫(huà)時(shí)間
private float mDeltaX;//滑動(dòng)過(guò)程中,在達(dá)到mFinalX前還需要滑動(dòng)的距離,水平方向
private float mDeltaY;//滑動(dòng)過(guò)程中,在達(dá)到mFinalX前還需要滑動(dòng)的距離,垂直方向
public void startScroll(int startX, int startY, int dx, int dy) {
startScroll(startX, startY, dx, dy, DEFAULT_DURATION);
}
/**
* 開(kāi)始一個(gè)動(dòng)畫(huà)控制,由(startX , startY)在duration時(shí)間內(nèi)前進(jìn)(dx,dy)個(gè)單位,即到達(dá)偏移坐標(biāo)為(startX+dx , startY+dy)處
*/
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;//確定本次滑動(dòng)完成后的偏移坐標(biāo)
mFinalY = startY + dy;
mDeltaX = dx;
mDeltaY = dy;
mDurationReciprocal = 1.0f / (float) mDuration;
}
/**
* 滑動(dòng)過(guò)程中,根據(jù)當(dāng)前已經(jīng)消逝的時(shí)間計(jì)算當(dāng)前偏移的坐標(biāo)點(diǎn),保存在mCurrX和mCurrY值中
* @return
*/
public boolean computeScrollOffset() {
if (mFinished) {//已經(jīng)完成了本次動(dòng)畫(huà)控制,直接返回為false
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);//計(jì)算出當(dāng)前的滑動(dòng)偏移位置,x軸
mCurrY = mStartY + Math.round(x * mDeltaY);//計(jì)算出當(dāng)前的滑動(dòng)偏移位置,y軸
break;
...
}
}else {
mCurrX = mFinalX;
mCurrY = mFinalY;
mFinished = true;
}
return true;
}
...
}
Scroller類中最重要的兩個(gè)方法就是startScroll()和computeScrollOffset(),但是Scroller類只是一個(gè)滑動(dòng)計(jì)算輔助類,它的startScroll()和computeScrollOffset()方法中也只是對(duì)一些軌跡參數(shù)進(jìn)行設(shè)置和計(jì)算,真正需要進(jìn)行滑動(dòng)還是得通過(guò)View的scrollTo()、scrollBy()方法。為此,View中提供了computeScroll()方法來(lái)控制這個(gè)滑動(dòng)流程。computeScroll()方法會(huì)在繪制子視圖的時(shí)候進(jìn)行調(diào)用。其源碼如下:
/**
* Called by a parent to request that a child update its values for mScrollX
* and mScrollY if necessary. This will typically be done if the child is
* animating a scroll using a {@link android.widget.Scroller Scroller}
* object.
* 由父視圖調(diào)用用來(lái)請(qǐng)求子視圖根據(jù)偏移值 mScrollX,mScrollY重新繪制
*/
public void computeScroll() { //空方法 ,自定義滑動(dòng)功能的ViewGroup必須實(shí)現(xiàn)方法體
}
因此Scroller類的基本使用流程可以總結(jié)如下:
(1)首先通過(guò)Scroller類的startScroll()開(kāi)始一個(gè)滑動(dòng)動(dòng)畫(huà)控制,里面進(jìn)行了一些軌跡參數(shù)的設(shè)置和計(jì)算;
(2)在調(diào)用startScroll()的后面調(diào)用invalidate();引起視圖的重繪操作,從而觸發(fā)ViewGroup中的computeScroll()被調(diào)用;
(3)在computeScroll()方法中,先調(diào)用Scroller類中的computeScrollOffset()方法,里面根據(jù)當(dāng)前消耗時(shí)間進(jìn)行軌跡坐標(biāo)的計(jì)算,然后取得計(jì)算出的當(dāng)前滑動(dòng)的偏移坐標(biāo),調(diào)用View的scrollTo()方法進(jìn)行滑動(dòng)控制,最后也需要調(diào)用invalidate();進(jìn)行重繪。
如下的一個(gè)簡(jiǎn)單代碼示例:
@Override
public boolean onTouchEvent(MotionEvent ev) {
initVelocityTrackerIfNotExists();
mVelocityTracker.addMovement(ev);
int x = (int) ev.getX();
switch (ev.getAction()){
case MotionEvent.ACTION_DOWN:
if(!mScroller.isFinished()){
mScroller.abortAnimation();
}
mLastX = x;
break;
case MotionEvent.ACTION_MOVE:
int dx = mLastX - x;
int oldScrollX = getScrollX();//原來(lái)的偏移量
int preScrollX = oldScrollX + dx;//本次滑動(dòng)后形成的偏移量
if(preScrollX > (getChildCount() - 1) * getWidth()){
preScrollX = (getChildCount() - 1) * getWidth();
}
if(preScrollX < 0){
preScrollX = 0;
}
//開(kāi)始滑動(dòng)動(dòng)畫(huà)
mScroller.startScroll(mScroller.getFinalX(),mScroller.getFinalY(),dx,0);//第一步
//注意,一定要進(jìn)行invalidate刷新界面,觸發(fā)computeScroll()方法,因?yàn)閱渭兊膕tartScroll()是屬于Scroller的,只是一個(gè)輔助類,并不會(huì)觸發(fā)界面的繪制
invalidate();
mLastX = x;
break;
}
return true;
}
@Override
public void computeScroll() {
super.computeScroll();
if(mScroller.computeScrollOffset()){//第二步
scrollTo(mScroller.getCurrX(),mScroller.getCurrY());//第三步
invalidate();
}
}
下面是一個(gè)完整的例子:一個(gè)類似ViewPager的Demo,效果圖如下:

代碼如下:
public class MyViewPager3 extends ViewGroup {
private int mLastX;
private Scroller mScroller;
private VelocityTracker mVelocityTracker;
private int mTouchSlop;
private int mMaxVelocity;
/**
* 當(dāng)前顯示的是第幾個(gè)屏幕
*/
private int mCurrentPage = 0;
public MyViewPager3(Context context) {
super(context);
init(context);
}
public MyViewPager3(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public MyViewPager3(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
private void init(Context context) {
mScroller = new Scroller(context);
ViewConfiguration config = ViewConfiguration.get(context);
mTouchSlop = config.getScaledPagingTouchSlop();
mMaxVelocity = config.getScaledMinimumFlingVelocity();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int count = getChildCount();
for(int i = 0; i < count; i++){
View child = getChildAt(i);
child.measure(widthMeasureSpec, heightMeasureSpec);
}
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int count = getChildCount();
Log.d("TAG","--l-->"+l+",--t-->"+t+",-->r-->"+r+",--b-->"+b);
for(int i = 0; i < count; i++){
View child = getChildAt(i);
child.layout(i * getWidth(), t, (i + 1) * getWidth(), b);
}
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
initVelocityTrackerIfNotExists();
mVelocityTracker.addMovement(ev);
int x = (int) ev.getX();
switch (ev.getAction()){
case MotionEvent.ACTION_DOWN:
if(!mScroller.isFinished()){
mScroller.abortAnimation();
}
mLastX = x;
break;
case MotionEvent.ACTION_MOVE:
int dx = mLastX - x;
/* 注釋的里面是使用startScroll()來(lái)進(jìn)行滑動(dòng)的
int oldScrollX = getScrollX();//原來(lái)的偏移量
int preScrollX = oldScrollX + dx;//本次滑動(dòng)后形成的偏移量
if (preScrollX > (getChildCount() - 1) * getWidth()) {
preScrollX = (getChildCount() - 1) * getWidth();
dx = preScrollX - oldScrollX;
}
if (preScrollX < 0) {
preScrollX = 0;
dx = preScrollX - oldScrollX;
}
mScroller.startScroll(mScroller.getFinalX(), mScroller.getFinalY(), dx, 0);
//注意,使用startScroll后面一定要進(jìn)行invalidate刷新界面,觸發(fā)computeScroll()方法,因?yàn)閱渭兊膕tartScroll()是屬于Scroller的,只是一個(gè)輔助類,并不會(huì)觸發(fā)界面的繪制
invalidate();
*/
//但是一般在ACTION_MOVE中我們直接使用scrollTo或者scrollBy更加方便
scrollBy(dx,0);
mLastX = x;
break;
case MotionEvent.ACTION_UP:
final VelocityTracker velocityTracker = mVelocityTracker;
velocityTracker.computeCurrentVelocity(1000);
int initVelocity = (int) velocityTracker.getXVelocity();
if(initVelocity > mMaxVelocity && mCurrentPage > 0){//如果是快速的向右滑,則需要顯示上一個(gè)屏幕
Log.d("TAG","----------------快速的向右滑--------------------");
scrollToPage(mCurrentPage - 1);
}else if(initVelocity < -mMaxVelocity && mCurrentPage < (getChildCount() - 1)){//如果是快速向左滑動(dòng),則需要顯示下一個(gè)屏幕
Log.d("TAG","----------------快速的向左滑--------------------");
scrollToPage(mCurrentPage + 1);
}else{//不是快速滑動(dòng)的情況,此時(shí)需要計(jì)算是滑動(dòng)到
Log.d("TAG","----------------慢慢的滑動(dòng)--------------------");
slowScrollToPage();
}
recycleVelocityTracker();
break;
}
return true;
}
/**
* 緩慢滑動(dòng)抬起手指的情形,需要判斷是停留在本Page還是往前、往后滑動(dòng)
*/
private void slowScrollToPage() {
//當(dāng)前的偏移位置
int scrollX = getScrollX();
int scrollY = getScrollY();
//判斷是停留在本Page還是往前一個(gè)page滑動(dòng)或者是往后一個(gè)page滑動(dòng)
int whichPage = (getScrollX() + getWidth() / 2 ) / getWidth() ;
scrollToPage(whichPage);
}
/**
* 滑動(dòng)到指定屏幕
* @param indexPage
*/
private void scrollToPage(int indexPage) {
mCurrentPage = indexPage;
if(mCurrentPage > getChildCount() - 1){
mCurrentPage = getChildCount() - 1;
}
//計(jì)算滑動(dòng)到指定Page還需要滑動(dòng)的距離
int dx = mCurrentPage * getWidth() - getScrollX();
mScroller.startScroll(getScrollX(),0,dx,0,Math.abs(dx) * 2);//動(dòng)畫(huà)時(shí)間設(shè)置為Math.abs(dx) * 2 ms
//記住,使用Scroller類需要手動(dòng)invalidate
invalidate();
}
@Override
public void computeScroll() {
Log.d("TAG", "---------computeScrollcomputeScrollcomputeScroll--------------");
super.computeScroll();
if(mScroller.computeScrollOffset()){
scrollTo(mScroller.getCurrX(),mScroller.getCurrY());
invalidate();
}
}
private void recycleVelocityTracker() {
if (mVelocityTracker != null) {
mVelocityTracker.recycle();
mVelocityTracker = null;
}
}
private void initVelocityTrackerIfNotExists() {
if(mVelocityTracker == null){
mVelocityTracker = VelocityTracker.obtain();
}
}
}
布局文件如下:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <com.lusheep.viewtest.view.MyViewPager3 android:layout_width="match_parent" android:layout_height="200dp" android:background="#999" > <ImageView android:layout_width="300dp" android:layout_height="match_parent" android:scaleType="fitXY" android:src="@drawable/test1" /> <ImageView android:layout_width="300dp" android:layout_height="match_parent" android:scaleType="fitXY" android:src="@drawable/test2" /> <ImageView android:layout_width="300dp" android:layout_height="match_parent" android:scaleType="fitXY" android:src="@drawable/test3" /> <ImageView android:layout_width="300dp" android:layout_height="match_parent" android:scaleType="fitXY" android:src="@drawable/test4" /> </com.lusheep.viewtest.view.MyViewPager3> </LinearLayout>
簡(jiǎn)單總結(jié):
(1)Scroller類能夠幫助我們實(shí)現(xiàn)高級(jí)的滑動(dòng)功能,如手指抬起后的慣性滑動(dòng)功能。使用流程為,首先通過(guò)Scroller類的startScroll()+invalidate()觸發(fā)View的computeScroll(),在computeScroll()中讓Scroller類去計(jì)算最新的坐標(biāo)信息,拿到最新的坐標(biāo)偏移信息后還是要調(diào)用View的scrollTo來(lái)實(shí)現(xiàn)滑動(dòng)。可以看到,使用Scroller的整個(gè)流程比較簡(jiǎn)單,關(guān)鍵的是控制滑動(dòng)的一些邏輯計(jì)算,比如上面例子中的計(jì)算什么時(shí)候該往哪一頁(yè)滑動(dòng)...
(2)Android后面推出了OverScroller類,OverScroller在整體功能上和Scroller類似,使用也相同。OverScroller類可以完全代替Scroller,相比Scroller,OverScroller主要是增加了對(duì)滑動(dòng)到邊界的一些控制,如增加一些回彈效果等,功能更加強(qiáng)大。
以上就是本文的全部?jī)?nèi)容,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作能帶來(lái)一定的幫助,同時(shí)也希望多多支持腳本之家!
- Android使用Scroller實(shí)現(xiàn)彈性滑動(dòng)效果
- Android自定義View彈性滑動(dòng)Scroller詳解
- Android用Scroller實(shí)現(xiàn)一個(gè)可向上滑動(dòng)的底部導(dǎo)航欄
- 詳解Android應(yīng)用開(kāi)發(fā)中Scroller類的屏幕滑動(dòng)功能運(yùn)用
- android使用 ScrollerView 實(shí)現(xiàn) 可上下滾動(dòng)的分類欄實(shí)例
- 深入理解Android中Scroller的滾動(dòng)原理
- Android程序開(kāi)發(fā)之UIScrollerView里有兩個(gè)tableView
- Android Scroller完全解析
- Android Scroller及下拉刷新組件原理解析
- android開(kāi)發(fā)通過(guò)Scroller實(shí)現(xiàn)過(guò)渡滑動(dòng)效果操作示例
相關(guān)文章
Android 三種動(dòng)畫(huà)詳解及簡(jiǎn)單實(shí)例
這篇文章主要介紹了Android 三種動(dòng)畫(huà)詳解及簡(jiǎn)單實(shí)例的相關(guān)資料,需要的朋友可以參考下2017-04-04
Retrofit2.0 實(shí)現(xiàn)圖文(參數(shù)+圖片)上傳方法總結(jié)
本篇文章主要介紹了Retrofit2.0 實(shí)現(xiàn)圖文(參數(shù)+圖片)上傳方法總結(jié),具有一定的參考價(jià)值,有興趣的可以了解一下2017-08-08
Android Service服務(wù)不被停止詳解及實(shí)現(xiàn)
這篇文章主要介紹了Android Service服務(wù)不被停止詳解及實(shí)現(xiàn)的相關(guān)資料,有很多應(yīng)用在設(shè)置運(yùn)行中會(huì)被直接停止掉,這里就提供一個(gè)方法一直運(yùn)行,需要的朋友可以參考下2016-11-11
Android 實(shí)現(xiàn)帶字母索引的側(cè)邊欄功能
這篇文章主要介紹了Android 實(shí)現(xiàn)帶字母索引的側(cè)邊欄功能,需要的朋友可以參考下2017-08-08
Android ViewPager動(dòng)態(tài)加載問(wèn)題
這篇文章主要介紹了Android ViewPager動(dòng)態(tài)加載問(wèn)題,需要的朋友可以參考下2017-03-03
Android Studio出現(xiàn)Failed to pull selection: open failed: Permi
本篇文章給大家分享了Android Studio中導(dǎo)出數(shù)據(jù)庫(kù)文件的方法以及出現(xiàn)Failed to pull selection: open failed: Permission denied的解決思路,有興趣的學(xué)習(xí)下。2018-05-05
Android中Root權(quán)限獲取的簡(jiǎn)單代碼
那么我們?cè)贏ndroid開(kāi)發(fā)中如何獲取Android的Root權(quán)限呢?下面是主要的簡(jiǎn)單代碼。2013-06-06
Kotlin Service實(shí)現(xiàn)消息推送通知過(guò)程
這幾天分析了一下的啟動(dòng)過(guò)程,于是乎,今天寫(xiě)一下Service使用; 給我的感覺(jué)是它并不復(fù)雜,千萬(wàn)不要被一坨一坨的代碼嚇住了,雖然彎彎繞繞不少,重載函數(shù)一個(gè)接著一個(gè),就向走迷宮一樣,但只要抓住主線閱讀,很快就能找到出口2022-12-12
Android入門(mén)之讀寫(xiě)本地文件的實(shí)現(xiàn)
這篇文章主要為大家詳細(xì)介紹了Android如何實(shí)現(xiàn)讀寫(xiě)本地文件的功能,文中的示例代碼講解詳細(xì),對(duì)我們學(xué)習(xí)Android有一定的幫助,需要的可以參考一下2022-12-12

