View的滑动是Android实现自定义控件的基础,同时在开发中我们也难免会遇到View的滑动处理。其实不管是哪种滑动方式,其基本思想都是类似的:当点击事件传递到View时,系统记下触摸点的坐标,手指移动式系统记下移动后的触摸的坐标并算出偏移量,并通过偏移量来修改View的坐标。
实现View滑动有很多种方法,在这里主要介绍6种滑动方法:layout()、offsetLeftAndRight与offsetTopAndBottom、LayoutParams、动画、scrollTo与scrollBy、以及Scroller。
layout()方法
View进行绘制的时候会调用onLayout方法来设置显示的位置,因此我们同样也可以通过修改View的left、top、right、bottom这4种属性来控制View的坐标。首先我们要自定义一个View,在onTouchEvent()方法中获取触摸点的坐标,代码如下:
java
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;
接下来我们在ACTION_DOWN事件中计算偏移量,在调用layout()方法重新放置这个自定义位置即可。
java
case MotionEvent.ACTION_MOVE:
int offsetX = x - lastX;
int offsetY = y - lastY;
layout(getLeft() + offsetX,getTop() + offsetY,
getRight() + offsetX,getBottom() + offsetY);
break;
在每次移动式都会调用layout()方法对View进行重新布局,从而达到移动View的效果。自定义SlideView的全部代码如下所示:
java
public class SlideView extends View {
public SlideView(Context context) {
super(context);
}
public SlideView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public SlideView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
private int lastX,lastY;
@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;
}
return true;
}
}
offsetLeftAndRight与offsetTopAndBottom
这两种方法和layout()方法的效果差不多,其使用方式也差不多。我们将ACTION_MOVE中的代码替换成如下代码:
java
case MotionEvent.ACTION_MOVE:
int offsetX = x - lastX;
int offsetY = y - lastY;
offsetLeftAndRight(offsetX);
offsetTopAndBottom(offsetY);
break;
LayoutParams
LayoutParams主要保存了一个View的布局参数,因此我们可以通过LayoutParams来改变View的布局参数从而达到改变View位置的效果。同样我们将ACTION_MOVE中的代码替换成如下代码:
java
ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams) getLayoutParams();
layoutParams.leftMargin = getLeft() + offsetX;
layoutParams.topMargin = getTop() + offsetY;
setLayoutParams(layoutParams);
注意这里使用的是 ViewGroup.MarginLayoutParams,需要父控件的LayoutParams继承了该类。
动画
可以采用View动画来移动,在res目录下新建anim文件夹并创建translate.xml:
XML
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:duration="1000"
android:toXDelta="0"
android:toYDelta="300">
</translate>
</set>
接下来在Java代码中调用就好了,代码如下所有:
java
SlideView slideView = findViewById(R.id.slide);
slideView.setAnimation(AnimationUtils.loadAnimation(this,R.anim.translate));
运行程序,我们设置的View会向右平移300像素,然后又会回到原来的位置。为了解决这个问题,我们需要在translate.xml中加上fillAfter="true",代码如下所示:
XML
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:fillAfter="true">
<translate
android:duration="1000"
android:toXDelta="0"
android:toYDelta="300">
</translate>
</set>
需要注意的是,View动画并不能改变View的位置参数。如果一个Button进行如上的平移动画操作,当Button平移300像素停留在当前位置时,我们点击这个Button并不会触发点击事件,但在我们点击这个Button的原始位置时触发了点击事件。对于系统来说这个Button并没有改变原来的位置,所以我们点击其他位置当然不会触发这个Button的点击事件。在Android 3.0时出现的属性动画解决了上述问题,因为它不仅可以执行动画,还能够改变View的位置参数。当然,这里使用属性动画移动那就更简单了,我们让SlideView在1000ms内沿着X轴向右平移300像素,代码如下所示:
java
ObjectAnimator.ofFloat(slideView,"translateX",0,300).setDuration(1000).start();
scrollTo与scrollBy
scrollTo(x,y)表示移动到一个具体的坐标点,而scrollBy(dx,dy)则表示移动的增量为dx、dy。其中scrollBy最终也是要调用scrollTo的。View的scrollBy和scrollTo的源码如下所示:
java
public void scrollTo(int x, int y) {
if (mScrollX != x || mScrollY != y) {
int oldX = mScrollX;
int oldY = mScrollY;
mScrollX = x;
mScrollY = y;
invalidateParentCaches();
onScrollChanged(mScrollX, mScrollY, oldX, oldY);
if (!awakenScrollBars()) {
postInvalidateOnAnimation();
}
}
}
public void scrollBy(int x, int y) {
scrollTo(mScrollX + x, mScrollY + y);
}
scrollTo、scrollBy移动的是View的内容,如果在ViewGroup中使用,则是移动其所有的子View。我们将ACTION_MOVE中的代码替换成如下代码:
java
((View)getParent()).scrollBy(-offsetX,-offsetY);
Scroller
我们在用scrollTo/scrollBy方法进行滑动时,这个过程是瞬间完成的,所以用户体验不太好。这里我们可以使用Scroller来实现有过渡效果的滑动,这个过程不是瞬间完成的,而是在一定的时间间隔内完成的。Scroller本身不能实现View的滑动的,它需要与View的computeScroll()方法配合才能实现弹性滑动的效果。在这里我们实现SlideView平滑地向右移动。首先我们要初始化Scroller,代码如下所示:
java
public SlideView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
mScroller = new Scroller(context);
}
接下俩重写computeScroll()方法,系统会在绘制View的时候draw()方法中调用该方法。在这个方法中,我们调用父类的scrollTo()方法并通过Scroller来不断获取当前滚动值,没滑动一小段距离我们就调用invalidate()方法不断地进行重绘,重绘就会调用computeScroll()方法,这样我们通过不断地移动一个小的距离并连贯起来就实现了平滑移动的效果。
java
@Override
public void computeScroll() {
if(mScroller.computeScrollOffset()){
((View)getParent()).scrollBy(mScroller.getCurrX(),mScroller.getCurrY());
invalidate();
}
}
我们在SlideView中写一个smoothScrollTo方法,调用Scroller的startScroll()方法,在2000ms内沿着X轴平移,代码如下:
java
public void smoothScrollTo(int destX,int destY){
mScroller.startScroll(getScrollX(),getScrollY(),destX,destY,2000);
invalidate();
}
最后调用smoothScrollTo()方法,这里我们设定沿着X轴右移的增量为-10,代码如下:
java
slideView.smoothScrollTo(-10,0);