Android Scroll分析
概述
相对于Android2.x版本中常见的长按、点击操作,滑动的方式具有更友好的用户体验性。因此从4.x的版本开始,滑动操作大量出现在Android系统中。
我们在这里主要阐述两个问题
- 发生滑动的效果的原因
- 如何处理、实现滑动效果
滑动效果分析
滑动一个View,本质上就是移动一个View。
改变其当前所处的位置,它的原理和动画效果的实现非常相似,都是通过不断的改变View的坐标来实现这一个效果。
所以要实现View的滑动,必须要监听用户的触摸事件,并根据事件传入的坐标,动态且不断的改变View的坐标,从而实现View跟随用户触摸的滑动而滑动。
在此之前,我们需要先了解下Android中的窗口坐标体系和屏幕的触控事件MotionEvent。
Android坐标系
所谓滑动,正是相对于参考系的运动。
在Android中,将屏幕最左上角的顶点作为Android坐标系的原点,从这个点向右是X轴的正方向,从这个点向下是Y轴的正方向。
系统提供了getLocationOnScreen(int location[])这样的方法来获取Android坐标系中点的位置,即该视图左上角在Android坐标系中的坐标。
另外在触控事件中使用 getRawX(),getRawY()方法所获得的坐标同样是Android坐标系中的坐标。
视图坐标系
Android还有一个视图坐标系,它描述的是子视图在父视图中的位置关系。
和上面的Android坐标系相辅相成。
和Android坐标系类似,视图坐标系同样是以原点方向向右为X轴正方向,以原点向下为Y轴正方向,只是这个原点不再是Android坐标系中屏幕的左上角,而是父视图左上角为坐标原点。
在触摸事件中,通过getX()和getY()所获得的坐标就是视图坐标系中的坐标。
触控事件-MotionEvent
触控事件MotionEvent在用户交互中,占据着举足轻重的位置。
首先我们来看下MotionEvent中封装的一些常用的事件变量,它定义了触控事件的不同的类型。
// 单点触摸按下动作
public static final int ACTION_DOWN = 0 ;
// 单点触摸离开动作
public static final int ACTION_UP = 1 ;
// 触摸点移动动作
public static final int ACTION_MOVE = 2 ;
// 触摸动作取消
public static final int ACTION_CANCEL = 3 ;
// 触摸动作超出边界
public static final int ACTION_OUTSIDE = 4 ;
// 多点触摸按下动作
public static final int ACTION_POINTER_DOWN = 5 ;
// 多点离开动作
public static final int ACTION_POINTER_UP = 6 ;
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
通常情况下,我们会在onTouchEvent(MotionEvnet event)方法中通过event.getAction()方法来获取触控事件的类型,并使用switch-case方法来进行筛选,这个代码的模式基本固定,如下
@Override
public boolean onTouchEvent(MotionEvent envnt){
// 获取当前输入点的X、Y坐标(视图坐标)
int x = (int)event.getX();
int y = (int)event.getY();
switch(event.getAction()){
case MotionEvent.ACTION_DOWN:
//处理输入的按下事件
break;
case MotionEvent.ACTION_MOVE:
// 处理输入的移动事件
break;
case MotionEvent.ACTION_UP:
// 处理输入的离开事件
break;
}
return true ;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
在不涉及多点操作的情况下,通常可以使用以上代码来完成触控事件的监听,上述仅仅是一个代码模板~
在Android中提供了很多获取坐标值,相对举例的方法,我们来梳理一下。
View 提供的获取坐标的方法
getTop():获取到的是View自身的顶边到其父布局顶边的距离
getLeft():获取到的是View自身的左边到其父布局左边的距离
getRight():获取到的是View自身的右边到其父布局左边的距离
getBottom():获取到的是View自身的底边到其父布局顶边的距离
MotionEvent 提供的方法
getX():获取点击事件距离控件左边的距离,即视图坐标
getY():获取点击事件距离控件顶边的距离,即视图坐标
getRawX():获取点击事件距离整个屏幕左边的距离,即绝对坐标
getRawY():获取点击事件距离整个屏幕顶边的距离,即绝对坐标
实现滑动的七种方法
不管使用何种方法,其实现的基本思路是一致的:当触摸View时,系统记下当前触摸点坐标,当手指移动时,系统记下移动后的触摸点坐标,从而获取到相对于前一次坐标点的偏移量,并通过偏移量来修改View的坐标,这样不断地重复,从而实现滑动的过程。
下面我们通过例子来看看Android是如何实现滑动效果的。
首先我们自定义一个View,置于布局文件中,实现一个简单的布局。
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.turing.base.android_hero.chapter5_Scroll.DragView
android:layout_width="100dp"
android:layout_height="100dp"/>
</RelativeLayout>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
layout方法
概述
在View绘制时,会调用onLayout()方法来设置显示的位置。
同样,可以通过修改View的 left top right bottom四个属性来控制View的坐标。
在每次回调onTouchEvent方法的时候,我们都来获取一下触摸点的坐标。
Code
自定义DragView,重写onTouchEvent方法
package com.turing.base.android_hero.chapter5_Scroll;
import android.content.Context;
import android.graphics.Color;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
/**
* MyApp
*
* @author Mr.Yang on 2016-04-09 21:11.
* @version 1.0
* 自定义View
*/
public class DragView extends View {
// 定义上次触摸的位置
private int lastX;
private int lastY;
/**
* 构造函数中调用 initViewColor方法
*
* @param context
*/
public DragView(Context context) {
super(context);
initViewBackGroundColor();
}
public DragView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initViewBackGroundColor();
}
public DragView(Context context, AttributeSet attrs) {
super(context, attrs);
initViewBackGroundColor();
}
public void initViewBackGroundColor() {
// 给View设置背景颜色,便于观察
setBackgroundColor(Color.BLUE);
}
/**
* 重写 onTouchEvent方法
*
* @param event
* @return
*/
@Override
public boolean onTouchEvent(MotionEvent event) {
int x = (int) event.getX();
int y = (int) event.getY();
int rawx = (int) event.getRawX();
int rawy = (int) event.getRawY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// 按下的时候 记录触摸点坐标 scrollByXY
// lastX = x;
// lastY = y ;
lastX = rawx;
lastY = rawy;
break;
case MotionEvent.ACTION_MOVE:
// scrollByXY(x,y);
scrollyByRawXY(rawx, rawy);
break;
}
return true;
}
/**
* 使用Android坐标系 绝对坐标来计算偏移量,并移动View
* 使用绝对坐标系,在每次执行完ACTION_MOVE的逻辑后一定要重新设置初始坐标,
* 这样才能准确的获取到偏移量
* @param rawx
* @param rawy
*/
public void scrollyByRawXY(int rawx, int rawy) {
// 计算偏移量
int offsetX = rawx - lastX;
int offsetY = rawy - lastY;
// 增加偏移量
layout(getLeft() + offsetX,
getTop() + offsetY,
getRight() + offsetX,
getBottom() + offsetY);
// 重新设置初坐标
lastX = rawx;
lastY = rawy;
}
/**
* 使用视图坐标系 计算偏移量,并移动View
*/
public void scrollByXY(int x, int y) {
// 计算偏移量
int offsetX = x - lastX;
int offsetY = y - lastY;
// 在当前left right top bottom的基础上增加偏移量
layout(getLeft() + offsetX,
getTop() + offsetY,
getRight() + offsetX,
getBottom() + offsetY);
}
}
- 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
Scroll_Layout
package com.turing.base.android_hero.chapter5_Scroll;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import com.turing.base.R;
public class Scroll_Layout extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_scroll__layout);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
效果图
offsetLeftAndRight 和 offsetTopAndBottom
这个方法相当于系统提供了一个对左右和上下移动的API的封装。
当计算出偏移量之后,只需要使用如下代码完成View的重新布局,效果和使用layout方法一样
// 同时对left和right进行偏移
offsetLeftAndRigth(offsetX);
// 同时对top和bottom进行偏移
offsetTopAndBottom(offsetY);
- 1
- 2
- 3
- 4
效果同layout 就不贴代码和运行效果图了。
LayoutParams
概述
LayoutParams保存了一个View的布局参数。因此可以通过改变LayoutParms来动态的修改一个 布局的位置参数,从而达到改变View位置的效果。
我们可以通过getLayoutParams()来获取一个View的LayoutParams. 当然了计算偏移量和Layout方法中计算offset也是一样的,当获取到偏移量之后,就可以通过setLayoutParams来改变LayoutParams.
代码如下:
// 获取偏移量
int offsetX = x - lastX;
int offsetY = y - lastY;
RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) getLayoutParams();
layoutParams.leftMargin = getLeft() + offsetX;
layoutParams.topMargin = getTop() + offsetY;
setLayoutParams(layoutParams);
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
注意事项:
- 通过getLayoutParams获取LayoutParams时,需要根据View在父布局中的类型来设置不同的类型,比如这里我们把View放到了RelativeLayout中,那么就是RelativeLayout.LayoutParams ,同样的道理 如果放到了LinearLayout中,则为LinearLayout.LayoutParams
- 通过getLayoutParams的方式获取布局参数,前提是必须要有一个父布局,否则系统无法获取。
Code
关键自定义类
package com.turing.base.android_hero.chapter5_Scroll;
import android.content.Context;
import android.graphics.Color;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.RelativeLayout;
/**
* MyApp
*
* @author Mr.Yang on 2016-04-10 10:23.
* @version 1.0
* @desc
*/
public class DragView_LayoutParams extends View {
private int lastX, lastY;
public DragView_LayoutParams(Context context) {
super(context);
initViewBackgroudCoclor();
}
public DragView_LayoutParams(Context context, AttributeSet attrs) {
super(context, attrs);
initViewBackgroudCoclor();
}
public DragView_LayoutParams(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initViewBackgroudCoclor();
}
private void initViewBackgroudCoclor() {
// 给View设置背景颜色,便于观察
setBackgroundColor(Color.BLUE);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// 记录当前的触摸坐标
lastX = x;
lastY = y;
break;
case MotionEvent.ACTION_MOVE:
// 获取偏移量
int offsetX = x - lastX;
int offsetY = y - lastY;
RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) getLayoutParams();
layoutParams.leftMargin = getLeft() + offsetX;
layoutParams.topMargin = getTop() + offsetY;
setLayoutParams(layoutParams);
break;
default:
break;
}
return true;
}
}
- 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
效果图
效果图同上~
方法二-ViewGroup.MarginLayoutParams
在通过LayoutParams来改变一个变量的位置的时候,通常改变的是这个View的margin属性,所以除了使用布局的LayoutParams之外,我们还可以使用ViewGroup.MarginLaoutParams来实现同样的功能。
// 计算偏移量
int offsetX = x - lastX;
int offsetY = y - lastY;
ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams) getLayoutParams();
layoutParams.leftMargin = getLeft() + offsetX;
layoutParams.topMargin = getTop() + offsetY;
setLayoutParams(layoutParams);
break;
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
使用ViewGroup.MarginLayoutParams更加方便,不需要考虑父布局的类型,当然了这两者的本质都是一样的。
scrollTo和scrollBy
概述
在一个View中,系统提供了scrollTo 、scrollBy两种方式来改变一个View的位置。
顾名思义,
- scrollTo(x,y)表示移动到一个具体的坐标点 (x,y).
- scrollBy(dx,dy)表示移动的增量为dx,dy.
需要注意的是:
- scrollTo和scrollBy方法移动的是View的content,即让View中的内容移动,如果在ViewGroup中使用scrollTo和scrollBy方法,那么移动的将是所有的子View,但如果在View中使用,那么移动的将是View的内容,比如TextView,content就是它的文本,ImageView,content就是它的Drawable对象。
- 要实现跟随手指移动而滑动的效果,必须将偏移量设置为负值。
- 如果将scrollBy中的参数dx和dy设置为正数,那么content将向坐标的负方向移动,设置为负数,content将向坐标轴的正方向移动。
- 在使用绝对坐标系时,也可以通过scrollTo来实现相同的效果
Code
关键自定义View
package com.turing.base.android_hero.chapter5_Scroll;
import android.content.Context;
import android.graphics.Color;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
/**
* MyApp
*
* @author Mr.Yang on 2016-04-10 12:32.
* @version 1.0
* @desc
*/
public class DragView_scrollToscrollBy extends View {
private int lastX;
private int lastY;
public DragView_scrollToscrollBy(Context context) {
super(context);
initViewBackgroundColor();
}
public DragView_scrollToscrollBy(Context context, AttributeSet attrs) {
super(context, attrs);
initViewBackgroundColor();
}
public DragView_scrollToscrollBy(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initViewBackgroundColor();
}
private void initViewBackgroundColor() {
setBackgroundColor(Color.BLUE);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
lastX = x;
lastY = y;
break;
case MotionEvent.ACTION_MOVE:
int offsetX = x - lastX;
int offsetY = y - lastY;
((View) getParent()).scrollBy(-offsetX,-offsetY);
break;
}
return true;
}
}
- 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
效果图同上~
Scroller
概述
上面说到了scrollTo、scrollby方法,就不得不提一下Scroller类。
总体来讲,scrollTo scrollBy方法,子View的移动都是瞬间的,在事件执行的时候平移已经完成了,而Scroller类可以实现平滑移动的效果,而不是在瞬间完成的移动。
演示:
子View随着手指的滑动而滑动,在手指离开屏幕时,让子View平滑的移动到初始位置,即屏幕的左上角。
使用Scroller一般需要三个步骤:
- 初始化Scroller
- 重写computerScrol方法,实现模拟滑动
- startScroll开启模拟过程
Code
package com.turing.base.android_hero.chapter5_Scroll;
import android.content.Context;
import android.graphics.Color;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Scroller;
/**
* MyApp
*
* @author Mr.Yang on 2016-04-10 13:15.
* @version 1.0
* @desc
*/
public class DragViewScroller extends View {
private int lastX;
private int lastY;
private Scroller mScroller;
public DragViewScroller(Context context) {
super(context);
ininView(context);
}
public DragViewScroller(Context context, AttributeSet attrs) {
super(context, attrs);
ininView(context);
}
public DragViewScroller(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
ininView(context);
}
private void ininView(Context context) {
setBackgroundColor(Color.BLUE);
// 初始化Scroller
mScroller = new Scroller(context);
}
@Override
public void computeScroll() {
super.computeScroll();
// 判断Scroller是否执行完毕
if (mScroller.computeScrollOffset()) {
((View) getParent()).scrollTo(
mScroller.getCurrX(),
mScroller.getCurrY());
// 通过重绘来不断调用computeScroll
invalidate();
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
lastX = (int) event.getX();
lastY = (int) event.getY();
break;
case MotionEvent.ACTION_MOVE:
int offsetX = x - lastX;
int offsetY = y - lastY;
((View) getParent()).scrollBy(-offsetX, -offsetY);
break;
case MotionEvent.ACTION_UP:
// 手指离开时,执行滑动过程
View viewGroup = ((View) getParent());
mScroller.startScroll(
viewGroup.getScrollX(),
viewGroup.getScrollY(),
-viewGroup.getScrollX(),
-viewGroup.getScrollY());
invalidate();
break;
}
return true;
}
}
- 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
效果图
属性动画
待出一篇博客详述~
ViewDragHelper
概述
步骤:
- 初始化ViewDragHelper
- 拦截事件
- 处理computerScroll
- 处理回调Callback
Code
效果图
文章来源: artisan.blog.csdn.net,作者:小小工匠,版权归原作者所有,如需转载,请联系作者。
原文链接:artisan.blog.csdn.net/article/details/51078281
- 点赞
- 收藏
- 关注作者
评论(0)