Android弹性ScrollView

开袋即食

  1 import android.content.Context;
  2 import android.graphics.Rect;
  3 import android.util.AttributeSet;
  4 import android.view.MotionEvent;
  5 import android.view.View;
  6 import android.view.animation.TranslateAnimation;
  7 import android.widget.ScrollView;
  8 
  9 /**
 10  * 有弹性的ScrollView
 11  * 实现下拉弹回和上拉弹回
 12  *
 13  * @author zhangjg
 14  * @date Feb 13, 2014 6:11:33 PM
 15  */
 16 public class ReboundScrollView extends ScrollView {
 17 
 18     private static final String TAG = "ElasticScrollView";
 19 
 20     //移动因子, 是一个百分比, 比如手指移动了100px, 那么View就只移动50px
 21     //目的是达到一个延迟的效果
 22     private static final float MOVE_FACTOR = 0.5f;
 23 
 24     //松开手指后, 界面回到正常位置需要的动画时间
 25     private static final int ANIM_TIME = 300;
 26 
 27     //ScrollView的子View, 也是ScrollView的唯一一个子View
 28     private View contentView;
 29 
 30     //手指按下时的Y值, 用于在移动时计算移动距离
 31     //如果按下时不能上拉和下拉, 会在手指移动时更新为当前手指的Y值
 32     private float startY;
 33 
 34     //用于记录正常的布局位置
 35     private Rect originalRect = new Rect();
 36 
 37     //手指按下时记录是否可以继续下拉
 38     private boolean canPullDown = false;
 39 
 40     //手指按下时记录是否可以继续上拉
 41     private boolean canPullUp = false;
 42 
 43     //在手指滑动的过程中记录是否移动了布局
 44     private boolean isMoved = false;
 45 
 46     public ReboundScrollView(Context context) {
 47         super(context);
 48     }
 49 
 50     public ReboundScrollView(Context context, AttributeSet attrs) {
 51         super(context, attrs);
 52     }
 53 
 54     @Override
 55     protected void onFinishInflate() {
 56         if (getChildCount() > 0) {
 57             contentView = getChildAt(0);
 58         }
 59     }
 60 
 61     @Override
 62     protected void onLayout(boolean changed, int l, int t, int r, int b) {
 63         super.onLayout(changed, l, t, r, b);
 64 
 65         if (contentView == null) return;
 66 
 67         //ScrollView中的唯一子控件的位置信息, 这个位置信息在整个控件的生命周期中保持不变
 68         originalRect.set(contentView.getLeft(), contentView.getTop(), contentView
 69                 .getRight(), contentView.getBottom());
 70     }
 71 
 72     /**
 73      * 在触摸事件中, 处理上拉和下拉的逻辑
 74      */
 75     @Override
 76     public boolean dispatchTouchEvent(MotionEvent ev) {
 77 
 78         if (contentView == null) {
 79             return super.dispatchTouchEvent(ev);
 80         }
 81 
 82         int action = ev.getAction();
 83 
 84         switch (action) {
 85             case MotionEvent.ACTION_DOWN:
 86 
 87                 //判断是否可以上拉和下拉
 88                 canPullDown = isCanPullDown();
 89                 canPullUp = isCanPullUp();
 90 
 91                 //记录按下时的Y值
 92                 startY = ev.getY();
 93                 break;
 94 
 95             case MotionEvent.ACTION_UP:
 96 
 97                 if (!isMoved) break;  //如果没有移动布局, 则跳过执行
 98 
 99                 // 开启动画
100                 TranslateAnimation anim = new TranslateAnimation(0, 0, contentView.getTop(),
101                         originalRect.top);
102                 anim.setDuration(ANIM_TIME);
103 
104                 contentView.startAnimation(anim);
105 
106                 // 设置回到正常的布局位置
107                 contentView.layout(originalRect.left, originalRect.top,
108                         originalRect.right, originalRect.bottom);
109 
110                 //将标志位设回false
111                 canPullDown = false;
112                 canPullUp = false;
113                 isMoved = false;
114 
115                 break;
116             case MotionEvent.ACTION_MOVE:
117 
118                 //在移动的过程中, 既没有滚动到可以上拉的程度, 也没有滚动到可以下拉的程度
119                 if (!canPullDown && !canPullUp) {
120                     startY = ev.getY();
121                     canPullDown = isCanPullDown();
122                     canPullUp = isCanPullUp();
123 
124                     break;
125                 }
126 
127                 //计算手指移动的距离
128                 float nowY = ev.getY();
129                 int deltaY = (int) (nowY - startY);
130 
131                 //是否应该移动布局
132                 boolean shouldMove =
133                         (canPullDown && deltaY > 0)    //可以下拉, 并且手指向下移动
134                                 || (canPullUp && deltaY < 0)    //可以上拉, 并且手指向上移动
135                                 || (canPullUp && canPullDown); //既可以上拉也可以下拉(这种情况出现在ScrollView包裹的控件比ScrollView还小)
136 
137                 if (shouldMove) {
138                     //计算偏移量
139                     int offset = (int) (deltaY * MOVE_FACTOR);
140 
141                     //随着手指的移动而移动布局
142                     contentView.layout(originalRect.left, originalRect.top + offset,
143                             originalRect.right, originalRect.bottom + offset);
144 
145                     isMoved = true;  //记录移动了布局
146                 }
147 
148                 break;
149             default:
150                 break;
151         }
152 
153         return super.dispatchTouchEvent(ev);
154     }
155 
156 
157     /**
158      * 判断是否滚动到顶部
159      */
160     private boolean isCanPullDown() {
161         return getScrollY() == 0 ||
162                 contentView.getHeight() < getHeight() + getScrollY();
163     }
164 
165     /**
166      * 判断是否滚动到底部
167      */
168     private boolean isCanPullUp() {
169         return contentView.getHeight() <= getHeight() + getScrollY();
170     }
171 
172 }
原文地址:https://www.cnblogs.com/lavalike/p/5320115.html