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 }

 

posted @ 2017-01-24 10:11  dreamGong  阅读(5533)  评论(0编辑  收藏  举报