国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 系統(tǒng) > Android > 正文

Android Scroller大揭秘

2019-10-23 18:31:05
字體:
供稿:網(wǎng)友

在學習使用Scroller之前,需要明白scrollTo()、scrollBy()方法。

一、View的scrollTo()、scrollBy()

scrollTo、scrollBy方法是View中的,因此任何的View都可以通過這兩種方法進行移動。首先要明白的是,scrollTo、scrollBy滑動的是View中的內(nèi)容(而且還是整體滑動),而不是View本身。我們的滑動控件如SrollView可以限定寬、高大小,以及在布局中的位置,但是滑動控件中的內(nèi)容(或者里面的childView)可以是無限長、寬的,我們調(diào)用View的scrollTo、scrollBy方法,相當于是移動滑動控件中的畫布Canvas,然后進行重繪,屏幕上也就顯示相應的內(nèi)容。如下:

android,scroller

1、getScrollX()、getScrollY()

在學習scrollTo()、scrollBy()之前,先來了解一下getScrollX()、getScrollY()方法。

getScrollX()、getScrollY()得到的是偏移量,是相對自己初始位置的滑動偏移距離,只有當有scroll事件發(fā)生時,這兩個方法才能有值,否則getScrollX()、getScrollY()都是初始時的值0,而不管你這個滑動控件在哪里。所謂自己初始位置是指,控件在剛開始顯示時、沒有滑動前的位置。以getScrollX()為例,其源碼如下:

 public final int getScrollX() { return mScrollX;}

可以看到getScrollX()直接返回的就是mScrollX,代表水平方向上的偏移量,getScrollY()也類似。偏移量mScrollX的正、負代表著,滑動控件中的內(nèi)容相對于初始位置在水平方向上偏移情況,mScrollX為正代表著當前內(nèi)容相對于初始位置向左偏移了mScrollX的距離,mScrollX為負表示當前內(nèi)容相對于初始位置向右偏移了mScrollX的距離。

這里的坐標系和我們平常的認知正好相反。為了以后更方便的處理滑動相關坐標和偏移,在處理偏移、滑動相關的功能時,我們就可以把坐標反過來看,如下圖:

android,scroller

因為滑動控件中的內(nèi)容是整體進行滑動的,同時也是相對于自己顯示時的初始位置的偏移,對于View中內(nèi)容在偏移時的參考坐標原點(注意是內(nèi)容視圖的坐標原點,不是圖中說的滑動控件的原點),可以選擇初始位置的某一個地方,因為滑動時整體行為,在進行滑動的時候從這個選擇的原點出進行分析即可。

2、scrollTo()、scrollBy()

scrollTo(int x,int y)移動的是View中的內(nèi)容,而滑動控件中的內(nèi)容都是整體移動的,scrollTo(int x,int y)中的參數(shù)表示View中的內(nèi)容要相對于內(nèi)容初始位置移動x和y的距離,即將內(nèi)容移動到距離內(nèi)容初始位置x和y的位置。正如前面所說,在處理偏移、滑動問題時坐標系和平常認知的坐標系是相反的。以一個例子說明scrollTo():

