Android彈性滑動的三種實現方式


引言

  上一篇文章我們介紹了實現彈性滑動的三種方式,但僅僅是給出了代碼片段和方法理論。今天我們結合一個具體的例子來談一下如何使用這三種方法來實現彈性滑動。今天我們的例子是仿IOS的下拉操作,我們知道Android系統ListView之類的控件的是不存在下拉操作的,IOS系統大多數界面都可以下拉,然后緩緩恢復,今天我們的例子就是簡單的仿IOS的這種效果。

一些准備工作

  我們自定義了一個View,讓一個LinearLayout填充這個View,模擬占滿全屏的效果。XML代碼如下:

 1 <com.research.gong.android_view_research.view.PullView
 2         android:layout_width="match_parent"
 3         android:layout_height="wrap_content">
 4 
 5         <LinearLayout
 6             android:layout_width="match_parent"
 7             android:layout_height="1000dp"
 8             android:orientation="vertical"
 9             android:background="#4097e6"
10             android:id="@+id/main">
11 
12         </LinearLayout>
13 
14 </com.research.gong.android_view_research.view.PullView>

Scroller實現彈性滑動

  我們想實現彈性滑動,第一步需要實現的就是View需要能夠跟隨手指滑動,這當然讓我們想到了OnTouchEvent來檢測用戶的觸摸事件。先看核心代碼:

 1 @Override
 2     public boolean onTouchEvent(MotionEvent event) {
 3         int y=(int)event.getY();
 4         switch (event.getAction()){
 5             //手指按下時,初始化按下位置的X,Y位置值
 6             case MotionEvent.ACTION_DOWN:
 7                 mLastY=y;
 8                 break;
 9             //計算滑動的偏移量,產生滑動效果
10             case MotionEvent.ACTION_MOVE:
11                 //手指向下滑動delayY>0,向上滑動delayY<0
12                 int delayY=y-mLastY;
13                 delayY=delayY*-1;
14                 scrollBy(0,delayY);
15                 break;
16             case MotionEvent.ACTION_UP:
17                 /**
18                  * scrollY是指:View的上邊緣和View內容的上邊緣(其實就是第一個ChildView的上邊緣)的距離
19                  * scrollY=上邊緣-View內容上邊緣,scrollTo/By方法滑動的知識View的內容
20                  * 往下滑動scrollY是負值
21                  */
22                 int scrollY=getScrollY();
23                 smoothScrollByScroller(scrollY);
24                 //smoothScrollByAnim(scrollY);
25                 //smoothScrollByHandler(scrollY);
26                 break;
27         }
28         mLastY=y;
29         return true;
30     }

  在代碼中,我們看到當手指按下時,記錄按下的位置mLastY,然后我們在ACTION_MOVE事件中不斷的計算滑動的偏移量delayY然后使用scrollBy來實現View的滑動,這樣我們就可以實現View跟隨手指滑動而滑動。細心的朋友可能發現我手指向下滑動,delayY應該是正值,拿View向下滑動為什么需要將delayY*-1變成負數?這是因為Android系統是通過移動可視區域來實現改變View內容位置的,我們自覺上View向下滑動,對於可視區域來說是向上滑動,所以scrollBy需要使用負值,這樣感官上和我們向下滑動效果是一樣的,這一點需要注意。

  下面我們開始分析手指放開的那一段代碼邏輯,看代碼的22-23行,我們先獲取mScrollY的值,這個值我們在上一篇文章中已經介紹過了,是指View的上邊緣和View內容上邊緣的距離,其實就是我們手指釋放的那一刻,滑動的總的大小。我們只需要將View緩緩划過這一段距離,就可以產生彈性滑動的效果。我們看下面的代碼如何處理:

 1     /**
 2      * 執行滑動效果
 3      * 使用scroller實現
 4      * @param dy
 5      */
 6     private void smoothScrollByScroller(int dy){
 7         mScroller.startScroll(0,dy,0,dy*-1,1000);
 8         invalidate();
 9     }
10 
11     @Override
12     public void computeScroll() {
13         if (mScroller.computeScrollOffset()) {
14             scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
15             postInvalidate();
16         }
17     }

  我們從代碼中看到,我們使用了上一篇博客中的代碼范式,將需要滑動的距離dy作為參數進行傳遞,然后使用startScroll方法來實現滑動。這個方法在上一篇文章中已經介紹過。

使用動畫實現滑動

  我們上一篇文章中還介紹了使用動畫來實現彈性滑動效果,現在我們給出代碼來看一下具體的實現思路:

 1 /**
 2      * 使用動畫來實現
 3      * @param dy
 4      */
 5     private void smoothScrollByAnim(int dy){
 6         final float delayY=dy;
 7         ValueAnimator valueAnimator=ValueAnimator.ofInt(0,1).setDuration(1000);
 8         valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
 9             @Override
10             public void onAnimationUpdate(ValueAnimator animation) {
11                 //計算動畫完成的百分比
12                 float percent=animation.getAnimatedFraction();
13                 float dy=(1.0f-percent)*delayY;
14                 scrollTo(0,(int)dy);
15             }
16         });
17         valueAnimator.start();
18     }

  我們看到第一步也是記錄滑動的總的距離,然后使用動畫的addUpdateListener方法來監聽動畫的每一幀,然后根據執行動畫的百分比來計算現在需要滑動的位置,使用scrollTo方法滑動到指定的位置。dy計算出來都是負數並且越往后,越接近0,也就是可視區域逐漸往下滑動,這樣我們看起來就是View往上恢復。

