View的滑动--让View动起来
前言
View是Android中所有UI的基础,在Android开发中,我们不可避免地要面临和View的交互问题。这里我们主要介绍View的滑动操作。从Android 4.X开始,滑动操作就大量出现在Android中,本篇文章就介绍如何在应用中添加滑动效果。滑动View的思路是:当触摸事件传递到View的时候,记下起始坐标,当滑动View时,记下滑动后的坐标,通过计算出偏移量,设置到View上。
实现View滑动有很多种方法,这篇文章主要讲解六种滑动的方法,分别是:layout()、offsetLeftAndRight()与offsetTopAndBottom()、LayoutParams、动画、scollTo与scollBy和Scroller;
Android中的坐标体系
在介绍View滑动之前,请先看前篇关于Android坐标体系的介绍 http://www.jianshu.com/p/6c0539cf0416
触控事件--MotionEvent
MotionEvent在触控事件中占有很重要的位置,首先来看一下MotionEvent里面定义的常量,全部是和滑动触摸有关的常量。
public static final int ACTION_BUTTON_PRESS = 11;
public static final int ACTION_BUTTON_RELEASE = 12;
public static final int ACTION_CANCEL = 3;
public static final int ACTION_DOWN = 0;
public static final int ACTION_HOVER_ENTER = 9;
public static final int ACTION_HOVER_EXIT = 10;
public static final int ACTION_HOVER_MOVE = 7;
public static final int ACTION_MASK = 255;
public static final int ACTION_MOVE = 2;
public static final int ACTION_OUTSIDE = 4;
public static final int ACTION_UP = 1;
......
通常情况下,我们处理触摸事件的逻辑是,重写onTouchEvent()方法,在此方法里通过对触摸的手势事件进行判断,做出相应的处理。一般的代码如下:
@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;
}
layout()方法
我们知道,在自定义ViewGroup时,会调用onLayout()方法来确定每个子View的位置,每个子View布局的确定是调用子View的layout()方法,来确定子View的位置。
public class DragView extends View {
//记录上次触摸的坐标
private int lastX;
private int lastY;
public DragView(Context context) {
super(context);
}
public DragView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public DragView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@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;
}
}
offsetLeftAndRight()与offsetTopAndBottom()
和layout()方法差不多,只不过这两个方法分别设置左右和上下偏移量的。
只需要修改MotionEvent.ACTION_MOVE里的代码
case MotionEvent.ACTION_MOVE: //处理滑动事件
int offsetX = x - lastX;
int offsetY = y - lastY;
offsetLeftAndRight(offsetX);
offsetTopAndBottom(offsetY);
break;
效果是一样的。
LayoutParams
LayoutParams封装了Layout的位置,宽,高等信息,通过设置LayoutParams来改变View的参数,达到改变View的位置的目的。
修改MotionEvent.ACTION_MOVE代码如下:
case MotionEvent.ACTION_MOVE: //处理滑动事件
int offsetX = x - lastX;
int offsetY = y - lastY;
LinearLayout.LayoutParams layoutParams= (LinearLayout.LayoutParams) getLayoutParams();
layoutParams.leftMargin=getLeft()+offsetX;
layoutParams.topMargin=getTop()+offsetY;
setLayoutParams(layoutParams);
break;
在这里,因为自定义DragView的父布局是LinearLayout,所以这里用的是LinearLayout.LayoutParams,如果布局变成RelativeLayout,则就要通过RelativeLayout.LayoutParams来获取layoutParams。
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.jackson.scrollview.activity.DragActivity">
<com.jackson.scrollview.view.DragView
android:layout_width="100dp"
android:layout_height="100dp"
android:background="@color/colorAccent"/>
</LinearLayout>
时期还可以通过ViewGroup.MarginLayoutParams来实现。
ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams) getLayoutParams();
layoutParams.leftMargin = getLeft() + offsetX;
layoutParams.topMargin = getTop() + offsetY;
setLayoutParams(layoutParams);
scollTo与scollBy
View内部提供了scollTo与scollBy来改变View的位置,两者的区别也类似于To和By的区别,scollTo(x,y)表示将View移动到具体的坐标(x,y),scollBy(dx,dy)表示移动的增量是dx,dy。需要注意的有两点:
- scollTo与scollBy移动的是View的Cotent,既View里面的内容,所以我们要想使用正确,必须要到View的父布局ViewGroup中使用。
- 必须将偏移量设为负值,才能正确地滑动
既滑动的代码如下:
((View)getParent()).scrollBy(-offsetX,-offsetY);
修改MotionEvent.ACTION_MOVE代码如下:
case MotionEvent.ACTION_MOVE: //处理滑动事件
int offsetX = x - lastX;
int offsetY = y - lastY;
((View) getParent()).scrollBy(-offsetX, -offsetY);
break;
Scroller
使用scollTo/scollBy方法来进行滑动时,这个过程是瞬间完成的,用户的体验不好,因此我们使用Scroller实现平滑的过渡效果。Scroller是Android中专门处理滑动效果的工具类。
总体说来,使用scollTo/scollBy实现的效果是移动,而使用Scroller实现的效果是滚动。
Scroller的使用主要分为以下几个步骤:
- 创建并初始化Scroller,通过Scroller的构造方法来创建Scroller实例。
mScroller=new Scroller(getContext());
- 重写computeScroll()方法,并在其内部完成平滑滚动的逻辑
@Override
public void computeScroll() {
super.computeScroll();
//判断Scroller是否执行完毕
if (mScroller.computeScrollOffset()){
((View)getParent()).scrollTo(mScroller.getCurrX(),mScroller.getCurrY());
//通过重绘不断调用computeScroll()
invalidate();
}
}
- 调用startScroll()方法来初始化滚动数据并刷新界面
public void smoothScrollTo(int x,int y) {
int dx = x - mScroller.getFinalX();
int dy = y - mScroller.getFinalY();
mScroller.startScroll(mScroller.getFinalX(),mScroller.getFinalY(),dx,dy,3000);
invalidate();
}