标签: androidScrollVeiw
2016-07-12 19:52 6748人阅读 收藏 举报
想要的效果
最近项目中想实现一个效果,效果如下:
网上demo展示
就是上滑或者下滑,能实现弹性效果,刚开始在网上找了好几个demo,代码大致如下:
public class BounceScrollView extends ScrollView { private View inner; private float y; private Rect normal = new Rect(); private boolean isCount = false; public BounceScrollView(Context context, AttributeSet attrs) { super(context, attrs); } /*** * 根据 XML 生成视图工作完成.该函数在生成视图的最后调用,在所有子视图添加完之后. 即使子类覆盖了 onFinishInflate * 方法,也应该调用父类的方法,使该方法得以执行. */ @Override protected void onFinishInflate() { if (getChildCount() > 0) { inner = getChildAt(0); } } /*** * 监听touch */ @Override public boolean onTouchEvent(MotionEvent ev) { if (inner != null) { commOnTouchEvent(ev); } return super.onTouchEvent(ev); } /*** * 触摸事件 * * @param ev */ public void commOnTouchEvent(MotionEvent ev) { int action = ev.getAction(); switch (action) { case MotionEvent.ACTION_DOWN: break; case MotionEvent.ACTION_UP: if (isNeedAnimation()) { animation(); isCount = false; } break; case MotionEvent.ACTION_MOVE: final float preY = y; float nowY = ev.getY(); int deltaY = (int) (preY - nowY); if (!isCount) { deltaY = 0; } y = nowY; if (isNeedMove()) { if (normal.isEmpty()) { normal.set(inner.getLeft(), inner.getTop(), inner.getRight(), inner.getBottom()); } inner.layout(inner.getLeft(), inner.getTop() - deltaY / 2, inner.getRight(), inner.getBottom() - deltaY / 2); } isCount = true; break; default: break; } } /*** * 回缩动画 */ public void animation() { TranslateAnimation ta = new TranslateAnimation(0, 0, inner.getTop(), normal.top); ta.setDuration(200); inner.startAnimation(ta); inner.layout(normal.left, normal.top, normal.right, normal.bottom); normal.setEmpty(); } public boolean isNeedAnimation() { return !normal.isEmpty(); } /*** * 是否需要移动布局 inner.getMeasuredHeight():获取的是控件的总高度 * * getHeight():获取的是屏幕的高度 * * @return */ public boolean isNeedMove() { int offset = inner.getMeasuredHeight() - getHeight(); int scrollY = getScrollY(); if (scrollY == 0 || scrollY == offset) { return true; } return false; }}
- 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
- 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
它的实现原理:
- 通过getChildAt(0)方法获取到scrollView的唯一一个子view—inner, 并用矩形Rect–>normal记录子view的初始位置(left,top,right,bottom);
- 在onTouchEvent()方法的ACTION_MOVE中,根据子view inner滑动的距离,用view.setLayout(left,top,right,bottom)对子view inner进行滑动;
- 在onTouchEvent()方法的ACTION_UP中,对子view –> inner设置帧动画,让其回缩到原始位置。
出现的问题:
- scrollView中的子view,必须是溢出全屏的(比如说子view中含有较长的listview之类),才能实现弹性效果,就是设置scrollView或者它的子View的layout_height为match_content也没用。这种效果就看起来很尴尬,在安全中心,退出程序之类的按钮上滑动就没反应,在下面灰色部分上下滑动就有弹性效果。这是个bug,得治!
解决原理:
- 根据事件分发机制,重写子view的dispatchTouchEvent(),onInterceptTouchEvent(),onTouchEvent();
- 根据事件分发机制,重写父类->scrollView的dispatchTouchEvent(),onInterceptTouchEvent(),onTouchEvent()
关于android的事件分发机制,在文章http://blog.csdn.net/a394268045/article/details/51889797有详细介绍
在自定义的scrollView和它的子View的三个方法中,分别加上log,会发现,
当在上图中画红圈部分上下滑动时,log如下:
BounceScrollView dispatchTouchEvent...ACTION_DOWNBounceScrollView onInterceptTouchEvent...ACTION_DOWNBounceScrollView onTouchEvent...ACTION_DOWNBounceScrollView dispatchTouchEvent...ACTION_MOVEBounceScrollView onTouchEvent...ACTION_MOVEBounceScrollView dispatchTouchEvent...ACTION_MOVEBounceScrollView onTouchEvent...ACTION_MOVEBounceScrollView dispatchTouchEvent...ACTION_MOVEBounceScrollView onTouchEvent...ACTION_MOVEBounceScrollView dispatchTouchEvent...ACTION_UPBounceScrollView onTouchEvent...ACTION_UP
当在图中类似安全中心等按钮上滑动时,log如下:
BounceScrollView dispatchTouchEvent...ACTION_DOWNBounceScrollView onInterceptTouchEvent...ACTION_DOWN子View dispatchTouchEvent...ACTION_DOWN子View onInterceptTouchEvent...ACTION_DOWN子View onTouchEvent...ACTION_DOWNBounceScrollView dispatchTouchEvent...ACTION_MOVEBounceScrollView onInterceptTouchEvent...ACTION_MOVE子View dispatchTouchEvent...ACTION_MOVE子View onTouchEvent...ACTION_MOVEBounceScrollView dispatchTouchEvent...ACTION_MOVEBounceScrollView onInterceptTouchEvent...ACTION_MOVE子View dispatchTouchEvent...ACTION_MOVE子View onTouchEvent...ACTION_MOVEBounceScrollView dispatchTouchEvent...ACTION_MOVEBounceScrollView onInterceptTouchEvent...ACTION_MOVE子View dispatchTouchEvent...ACTION_MOVE子View onTouchEvent...ACTION_MOVEBounceScrollView dispatchTouchEvent...ACTION_UPBounceScrollView onInterceptTouchEvent...ACTION_UP子View dispatchTouchEvent...ACTION_UP子View onTouchEvent...ACTION_UP
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
最终解决方案
如果在子view中处理,那么这个父View BounceScrollView就没有通用性,而且,当子view类型不同时,还得处理各种想不到的问题。所以,在父view中处理弹性效果是最好的。但是,当我在子view上下滑动时,BounceScrollView居然没有调用onTouchEvent()!而不管是在画红圈区域上下滑动,还是在按钮上滑动,都会调用父View的dispatchTouchEvent()方法。OK,那我们就可以将弹性效果放到dispatchTouchEvent()中,并加上上下滑动的判断,最终代码如下:
public class BounceScrollView extends ScrollView { private static final String TAG = "BounceScrollView"; private View inner; private float y; private Rect normal = new Rect(); private boolean isCount = false; private float lastX = 0; private float lastY = 0; private float currentX = 0; private float currentY = 0; private float distanceX = 0; private float distanceY = 0; private boolean upDownSlide = false; public BounceScrollView(Context context, AttributeSet attrs) { super(context, attrs); } /*** * 根据 XML 生成视图工作完成.该函数在生成视图的最后调用,在所有子视图添加完之后. 即使子类覆盖了 onFinishInflate * 方法,也应该调用父类的方法,使该方法得以执行. */ @Override protected void onFinishInflate() { if (getChildCount() > 0) { inner = getChildAt(0); View view; } } @Override public boolean dispatchTouchEvent(MotionEvent ev) { currentX = ev.getX(); currentY = ev.getY(); switch(ev.getAction()){ case MotionEvent.ACTION_DOWN: break; case MotionEvent.ACTION_MOVE: distanceX = currentX - lastX; distanceY = currentY - lastY; if(Math.abs(distanceX)<Math.abs(distanceY) && Math.abs(distanceY)>12){ upDownSlide = true; } break; case MotionEvent.ACTION_UP: break; default: break; } lastX = currentX; lastY = currentY; if (upDownSlide && inner != null) commOnTouchEvent(ev); return super.dispatchTouchEvent(ev); } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { return super.onInterceptTouchEvent(ev); } /*** * 监听touch */ @Override public boolean onTouchEvent(MotionEvent ev) { return super.onTouchEvent(ev); } /*** * 触摸事件 * * @param ev */ public void commOnTouchEvent(MotionEvent ev) { int action = ev.getAction(); switch (action) { case MotionEvent.ACTION_DOWN: break; case MotionEvent.ACTION_UP: if (isNeedAnimation()) { animation(); isCount = false; } clear0(); break; */ case MotionEvent.ACTION_MOVE: final float preY = y; float nowY = ev.getY(); int deltaY = (int) (preY - nowY); if (!isCount) { deltaY = 0; } y = nowY; if (isNeedMove()) { if (normal.isEmpty()) { normal.set(inner.getLeft(), inner.getTop(), inner.getRight(), inner.getBottom()); } inner.layout(inner.getLeft(), inner.getTop() - deltaY / 2, inner.getRight(), inner.getBottom() - deltaY / 2); } isCount = true; break; default: break; } } /*** * 回缩动画 */ public void animation() { TranslateAnimation ta = new TranslateAnimation(0, 0, inner.getTop(), normal.top); ta.setDuration(200); inner.startAnimation(ta); inner.layout(normal.left, normal.top, normal.right, normal.bottom); normal.setEmpty(); } public boolean isNeedAnimation() { return !normal.isEmpty(); } /*** * 是否需要移动布局 inner.getMeasuredHeight():获取的是控件的总高度 * * getHeight():获取的是屏幕的高度 * * @return */ public boolean isNeedMove() { int offset = inner.getMeasuredHeight() - getHeight(); int scrollY = getScrollY(); if (scrollY == 0 || scrollY == offset) { return true; } return false; } private void clear0(){ lastX = 0; lastY = 0; distanceX = 0; distanceY = 0; upDownSlide = false; }
- 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
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 145
- 146
- 147
- 148
- 149
- 150
- 151
- 152
- 153
- 154
- 155
- 156
- 157
- 158
- 159
- 160
- 161
- 162
- 163
- 164
- 165
- 166
- 167
- 168
- 169
- 170
- 171
- 172
- 173
- 174
- 175
- 176
- 177
- 178
- 179
- 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
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 145
- 146
- 147
- 148
- 149
- 150
- 151
- 152
- 153
- 154
- 155
- 156
- 157
- 158
- 159
- 160
- 161
- 162
- 163
- 164
- 165
- 166
- 167
- 168
- 169
- 170
- 171
- 172
- 173
- 174
- 175
- 176
- 177
- 178
- 179
这种处理方式,毫不影响子View的onClick事件,并能完美实现弹性效果,经过反复测试,现已放入项目中~