使用Handler或者延時策略

  下面我們介紹最后一種方法,使用延時策略來模擬Scroller。我們將1000毫秒分成50此執行,每一次延時20ms,然后在handler中根據執行的次數來計算完成的比例,然后修改View的位置實現滑動,代碼如下:

 1 private int count;
 2     private int delayY;
 3     /**
 4      * 使用Handler來實現
 5      * @param dy
 6      */
 7     private void smoothScrollByHandler(int dy){
 8         delayY=dy;
 9         count=0;
10         scrollHandler.sendEmptyMessageDelayed(0,20);
11     }
12 
13     private Handler scrollHandler=new Handler(){
14         @Override
15         public void handleMessage(Message msg) {
16             switch (msg.what){
17                 case 0:
18                     count++;
19                     if(count<=50){
20                         float percent=count/50.0f;
21                         int scrollY=(int)(delayY*(1.0f-percent));
22                         Log.d("scrollY:",String.valueOf(scrollY));
23                         scrollTo(0,scrollY);
24                         scrollHandler.sendEmptyMessageDelayed(0,20);
25                     }
26                     break;
27                 default:
28                     break;
29             }
30         }
31     };

  我們看到代碼思路和使用動畫類似。

總結

  上面3種實現彈性滑動的方法,我們建議還是優先選擇Scroller來實現,其他兩種方法指示提供類似的思路。下面我貼出詳細的代碼,供各位朋友實驗學習。詳細代碼如下:

  1 package com.research.gong.android_view_research.view;
  2 
  3 import android.animation.ValueAnimator;
  4 import android.content.Context;
  5 import android.os.Handler;
  6 import android.os.Message;
  7 import android.util.AttributeSet;
  8 import android.util.Log;
  9 import android.view.LayoutInflater;
 10 import android.view.MotionEvent;
 11 import android.view.View;
 12 import android.view.ViewGroup;
 13 import android.widget.Scroller;
 14 
 15 /**
 16  * 模擬下拉組件
 17  */
 18 public final class PullView extends ViewGroup {
 19 
 20     private int mLastY;
 21     private Context mContext;
 22     private Scroller mScroller;
 23     //子View的個數
 24     private int mChildCount;
 25 
 26     public PullView(Context context){
 27         this(context,null);
 28     }
 29 
 30     public PullView(Context context, AttributeSet attributeSet){
 31         super(context,attributeSet);
 32         mContext=context;
 33         initView();
 34     }
 35 
 36     private void initView(){
 37         mScroller=new Scroller(mContext);
 38     }
 39 
 40     @Override
 41     public boolean onTouchEvent(MotionEvent event) {
 42         int y=(int)event.getY();
 43         switch (event.getAction()){
 44             //手指按下時,初始化按下位置的X,Y位置值
 45             case MotionEvent.ACTION_DOWN:
 46                 mLastY=y;
 47                 break;
 48             //計算滑動的偏移量,產生滑動效果
 49             case MotionEvent.ACTION_MOVE:
 50                 //手指向下滑動delayY>0,向上滑動delayY<0
 51                 int delayY=y-mLastY;
 52                 delayY=delayY*-1;
 53                 scrollBy(0,delayY);
 54                 break;
 55             case MotionEvent.ACTION_UP:
 56                 /**
 57                  * scrollY是指:View的上邊緣和View內容的上邊緣(其實就是第一個ChildView的上邊緣)的距離
 58                  * scrollY=上邊緣-View內容上邊緣,scrollTo/By方法滑動的知識View的內容
 59                  * 往下滑動scrollY是負值
 60                  */
 61                 int scrollY=getScrollY();
 62                 //smoothScrollByScroller(scrollY);
 63                 //smoothScrollByAnim(scrollY);
 64                 smoothScrollByHandler(scrollY);
 65                 break;
 66         }
 67         mLastY=y;
 68         return true;
 69     }
 70 
 71     /**
 72      * 執行滑動效果
 73      * 使用scroller實現
 74      * @param dy
 75      */
 76     private void smoothScrollByScroller(int dy){
 77         mScroller.startScroll(0,dy,0,dy*-1,1000);
 78         invalidate();
 79     }
 80 
 81     @Override
 82     public void computeScroll() {
 83         if (mScroller.computeScrollOffset()) {
 84             scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
 85             postInvalidate();
 86         }
 87     }
 88 
 89     /**
 90      * 使用動畫來實現
 91      * @param dy
 92      */
 93     private void smoothScrollByAnim(int dy){
 94         final float delayY=dy;
 95         ValueAnimator valueAnimator=ValueAnimator.ofInt(0,1).setDuration(1000);
 96         valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
 97             @Override
 98             public void onAnimationUpdate(ValueAnimator animation) {
 99                 //計算動畫完成的百分比
100                 float percent=animation.getAnimatedFraction();
101                 float dy=(1.0f-percent)*delayY;
102                 scrollTo(0,(int)dy);
103             }
104         });
105         valueAnimator.start();
106     }
107 
108     private int count;
109     private int delayY;
110     /**
111      * 使用Handler來實現
112      * @param dy
113      */
114     private void smoothScrollByHandler(int dy){
115         delayY=dy;
116         count=0;
117         scrollHandler.sendEmptyMessageDelayed(0,20);
118     }
119 
120     private Handler scrollHandler=new Handler(){
121         @Override
122         public void handleMessage(Message msg) {
123             switch (msg.what){
124                 case 0:
125                     count++;
126                     if(count<=50){
127                         float percent=count/50.0f;
128                         int scrollY=(int)(delayY*(1.0f-percent));
129                         Log.d("scrollY:",String.valueOf(scrollY));
130                         scrollTo(0,scrollY);
131                         scrollHandler.sendEmptyMessageDelayed(0,20);
132                     }
133                     break;
134                 default:
135                     break;
136             }
137         }
138     };
139 
140     /**
141      * 重新計算子View的高度和寬度
142      * @param widthMeasureSpec
143      * @param heightMeasureSpec
144      */
145     @Override
146     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
147         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
148         int measuredWidth;
149         int measureHeight;
150         mChildCount = getChildCount();
151         //測量子View
152         measureChildren(widthMeasureSpec, heightMeasureSpec);
153         int widthSpaceSize = MeasureSpec.getSize(widthMeasureSpec);
154         int widthSpaceMode = MeasureSpec.getMode(widthMeasureSpec);
155         int heightSpaceSize = MeasureSpec.getSize(heightMeasureSpec);
156         int heightSpaceMode = MeasureSpec.getMode(heightMeasureSpec);
157 
158         //獲取橫向的padding值
159         int paddingLeft=getPaddingLeft();
160         int paddingRight=getPaddingRight();
161         final View childView = getChildAt(0);
162         /**
163          * 如果子View的數量是0,就讀取LayoutParams中數據
164          * 否則就對子View進行測量
165          * 此處主要是針對wrap_content這種模式進行處理,因為默認情況下
166          * wrap_content等於match_parent
167          */
168         if (mChildCount == 0) {
169             ViewGroup.LayoutParams layoutParams=getLayoutParams();
170             if(layoutParams!=null){
171                 setMeasuredDimension(layoutParams.width,layoutParams.height);
172             }else {
173                 setMeasuredDimension(0, 0);
174             }
175         } else if (heightSpaceMode == MeasureSpec.AT_MOST && widthSpaceMode == MeasureSpec.AT_MOST) {
176             measuredWidth = childView.getMeasuredWidth() * mChildCount;
177             measureHeight = getChildMaxHeight();
178             //將兩側的padding值加上去
179             measuredWidth=paddingLeft+measuredWidth+paddingRight;
180             setMeasuredDimension(measuredWidth, measureHeight);
181         } else if (heightSpaceMode == MeasureSpec.AT_MOST) {
182             measureHeight = getChildMaxHeight();
183             setMeasuredDimension(widthSpaceSize, measureHeight);
184         } else if (widthSpaceMode == MeasureSpec.AT_MOST) {
185             measuredWidth = childView.getMeasuredWidth() * mChildCount;
186             measuredWidth=paddingLeft+measuredWidth+paddingRight;
187             setMeasuredDimension(measuredWidth, heightSpaceSize);
188         }
189     }
190 
191 
192     /**
193      * 獲取子View中最大高度
194      * @return
195      */
196     private int getChildMaxHeight(){
197         int maxHeight=0;
198         for (int i = 0; i < mChildCount; i++) {
199             View childView = getChildAt(i);
200             if (childView.getVisibility() != View.GONE) {
201                 int height = childView.getMeasuredHeight();
202                 if(height>maxHeight){
203                     maxHeight=height;
204                 }
205             }
206         }
207         return maxHeight;
208     }
209 
210 
211     /**
212      * 設置子View的布局
213      * @param changed
214      * @param l
215      * @param t
216      * @param r
217      * @param b
218      */
219     @Override
220     protected void onLayout(boolean changed, int l, int t, int r, int b) {
221         int childLeft = 0;
222         for (int i = 0; i < mChildCount; i++) {
223             View childView = getChildAt(i);
224             if (childView.getVisibility() != View.GONE) {
225                 int childWidth = childView.getMeasuredWidth();
226                 childView.layout(childLeft, 0, childLeft + childWidth, childView.getMeasuredHeight());
227                 childLeft += childWidth;
228             }
229         }
230     }
231 }

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM