(学习参考书:Android群英传)
一、滑动效果的产生
滑动一个View本质上就是一移动一个View,改变当前其当前所处的位置。因要实现View的滑动,就必须监听用户的触摸事件,并根据事件传入的坐标,动态且不断的改变View的坐标,从而实现View跟随用户触摸的滑动而滑动。Android中的窗口坐标体系和触控事件MotionEvent
二、Android坐标系
在Android中,将屏幕的左上角的顶点作为Android坐标系的原点,从该点向左为X轴正方向,向下为Y轴正方向。系统提供了getLocationOnScreen(intlocation[])这样的方法来获取Android坐标系中点的位置,即该视图左上角在Android坐标系中的坐标,另外在触控事件中使用getRawX()、getRawY()方法所获得的坐标同样是Android坐标系中的坐标。
三、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; 多点离开动作
通常情况下,会在onTouchEvent(MotionEvent event) 方法中通过event.getAction() 方法来获取触控事件的类型,并使用switch-case方法来进行筛选。该代码模式基本固定:
@Override
public boolean onTouchEvent(MotionEvent event) {
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;
}
三、常用系统API
系统提供了很多方法来获取坐标值和相对距离,可以分为两个类别: View提供的获取坐标方法:
getTop() 获取到的是View自身的顶边到父布局边界的距离
getLeft() 获取到的是View自身的左边到父布局边界的距离
getRight() 获取到的是View自身的右边到父布局边界的距离
getBottom() 获取到的是View自身的底边到父布局边界的距离
MotionEvent提供的方法:
getX() 获取点击事件距离控件左边的距离,即视图坐标
getY() 获取点击事件距离控件顶边边的距离,即视图坐标
getRawX() 获取点击事件距离控件左边的距离,即绝对坐标
getRawY() 获取点击事件距离控件顶边的距离,即绝对坐标
四、实现滑动的其中方法
实现滑动效果即修改View的坐标,其基本思想是当触摸View时,系统记下当前触摸点的坐标;当手指移动时,系统记下移动后的触摸点坐标,从而获取到相对于前一次坐标点的偏移量,并通过偏移量来修改View的坐标,这样不断重复,从而实现滑动过程。
(1)layout方法
View进行绘制时,会调用onLayout()方法来设置显示的位置。同样也可以通过修改View的left、top、right、bottom四个属性来控制View的坐标。与前面提供的代码模板一样,每次回调onTouchEvent的时候,都获取一下触摸点的坐标。 除了使用视图坐标,还可以使用绝对坐标来计算偏移量。
@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;
layout(getLeft()+ offsetX,getTop()+offsetY,getRight()+offsetX,getBottom()+offsetY);
break;
case MotionEvent.ACTION_UP:
break;
}
return true;
}
(2)offsetLeftAndRight( )与offsetTopAndBottom( )
这个方法相当于系统提供的一个对左右、上下移动的API的封装。当计算出偏移量后,只需要使用如下代码就可以完成View的重新布局,效果与layout方法一样:
offsetLeftAndRight(offsetX);
offsetTopAndBottom(offsetY);
(3)LayoutParams
LayoutParams保存了一个View的布局参数。因此可以在程序中,通过改变LayoutParams来动态修改一个布局的位置参数,从而达到改变View位置的效果。可以在程序中很方便的使用getLayoutParams()来获取一个View的LayoutParams。获取到偏移量后,就可以通过setLayoutParams来改变其LayoutParams:
LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) getLayoutParams();
layoutParams.leftMargin = getLeft()+offsetX;
layoutParams.topMargin = getTop()+offsetY;
setLayoutParams(layoutParams);
(4)scrollTo和scrollBy
scrollTo(x,y)表示移动到某个具体的坐标点,scrollBy(x,y)表示移动的增量。但是这两个方法移动的是View的内容,如文本、图片;如果在ViewGroup中使用这两个方法,移动的将是其所有子View。
scrollBy(offsetX,offsetY);
((View) getParent()).scrollBy(-offsetX,-offsetY);
(5)Scroll类
Scroller类与scrollTo和scrollBy方法非常相似。但是Scroller类的移动实现了平滑移动的动画,而不是上述两种方法的瞬间移动。
- 创建一个Scroller对象实例,传入参数Context
- 重写computeScroll()方法,实现模拟滑动;Scroller提供了computeScrollOffset判断是否完成滑动,也提供了getCurrX()getCurrY()获取当前坐标。模板如下
@Override
public void computeScroll() {
super.computeScroll();
if(scroller.computeScrollOffset()){
((View) getParent()).scrollTo(scroller.getCurrX(),scroller.getCurrY());
invalidate();
}
}
- startScroll开启滑动过程:
View viewGroup = (View) getParent();
scroller.startScroll(viewGroup.getScrollX(),viewGroup.getScrollY(),-viewGroup.getScrollX(),-viewGroup.getScrollY());
invalidate();
(6)ViewDragHelper
ViewDragHelper基本可以实现各种不同的滑动、拖放需求、因此这个方法也是各种滑动解决方法中的终极绝招。
- 初始化ViewDragHelper:通常定义在一个ViewGroup的内部,并使用其静态方法进行初始化:ViewDragHelper.create(this,callback);第一个参数是要监听的View,通常是需要一个ViewGroup;第二个参数是一个Callback回调,这个回调就是整个ViewDragHelper的逻辑核心。
- 拦截事件:重写事件拦截方法,将事件传递给ViewDragHelper进行处理。在onTouchEvent()方法中调用ViewDragHelper的processTouchEvent (event)方法
- 处理computeScroll():因为ViewDragHelper内部也是通过Scroll来实现平滑移动的,因此也需要重写computeScroll()方法,模板如下:
public void computeScroll() {
if(helper.continueSettling(true)){
ViewCompat.postInvalidateOnAnimation(this);
}
}
- 处理回调Callback:最关键的Callback实现,通过下面的代码来创建一个ViewDragHelper.Callback。并通过tryCaptureView()方法指定在创建ViewDragHelper时,参数parentView中的哪一个子View可以被移动
private ViewDragHelper.Callback callback = new ViewDragHelper.Callback() {
@Override
public boolean tryCaptureView(@NonNull View child, int pointerId) {
return targetVeiw==child;
}
};
- 如果需要实现基本滑动效果,必须要在回调接口中重写clampViewPositionHorizontal和clampViewPositionVertical方法。如垂直方法中的top代表垂直方向上child移动的距离,dy表示比较前一次的增量,水平方法中参数同理。
@Override
public int clampViewPositionHorizontal(@NonNull View child, int left, int dx) {
return left;
}
@Override
public int clampViewPositionVertical(@NonNull View child, int top, int dy) {
return top;
}
除了此对ViewDragHelper简单的应用外,ViewDragHelper.Callback中,系统定义了大量的监听事件来帮助处理各种事件:
onViewCaptured() 该事件在用户触摸到View后回调
onViewDragStateChanged() 该事件在拖拽状态改变时回调
onViewPositionChanged() 该事件在位置改变时回调
|