說明:圖中黃色矩形區(qū)域表示的是一個可滑動的View控件,綠色虛線矩形為滑動控件中的滑動內(nèi)容。注意這里的坐標是相反的。(例子來源于:http://blog.csdn.net/bigconvience/article/details/26697645

(1)調(diào)用scrollTo(100,0)表示將View中的內(nèi)容移動到距離內(nèi)容初始顯示位置的x=100,y=0的地方,效果如下圖:

android,scroller

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

android,scroller

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

android,scroller

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

android,scroller

通過上面幾個圖,可以清楚看到scrollTo的作用和滑動坐標系的關系。在實際使用中,我們一般是在onTouchEvent()方法中處理滑動事件,在MotionEvent.ACTION_MOVE時調(diào)用scrollTo(int x,int y)進行滑動,在調(diào)用scrollTo(int x,int y)前,我們先要計算出兩個參數(shù)值,即水平和垂直方向需要滑動的距離,如下:

@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;//本次手勢滑動了多大距離  int oldScrollY = getScrollY();//先計算之前已經(jīng)偏移了多少距離  int scrollY = oldScrollY + dy;//本次需要偏移的距離=之前已經(jīng)偏移的距離+本次手勢滑動了多大距離  if(scrollY < 0){  scrollY = 0;  }  if(scrollY > getHeight() - mScreenHeight){  scrollY = getHeight() - mScreenHeight;  }  scrollTo(getScrollX(),scrollY);  mLastY = y;  break;  }  return true; } 

上面在計算參數(shù)時,分為了三步。第一是,通過int dy = mLastY - y;得到本次手勢在屏幕上滑動了多少距離,這里要特別注意這個相減順序,因為這里的坐標與平常是相反的,因此,手勢滑動距離是按下時的坐標mLastY - 當前的坐標y;第二是,通過oldScrollY = getScrollY();獲得滑動內(nèi)容之前已經(jīng)距初始位置便宜了多少;第三是,計算本次需要偏移的參數(shù)int scrollY = oldScrollY + dy; 后面通過兩個if條件進行了邊界處理,然后調(diào)用scrollTo進行滑動。調(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是相對于初始位置來進行移動的,而scrollBy(int x ,int y)則是相對于上一次移動的距離來進行本次移動。scrollBy其實還是依賴于scrollTo的,如下源碼:

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

可以看到,使用scrollBy其實就是省略了我們在計算scrollTo參數(shù)時的第三步而已,因為scrollBy內(nèi)部已經(jīng)自己幫我加上了第三步的計算。因此scrollBy的作用就是相當于在上一次的偏移情況下進行本次的偏移。

一個完整的水平方向滑動的例子:

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();//原來的偏移量  int preScrollX = oldScrollX + dx;//本次滑動后形成的偏移量  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> 

效果如圖:

android,scroller

二、Scroller滑動輔助類

根據(jù)我們上面的分析,可知View的scrollTo()、scrollBy()是瞬間完成的,當我們的手指在屏幕上移動時,內(nèi)容會跟著手指滑動,但是當我們手指一抬起時,滑動就會停止,如果我們想要有一種慣性的滾動過程效果和回彈效果,此時就需要使用Scroller輔助類。

但是注意的是,Scroller本身不會去移動View,它只是一個移動計算輔助類,用于跟蹤控件滑動的軌跡,只相當于一個滾動軌跡記錄工具,最終還是通過View的scrollTo、scrollBy方法完成View的移動的。

在使用Scroller類之前,先了解其重要的兩個方法:

(1)startScroll()

public void startScroll(int startX, int startY, int dx, int dy, int duration)

開始一個動畫控制,由(startX , startY)在duration時間內(nèi)前進(dx,dy)個單位,即到達偏移坐標為(startX+dx , startY+dy)處。

(2)computeScrollOffset()

public boolean computeScrollOffset()

滑動過程中,根據(jù)當前已經(jīng)消逝的時間計算當前偏移的坐標點,保存在mCurrX和mCurrY值中。

上面兩個方法的源碼如下:

public class Scroller {private int mStartX;//水平方向,滑動時的起點偏移坐標private int mStartY;//垂直方向,滑動時的起點偏移坐標private int mFinalX;//滑動完成后的偏移坐標,水平方向private int mFinalY;//滑動完成后的偏移坐標,垂直方向private int mCurrX;//滑動過程中,根據(jù)消耗的時間計算出的當前的滑動偏移距離,水平方向private int mCurrY;//滑動過程中,根據(jù)消耗的時間計算出的當前的滑動偏移距離,垂直方向private int mDuration; //本次滑動的動畫時間private float mDeltaX;//滑動過程中,在達到mFinalX前還需要滑動的距離,水平方向private float mDeltaY;//滑動過程中,在達到mFinalX前還需要滑動的距離,垂直方向public void startScroll(int startX, int startY, int dx, int dy) { startScroll(startX, startY, dx, dy, DEFAULT_DURATION);}/** * 開始一個動畫控制,由(startX , startY)在duration時間內(nèi)前進(dx,dy)個單位,即到達偏移坐標為(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;//確定本次滑動完成后的偏移坐標 mFinalY = startY + dy; mDeltaX = dx; mDeltaY = dy; mDurationReciprocal = 1.0f / (float) mDuration;}/** * 滑動過程中,根據(jù)當前已經(jīng)消逝的時間計算當前偏移的坐標點,保存在mCurrX和mCurrY值中 * @return*/public boolean computeScrollOffset() { if (mFinished) {//已經(jīng)完成了本次動畫控制,直接返回為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);//計算出當前的滑動偏移位置,x軸 mCurrY = mStartY + Math.round(x * mDeltaY);//計算出當前的滑動偏移位置,y軸 break; ... } }else { mCurrX = mFinalX; mCurrY = mFinalY; mFinished = true; } return true; } ...}

Scroller類中最重要的兩個方法就是startScroll()和computeScrollOffset(),但是Scroller類只是一個滑動計算輔助類,它的startScroll()和computeScrollOffset()方法中也只是對一些軌跡參數(shù)進行設置和計算,真正需要進行滑動還是得通過View的scrollTo()、scrollBy()方法。為此,View中提供了computeScroll()方法來控制這個滑動流程。computeScroll()方法會在繪制子視圖的時候進行調(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)用用來請求子視圖根據(jù)偏移值 mScrollX,mScrollY重新繪制  */ public void computeScroll() { //空方法 ,自定義滑動功能的ViewGroup必須實現(xiàn)方法體  } 

因此Scroller類的基本使用流程可以總結(jié)如下:

(1)首先通過Scroller類的startScroll()開始一個滑動動畫控制,里面進行了一些軌跡參數(shù)的設置和計算;

(2)在調(diào)用startScroll()的后面調(diào)用invalidate();引起視圖的重繪操作,從而觸發(fā)ViewGroup中的computeScroll()被調(diào)用;

(3)在computeScroll()方法中,先調(diào)用Scroller類中的computeScrollOffset()方法,里面根據(jù)當前消耗時間進行軌跡坐標的計算,然后取得計算出的當前滑動的偏移坐標,調(diào)用View的scrollTo()方法進行滑動控制,最后也需要調(diào)用invalidate();進行重繪。

如下的一個簡單代碼示例:  

@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();//原來的偏移量  int preScrollX = oldScrollX + dx;//本次滑動后形成的偏移量  if(preScrollX > (getChildCount() - 1) * getWidth()){   preScrollX = (getChildCount() - 1) * getWidth();  }  if(preScrollX < 0){   preScrollX = 0;  }  //開始滑動動畫  mScroller.startScroll(mScroller.getFinalX(),mScroller.getFinalY(),dx,0);//第一步  //注意,一定要進行invalidate刷新界面,觸發(fā)computeScroll()方法,因為單純的startScroll()是屬于Scroller的,只是一個輔助類,并不會觸發(fā)界面的繪制  invalidate();  mLastX = x;  break;  }  return true;  }  @Override  public void computeScroll() {  super.computeScroll();  if(mScroller.computeScrollOffset()){//第二步  scrollTo(mScroller.getCurrX(),mScroller.getCurrY());//第三步  invalidate();  }  } 

下面是一個完整的例子:一個類似ViewPager的Demo,效果圖如下:

android,scroller

代碼如下:

public class MyViewPager3 extends ViewGroup {  private int mLastX;  private Scroller mScroller;  private VelocityTracker mVelocityTracker;  private int mTouchSlop;  private int mMaxVelocity;  /**  * 當前顯示的是第幾個屏幕  */  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()來進行滑動的  int oldScrollX = getScrollX();//原來的偏移量  int preScrollX = oldScrollX + dx;//本次滑動后形成的偏移量  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后面一定要進行invalidate刷新界面,觸發(fā)computeScroll()方法,因為單純的startScroll()是屬于Scroller的,只是一個輔助類,并不會觸發(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){//如果是快速的向右滑,則需要顯示上一個屏幕   Log.d("TAG","----------------快速的向右滑--------------------");   scrollToPage(mCurrentPage - 1);  }else if(initVelocity < -mMaxVelocity && mCurrentPage < (getChildCount() - 1)){//如果是快速向左滑動,則需要顯示下一個屏幕   Log.d("TAG","----------------快速的向左滑--------------------");   scrollToPage(mCurrentPage + 1);  }else{//不是快速滑動的情況,此時需要計算是滑動到   Log.d("TAG","----------------慢慢的滑動--------------------");   slowScrollToPage();  }  recycleVelocityTracker();  break;  }  return true;  }  /**  * 緩慢滑動抬起手指的情形,需要判斷是停留在本Page還是往前、往后滑動  */  private void slowScrollToPage() {  //當前的偏移位置  int scrollX = getScrollX();  int scrollY = getScrollY();  //判斷是停留在本Page還是往前一個page滑動或者是往后一個page滑動  int whichPage = (getScrollX() + getWidth() / 2 ) / getWidth() ;  scrollToPage(whichPage);  }  /**  * 滑動到指定屏幕  * @param indexPage  */  private void scrollToPage(int indexPage) {  mCurrentPage = indexPage;  if(mCurrentPage > getChildCount() - 1){  mCurrentPage = getChildCount() - 1;  }  //計算滑動到指定Page還需要滑動的距離  int dx = mCurrentPage * getWidth() - getScrollX();  mScroller.startScroll(getScrollX(),0,dx,0,Math.abs(dx) * 2);//動畫時間設置為Math.abs(dx) * 2 ms  //記住,使用Scroller類需要手動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é):

(1)Scroller類能夠幫助我們實現(xiàn)高級的滑動功能,如手指抬起后的慣性滑動功能。使用流程為,首先通過Scroller類的startScroll()+invalidate()觸發(fā)View的computeScroll(),在computeScroll()中讓Scroller類去計算最新的坐標信息,拿到最新的坐標偏移信息后還是要調(diào)用View的scrollTo來實現(xiàn)滑動。可以看到,使用Scroller的整個流程比較簡單,關鍵的是控制滑動的一些邏輯計算,比如上面例子中的計算什么時候該往哪一頁滑動...

(2)Android后面推出了OverScroller類,OverScroller在整體功能上和Scroller類似,使用也相同。OverScroller類可以完全代替Scroller,相比Scroller,OverScroller主要是增加了對滑動到邊界的一些控制,如增加一些回彈效果等,功能更加強大。

以上就是本文的全部內(nèi)容,希望本文的內(nèi)容對大家的學習或者工作能帶來一定的幫助,同時也希望多多支持VEVB武林網(wǎng)!


注:相關教程知識閱讀請移步到Android開發(fā)頻道。
發(fā)表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發(fā)表
主站蜘蛛池模板: 兴隆县| 堆龙德庆县| 建水县| 大城县| 镇赉县| 家居| 合水县| 高密市| 星子县| 建昌县| 江川县| 万载县| 英吉沙县| 凤凰县| 响水县| 营山县| 会理县| 临城县| 疏勒县| 鄂伦春自治旗| 岱山县| 兴和县| 沙湾县| 辽阳县| 科尔| 昌宁县| 桐庐县| 乌苏市| 仁布县| 洪泽县| 崇阳县| 涿鹿县| 淮安市| 泌阳县| 汝州市| 德阳市| 祁阳县| 平和县| 元朗区| 团风县| 赣榆县|