滚动回弹效果分析: 首先,创建一个类,继承scrollview,重写ontouch事件,实现伸缩回弹效果。 scroollview节点下只能有一个子节点,这个子节点就是我们要移动的view布局。 第一步:获取要操作的子view布局 第二步:重写onTouch事件监听 分析具体事件: 观察分析得出结论: 让布局移动每一次拉动的Y轴一半的距离,然后松手滚动[携带动画]回到原来的位置。 下拉或者上拉的时候,记录按下时的Y轴位置 action_down
移动过程中的处理: 计算上一次与本次的Y轴(拉动距离)[而不是按下时候的Y值,和现在移动到的Y值,是每上一次和本次的Y值比较 判断是否需要移动布局的情况:Y轴的一个距离偏移 //2种情况,随着布局的拖动, inner.getMeasuredHeight()的值是变化的 //inner.getMeasuredHeight()与getHeight()的区别: 当屏幕可以包裹内容的时候,他们的值相等 当view的高度超出屏幕时,getMeasuredHeight()是实际View的大小,与屏幕无关,getHeight的大小此时则是屏幕的大小。
此时,getMeasuredHeight() = getHeight+超出部分。 抬起的处理:布局回滚到正常位置 移动动画回滚到正常位置(*:动画执行期间,不允许拖拉操作) 距离:-的滚动距离 ?
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 | public class MyScrollview extends ScrollView { //要操作的布局 private View innerView; private float y; private Rect normal = new Rect(); private boolean animationFinish = true ; public MyScrollview(Context context) { super(context, null ); } public MyScrollview(Context context, AttributeSet attrs) { super(context, attrs); }
public MyScrollview(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @Override protected void onFinishInflate() { int childCount = getChildCount(); if (childCount > 0 ) { innerView = getChildAt(0); } } @Override public boolean onTouchEvent(MotionEvent ev) { if (innerView == null ) { return super.onTouchEvent(ev); } else { commonTouchEvent(ev); } return super.onTouchEvent(ev); } /** * 自定义touch事件处理 * * @param ev */ private void commonTouchEvent(MotionEvent ev) { if (animationFinish) { int action = ev.getAction(); switch (action) { case MotionEvent.ACTION_DOWN: y = ev.getY(); break; case MotionEvent.ACTION_MOVE: float preY = y == 0 ? ev.getY() : y; float nowY = ev.getY(); int detailY = ( int) (preY - nowY); y = nowY; //操作view进行拖动detailY的一半 if (isNeedMove()) { //布局改变位置之前,记录一下正常状态的位置 if (normal.isEmpty()) { normal.set(innerView.getLeft(), innerView.getTop(), innerView.getRight(), innerView.getBottom()); } innerView.layout(innerView.getLeft(), innerView.getTop() - detailY / 2, innerView.getRight(), innerView.getBottom() - detailY / 2); } break; case MotionEvent.ACTION_UP: y = 0; //布局回滚到原来的位置 if (isNeedAnimation()) { animation(); } break; } } }
private void animation() { TranslateAnimation ta = new TranslateAnimation( 0, 0, 0, normal.top - innerView.getTop()); ta.setDuration(200); ta.setAnimationListener(new Animation.AnimationListener() { @Override public void onAnimationStart(Animation animation) { animationFinish = false; } @Override public void onAnimationEnd(Animation animation) { innerView.clearAnimation(); innerView.layout(normal.left, normal.top, normal.right, normal.bottom); normal.setEmpty(); animationFinish = true ; } @Override public void onAnimationRepeat(Animation animation) { } }); innerView.startAnimation(ta); } /** * 判断是否需要回滚 * * @return */ private boolean isNeedAnimation() { return !normal.isEmpty(); } /** * 判断是否需要移动 * * @return */ private boolean isNeedMove() { int offset = innerView.getMeasuredHeight() - getHeight(); int scrollY = getScrollY(); Log.e("zoubo", "getMeasuredHeight:" + innerView.getMeasuredHeight() + "----getHeight:" + getHeight()); Log.e("zoubo", "offset:" + offset + "----scrollY:" + scrollY); if (scrollY == 0 || scrollY == offset) { return true; } return false; } } |