Android Scroller完全解析
在Android中,任何一個(gè)控件都是可以滾動(dòng)的,因?yàn)樵赩iew類當(dāng)中有scrollTo()和scrollBy()這兩個(gè)方法,如下圖所示:
這兩個(gè)方法的主要作用是將View/ViewGroup移至指定的坐標(biāo)中,并且將偏移量保存起來。另外:
mScrollX 代表X軸方向的偏移坐標(biāo)
mScrollY 代表Y軸方向的偏移坐標(biāo)
這兩個(gè)方法都是用于對(duì)View進(jìn)行滾動(dòng)的,那么它們之間有什么區(qū)別呢?簡(jiǎn)單點(diǎn)講,scrollBy()方法是讓View相對(duì)于當(dāng)前的位置滾動(dòng)某段距離,而scrollTo()方法則是讓View相對(duì)于初始的位置滾動(dòng)某段距離。
關(guān)于偏移量的設(shè)置我們可以參看下源碼:
public class View { .... protected int mScrollX; //該視圖內(nèi)容相當(dāng)于視圖起始坐標(biāo)的偏移量,X軸方向 protected int mScrollY; //該視圖內(nèi)容相當(dāng)于視圖起始坐標(biāo)的偏移量,Y軸方向 //返回值 public final int getScrollX() { return mScrollX; } public final int getScrollY() { return mScrollY; } public void scrollTo(int x, int y) { //偏移位置發(fā)生了改變 if (mScrollX != x || mScrollY != y) { int oldX = mScrollX; int oldY = mScrollY; mScrollX = x; //賦新值,保存當(dāng)前便宜量 mScrollY = y; //回調(diào)onScrollChanged方法 onScrollChanged(mScrollX, mScrollY, oldX, oldY); if (!awakenScrollBars()) { invalidate(); //一般都引起重繪 } } } // 看出區(qū)別了吧 。 mScrollX 與 mScrollY 代表我們當(dāng)前偏移的位置 , 在當(dāng)前位置繼續(xù)偏移(x ,y)個(gè)單位 public void scrollBy(int x, int y) { scrollTo(mScrollX + x, mScrollY + y); } //... }
于是,在任何時(shí)刻我們都可以獲取該View/ViewGroup的偏移位置了,即調(diào)用getScrollX()方法和getScrollY()方法。
下面我們寫個(gè)例子看下它們的區(qū)別吧:
<?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:id="@+id/layout" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <Button android:id="@+id/scroll_to_btn" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="scrollTo"/> <Button android:id="@+id/scroll_by_btn" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="10dp" android:text="scrollBy"/> </LinearLayout>
外層使用了一個(gè)LinearLayout,在里面包含了兩個(gè)按鈕,一個(gè)用于觸發(fā)scrollTo邏輯,一個(gè)用于觸發(fā)scrollBy邏輯。
public class MainActivity extends AppCompatActivity { private LinearLayout layout; private Button scrollToBtn; private Button scrollByBtn; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); layout = (LinearLayout) findViewById(R.id.layout); scrollToBtn = (Button) findViewById(R.id.scroll_to_btn); scrollByBtn = (Button) findViewById(R.id.scroll_by_btn); scrollToBtn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { layout.scrollTo(getResources().getDimensionPixelOffset(R.dimen.horizontal_scroll), getResources().getDimensionPixelOffset(R.dimen.horizontal_scroll)); } }); scrollByBtn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { layout.scrollBy(getResources().getDimensionPixelOffset(R.dimen.horizontal_scroll), getResources().getDimensionPixelOffset(R.dimen.horizontal_scroll)); } }); } } <resources> <dimen name="horizontal_scroll">-20dp</dimen> <dimen name="vertical_scroll">-30dp</dimen> </resources>
當(dāng)點(diǎn)擊了scrollTo按鈕時(shí),我們調(diào)用了LinearLayout的scrollTo()方法,當(dāng)點(diǎn)擊了scrollBy按鈕時(shí),調(diào)用了LinearLayout的scrollBy()方法。那有的朋友可能會(huì)問了,為什么都是調(diào)用的LinearLayout中的scroll方法?這里一定要注意,不管是scrollTo()還是scrollBy()方法,滾動(dòng)的都是該View內(nèi)部的內(nèi)容,而LinearLayout中的內(nèi)容就是我們的兩個(gè)Button,如果你直接調(diào)用button的scroll方法的話,那結(jié)果一定不是你想看到的。
另外還有一點(diǎn)需要注意,就是兩個(gè)scroll方法中傳入的參數(shù),第一個(gè)參數(shù)x表示相對(duì)于當(dāng)前位置橫向移動(dòng)的距離,正值向左移動(dòng),負(fù)值向右移動(dòng)。第二個(gè)參數(shù)y表示相對(duì)于當(dāng)前位置縱向移動(dòng)的距離,正值向上移動(dòng),負(fù)值向下移動(dòng)。
運(yùn)行一下程序:
當(dāng)我們點(diǎn)擊scrollTo按鈕時(shí),兩個(gè)按鈕會(huì)一起向右下方滾動(dòng),之后再點(diǎn)擊scrollTo按鈕就沒有任何作用了,界面不會(huì)再繼續(xù)滾動(dòng),只有點(diǎn)擊scrollBy按鈕界面才會(huì)繼續(xù)滾動(dòng),并且不停點(diǎn)擊scrollBy按鈕界面會(huì)一起滾動(dòng)下去。
Scroller類
從上面例子運(yùn)行結(jié)果可以看出,利用scrollTo()/scrollBy()方法把一個(gè)View偏移至指定坐標(biāo)(x,y)處,整個(gè)過程是直接跳躍的,沒有對(duì)這個(gè)偏移過程有任何控制,對(duì)用戶而言不太友好。于是,基于這種偏移控制,Scroller類被設(shè)計(jì)出來了,該類的主要作用是為偏移過程制定一定的控制流程,從而使偏移更流暢,更完美。
我們分析下源碼里去看看Scroller類的相關(guān)方法,其源代碼(部分)如下: 路徑位于 \frameworks\base\core\Java\android\widget\Scroller.java
public class Scroller { private int mStartX; //起始坐標(biāo)點(diǎn) , X軸方向 private int mStartY; //起始坐標(biāo)點(diǎn) , Y軸方向 private int mCurrX; //當(dāng)前坐標(biāo)點(diǎn) X軸, 即調(diào)用startScroll函數(shù)后,經(jīng)過一定時(shí)間所達(dá)到的值 private int mCurrY; //當(dāng)前坐標(biāo)點(diǎn) Y軸, 即調(diào)用startScroll函數(shù)后,經(jīng)過一定時(shí)間所達(dá)到的值 private float mDeltaX; //應(yīng)該繼續(xù)滑動(dòng)的距離, X軸方向 private float mDeltaY; //應(yīng)該繼續(xù)滑動(dòng)的距離, Y軸方向 private boolean mFinished; //是否已經(jīng)完成本次滑動(dòng)操作, 如果完成則為 true //構(gòu)造函數(shù) public Scroller(Context context) { this(context, null); } public final boolean isFinished() { return mFinished; } //強(qiáng)制結(jié)束本次滑屏操作 public final void forceFinished(boolean finished) { mFinished = finished; } public final int getCurrX() { return mCurrX; } /* Call this when you want to know the new location. If it returns true, * the animation is not yet finished. loc will be altered to provide the * new location. */ //根據(jù)當(dāng)前已經(jīng)消逝的時(shí)間計(jì)算當(dāng)前的坐標(biāo)點(diǎn),保存在mCurrX和mCurrY值中 public boolean computeScrollOffset() { if (mFinished) { //已經(jīng)完成了本次動(dòng)畫控制,直接返回為false return false; } int timePassed = (int)(AnimationUtils.currentAnimationTimeMillis() - mStartTime); if (timePassed < mDuration) { switch (mMode) { case SCROLL_MODE: float x = (float)timePassed * mDurationReciprocal; ... mCurrX = mStartX + Math.round(x * mDeltaX); mCurrY = mStartY + Math.round(x * mDeltaY); break; ... } else { mCurrX = mFinalX; mCurrY = mFinalY; mFinished = true; } return true; } //開始一個(gè)動(dòng)畫控制,由(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) { mFinished = false; mDuration = duration; mStartTime = AnimationUtils.currentAnimationTimeMillis(); mStartX = startX; mStartY = startY; mFinalX = startX + dx; mFinalY = startY + dy; mDeltaX = dx; mDeltaY = dy; ... } }
其中比較重要的兩個(gè)方法為:
public boolean computeScrollOffset()
函數(shù)功能說明:根據(jù)當(dāng)前已經(jīng)消逝的時(shí)間計(jì)算當(dāng)前的坐標(biāo)點(diǎn),保存在mCurrX和mCurrY值中。
public void startScroll(int startX, int startY, int dx, int dy, int duration)
函數(shù)功能說明:開始一個(gè)動(dòng)畫控制,由(startX , startY)在duration時(shí)間內(nèi)前進(jìn)(dx,dy)個(gè)單位,到達(dá)坐標(biāo)為(startX+dx , startY+dy)處。
computeScroll()方法介紹:
為了易于控制滑屏控制,Android框架提供了 computeScroll()方法去控制這個(gè)流程。在繪制View時(shí),會(huì)在draw()過程調(diào)用該方法。因此, 再配合使用Scroller實(shí)例,我們就可以獲得當(dāng)前應(yīng)該的偏移坐標(biāo),手動(dòng)使View/ViewGroup偏移至該處。
computeScroll()方法原型如下,該方法位于ViewGroup.java類中
/** * 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)用用來請(qǐng)求子視圖根據(jù)偏移值 mScrollX,mScrollY重新繪制 */ public void computeScroll() { //空方法 ,自定義ViewGroup必須實(shí)現(xiàn)方法體 }
為了實(shí)現(xiàn)偏移控制,一般自定義View/ViewGroup都需要重載該方法 。其調(diào)用過程位于View繪制流程draw()過程中,如下:
@Override protected void dispatchDraw(Canvas canvas){ ... for (int i = 0; i < count; i++) { final View child = children[getChildDrawingOrder(count, i)]; if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) { more |= drawChild(canvas, child, drawingTime); } } } protected boolean drawChild(Canvas canvas, View child, long drawingTime) { ... child.computeScroll(); ... }
實(shí)例演示
ViewPager相信每個(gè)人都再熟悉不過了,因此它實(shí)在是太常用了,我們可以借助ViewPager來輕松完成頁面之間的滑動(dòng)切換效果,但是如果問到它是如何實(shí)現(xiàn)的話,我感覺大部分人還是比較陌生的。其實(shí)說到ViewPager最基本的實(shí)現(xiàn)原理主要就是兩部分內(nèi)容,一個(gè)是事件分發(fā),一個(gè)是Scroller。對(duì)于事件分發(fā),不了解的同學(xué)可以參考我這篇博客Android事件的分發(fā)、攔截和執(zhí)行。
接下來我將結(jié)合事件分發(fā)和Scroller來實(shí)現(xiàn)一個(gè)簡(jiǎn)易版的ViewPager。首先自定義一個(gè)ViewGroup,不了解的可以參考Android自定義ViewGroup(一)之CustomGridLayout這篇文章。平滑偏移的主要做法如下:
第一、調(diào)用Scroller實(shí)例去產(chǎn)生一個(gè)偏移控制(對(duì)應(yīng)于startScroll()方法)
第二、手動(dòng)調(diào)用invalid()方法去重新繪制,剩下的就是在computeScroll()里根據(jù)當(dāng)前已經(jīng)逝去的時(shí)間,獲取當(dāng)前應(yīng)該偏移的坐標(biāo)(由Scroller實(shí)例對(duì)應(yīng)的computeScrollOffset()計(jì)算而得)
第三、當(dāng)前應(yīng)該偏移的坐標(biāo),調(diào)用scrollBy()方法去緩慢移動(dòng)至該坐標(biāo)處。
新建一個(gè)ScrollerLayout并讓它繼承自ViewGroup來作為我們的簡(jiǎn)易ViewPager布局,代碼如下所示:
public class ScrollerLayout extends ViewGroup { private Scroller mScroller; //用于完成滾動(dòng)操作的實(shí)例 private VelocityTracker mVelocityTracker = null ; //處理觸摸的速率 public static int SNAP_VELOCITY = 600 ; //最小的滑動(dòng)速率 private int mTouchSlop = 0 ; //最小滑動(dòng)距離,超過了,才認(rèn)為開始滑動(dòng) private float mLastionMotionX = 0 ; //上次觸發(fā)ACTION_MOVE事件時(shí)的屏幕坐標(biāo) private int curScreen = 0 ; //當(dāng)前屏幕 private int leftBorder; //界面可滾動(dòng)的左邊界 private int rightBorder; //界面可滾動(dòng)的右邊界 //兩種狀態(tài): 是否處于滑屏狀態(tài) private static final int TOUCH_STATE_REST = 0; //什么都沒做的狀態(tài) private static final int TOUCH_STATE_SCROLLING = 1; //開始滑屏的狀態(tài) private int mTouchState = TOUCH_STATE_REST; //默認(rèn)是什么都沒做的狀態(tài) public ScrollerLayout(Context context, AttributeSet attrs) { super(context, attrs); // 創(chuàng)建Scroller的實(shí)例 mScroller = new Scroller(context); //初始化一個(gè)最小滑動(dòng)距離 mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int childCount = getChildCount(); for (int i = 0; i < childCount; i++) { View childView = getChildAt(i); // 為ScrollerLayout中的每一個(gè)子控件測(cè)量大小 measureChild(childView, widthMeasureSpec, heightMeasureSpec); } } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { if (changed) { int childCount = getChildCount(); for (int i = 0; i < childCount; i++) { View childView = getChildAt(i); // 為ScrollerLayout中的每一個(gè)子控件在水平方向上進(jìn)行布局 childView.layout(i * childView.getMeasuredWidth(), 0, (i + 1) * childView.getMeasuredWidth(), childView.getMeasuredHeight()); } } // 初始化左右邊界值 leftBorder = getChildAt(0).getLeft(); rightBorder = getChildAt(getChildCount() - 1).getRight(); } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { final int action = ev.getAction(); //表示已經(jīng)開始滑動(dòng)了,不需要走該Action_MOVE方法了(第一次時(shí)可能調(diào)用)。 //該方法主要用于用戶快速松開手指,又快速按下的行為。此時(shí)認(rèn)為是處于滑屏狀態(tài)的。 if ((action == MotionEvent.ACTION_MOVE) && (mTouchState != TOUCH_STATE_REST)) { return true; } final float x = ev.getX(); switch (action) { case MotionEvent.ACTION_MOVE: final int xDiff = (int) Math.abs(mLastionMotionX - x); //超過了最小滑動(dòng)距離,就可以認(rèn)為開始滑動(dòng)了 if (xDiff > mTouchSlop) { mTouchState = TOUCH_STATE_SCROLLING; } break; case MotionEvent.ACTION_DOWN: mLastionMotionX = x; mTouchState = mScroller.isFinished() ? TOUCH_STATE_REST : TOUCH_STATE_SCROLLING; break; case MotionEvent.ACTION_CANCEL: case MotionEvent.ACTION_UP: mTouchState = TOUCH_STATE_REST; break; } return mTouchState != TOUCH_STATE_REST; } public boolean onTouchEvent(MotionEvent event){ super.onTouchEvent(event); //獲得VelocityTracker對(duì)象,并且添加滑動(dòng)對(duì)象 if (mVelocityTracker == null) { mVelocityTracker = VelocityTracker.obtain(); } mVelocityTracker.addMovement(event); //觸摸點(diǎn) float x = event.getX(); switch(event.getAction()){ case MotionEvent.ACTION_DOWN: //如果屏幕的動(dòng)畫還沒結(jié)束,你就按下了,我們就結(jié)束上一次動(dòng)畫,即開始這次新ACTION_DOWN的動(dòng)畫 if(mScroller != null){ if(!mScroller.isFinished()){ mScroller.abortAnimation(); } } mLastionMotionX = x ; //記住開始落下的屏幕點(diǎn) break ; case MotionEvent.ACTION_MOVE: int detaX = (int)(mLastionMotionX - x ); //每次滑動(dòng)屏幕,屏幕應(yīng)該移動(dòng)的距離 if (getScrollX() + detaX < leftBorder) { //防止用戶拖出邊界這里還專門做了邊界保護(hù),當(dāng)拖出邊界時(shí)就調(diào)用scrollTo()方法來回到邊界位置 scrollTo(leftBorder, 0); return true; } else if (getScrollX() + getWidth() + detaX > rightBorder) { scrollTo(rightBorder - getWidth(), 0); return true; } scrollBy(detaX, 0);//開始緩慢滑屏咯。 detaX > 0 向右滑動(dòng) , detaX < 0 向左滑動(dòng) mLastionMotionX = x ; break ; case MotionEvent.ACTION_UP: final VelocityTracker velocityTracker = mVelocityTracker ; velocityTracker.computeCurrentVelocity(1000); //計(jì)算速率 int velocityX = (int) velocityTracker.getXVelocity() ; //滑動(dòng)速率達(dá)到了一個(gè)標(biāo)準(zhǔn)(快速向右滑屏,返回上一個(gè)屏幕) 馬上進(jìn)行切屏處理 if (velocityX > SNAP_VELOCITY && curScreen > 0) { // Fling enough to move left snapToScreen(curScreen - 1); } //快速向左滑屏,返回下一個(gè)屏幕 else if(velocityX < -SNAP_VELOCITY && curScreen < (getChildCount()-1)){ snapToScreen(curScreen + 1); } //以上為快速移動(dòng)的 ,強(qiáng)制切換屏幕 else{ //我們是緩慢移動(dòng)的,因此先判斷是保留在本屏幕還是到下一屏幕 snapToDestination(); } //回收VelocityTracker對(duì)象 if (mVelocityTracker != null) { mVelocityTracker.recycle(); mVelocityTracker = null; } //修正mTouchState值 mTouchState = TOUCH_STATE_REST ; break; case MotionEvent.ACTION_CANCEL: mTouchState = TOUCH_STATE_REST ; break; } return true ; } //我們是緩慢移動(dòng)的,因此需要根據(jù)偏移值判斷目標(biāo)屏是哪個(gè) private void snapToDestination(){ //判斷是否超過下一屏的中間位置,如果達(dá)到就抵達(dá)下一屏,否則保持在原屏幕 //公式意思是:假設(shè)當(dāng)前滑屏偏移值即 scrollCurX 加上每個(gè)屏幕一半的寬度,除以每個(gè)屏幕的寬度就是我們目標(biāo)屏所在位置了。 int destScreen = (getScrollX() + getWidth() / 2 ) / getWidth() ; snapToScreen(destScreen); } //真正的實(shí)現(xiàn)跳轉(zhuǎn)屏幕的方法 private void snapToScreen(int whichScreen){ //簡(jiǎn)單的移到目標(biāo)屏幕,可能是當(dāng)前屏或者下一屏幕,直接跳轉(zhuǎn)過去,不太友好,為了友好性,我們?cè)谠黾右粋€(gè)動(dòng)畫效果 curScreen = whichScreen ; //防止屏幕越界,即超過屏幕數(shù) if(curScreen > getChildCount() - 1) curScreen = getChildCount() - 1 ; //為了達(dá)到下一屏幕或者當(dāng)前屏幕,我們需要繼續(xù)滑動(dòng)的距離.根據(jù)dx值,可能向左滑動(dòng),也可能向右滑動(dòng) int dx = curScreen * getWidth() - getScrollX() ; mScroller.startScroll(getScrollX(), 0, dx, 0, Math.abs(dx) * 2); //由于觸摸事件不會(huì)重新繪制View,所以此時(shí)需要手動(dòng)刷新View 否則沒效果 invalidate(); } @Override public void computeScroll() { //重寫computeScroll()方法,并在其內(nèi)部完成平滑滾動(dòng)的邏輯 if (mScroller.computeScrollOffset()) { scrollTo(mScroller.getCurrX(), mScroller.getCurrY()); invalidate(); } } }
代碼比較長,但思路比較清晰。
(1)首先在ScrollerLayout的構(gòu)造函數(shù)里面我們創(chuàng)建Scroller的實(shí)例,由于Scroller的實(shí)例只需創(chuàng)建一次,因此我們把它放到構(gòu)造函數(shù)里面執(zhí)行。另外在構(gòu)建函數(shù)中我們還初始化的TouchSlop的值,這個(gè)值在后面將用于判斷當(dāng)前用戶的操作是否是拖動(dòng)。
(2)接著重寫onMeasure()方法和onLayout()方法,在onMeasure()方法中測(cè)量ScrollerLayout里的每一個(gè)子控件的大小,在onLayout()方法中為ScrollerLayout里的每一個(gè)子控件在水平方向上進(jìn)行布局,布局類似于方向?yàn)閔orizontal的LinearLayout。
(3) 接著重寫onInterceptTouchEvent()方法, 在這個(gè)方法中我們記錄了用戶手指按下時(shí)的X坐標(biāo)位置,以及用戶手指在屏幕上拖動(dòng)時(shí)的X坐標(biāo)位置,當(dāng)兩者之間的距離大于TouchSlop值時(shí),就認(rèn)為用戶正在拖動(dòng)布局,置狀態(tài)為TOUCH_STATE_SCROLLING,當(dāng)用戶手指抬起,重置狀態(tài)為TOUCH_STATE_REST。這里當(dāng)狀態(tài)值為TOUCH_STATE_SCROLLING時(shí)返回true,將事件在這里攔截掉,阻止事件傳遞到子控件當(dāng)中。
(4)那么當(dāng)我們把事件攔截掉之后,就會(huì)將事件交給ScrollerLayout的onTouchEvent()方法來處理。
如果當(dāng)前事件是ACTION_MOVE,說明用戶正在拖動(dòng)布局,那么我們就應(yīng)該對(duì)布局內(nèi)容進(jìn)行滾動(dòng)從而影響拖動(dòng)事件,實(shí)現(xiàn)的方式就是使用我們剛剛所學(xué)的scrollBy()方法,用戶拖動(dòng)了多少這里就scrollBy多少。另外為了防止用戶拖出邊界這里還專門做了邊界保護(hù),當(dāng)拖出邊界時(shí)就調(diào)用scrollTo()方法來回到邊界位置。
如果當(dāng)前事件是ACTION_UP時(shí),說明用戶手指抬起來了,但是目前很有可能用戶只是將布局拖動(dòng)到了中間,我們不可能讓布局就這么停留在中間的位置,因此接下來就需要借助Scroller來完成后續(xù)的滾動(dòng)操作。首先計(jì)算滾動(dòng)速率,判斷當(dāng)前動(dòng)作是scroll還是fling。如果是fling,再根據(jù)fling的方向跳轉(zhuǎn)到上一頁或者下一頁,調(diào)用函數(shù)snapToScreen。如果是scroll,就調(diào)用函數(shù)snapToDestination,函數(shù)中首先根據(jù)當(dāng)前的滾動(dòng)位置來計(jì)算布局應(yīng)該繼續(xù)滾動(dòng)到哪一頁,滾動(dòng)到哪一頁同樣調(diào)用snapToScreen。再來看看snapToScreen寫法吧,其實(shí)是調(diào)用startScroll()方法來滾動(dòng)數(shù)據(jù),緊接著調(diào)用invalidate()方法來刷新界面。
(5)重寫computeScroll()方法,并在其內(nèi)部完成平滑滾動(dòng)的邏輯 。在整個(gè)后續(xù)的平滑滾動(dòng)過程中,computeScroll()方法是會(huì)一直被調(diào)用的,因此我們需要不斷調(diào)用Scroller的computeScrollOffset()方法來進(jìn)行判斷滾動(dòng)操作是否已經(jīng)完成了,如果還沒完成的話,那就繼續(xù)調(diào)用scrollTo()方法,并把Scroller的curX和curY坐標(biāo)傳入,然后刷新界面從而完成平滑滾動(dòng)的操作。
現(xiàn)在ScrollerLayout已經(jīng)準(zhǔn)備好了,接下來我們修改activity_main.xml布局中的內(nèi)容,如下所示:
<?xml version="1.0" encoding="utf-8"?> <com.hx.scroller.ScrollerLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <ImageView android:layout_width="match_parent" android:layout_height="200dp" android:background="@drawable/crazy_1" /> <ImageView android:layout_width="match_parent" android:layout_height="200dp" android:background="@drawable/crazy_2" /> <ImageView android:layout_width="match_parent" android:layout_height="200dp" android:background="@drawable/crazy_3" /> </com.hx.scroller.ScrollerLayout>
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- 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)用開發(fā)中Scroller類的屏幕滑動(dòng)功能運(yùn)用
- android使用 ScrollerView 實(shí)現(xiàn) 可上下滾動(dòng)的分類欄實(shí)例
- 深入理解Android中Scroller的滾動(dòng)原理
- Android程序開發(fā)之UIScrollerView里有兩個(gè)tableView
- Android Scroller及下拉刷新組件原理解析
- Android Scroller大揭秘
- android開發(fā)通過Scroller實(shí)現(xiàn)過渡滑動(dòng)效果操作示例
相關(guān)文章
Android中封裝SDK時(shí)常用的注解總結(jié)
這篇文章主要給大家總結(jié)了在Android中封裝SDK時(shí)常用的注解的相關(guān)資料,文中介紹的非常詳細(xì),對(duì)大家具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起看看吧。2017-05-05Android開發(fā)實(shí)現(xiàn)繪制淘寶收益圖折線效果示例
這篇文章主要介紹了Android開發(fā)實(shí)現(xiàn)繪制淘寶收益圖折線效果,涉及Android canvas圖形繪制及布局控制相關(guān)操作技巧,需要的朋友可以參考下2017-11-11Android自定義控件實(shí)現(xiàn)九宮格解鎖功能
這篇文章主要為大家詳細(xì)介紹了Android自定義控件實(shí)現(xiàn)九宮格解鎖功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-05-05Android 獲取傳感器列表整理及簡(jiǎn)單實(shí)例
這篇文章主要介紹了Android 獲取傳感器列表整理及簡(jiǎn)單實(shí)例的相關(guān)資料,需要的朋友可以參考下2017-06-06