【Android】View 的滑动
在Android中,View的滑动是用户交互中的常见操作。实现View滑动的方法有很多,这里主要介绍6种滑动方法。
1. layout()方法
View 进行绘制的时候会调用 onLayout()方法来设置显示的位置,因此我们可以通过修改的 View 的left、top、right、bottom 这4种属性来控制View的坐标。
java
public class CustomView extends View {
private int lastX;
private int lastY;
public CustomView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public CustomView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CustomView(Context context) {
super(context);
}
@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;
}
}

2. offsetLeftAndRight()与offsetTopAndBottom()
这两种方法和 layout() 方法的效果差不多,使用方法也差不多。将 ACTION_MOVE 中的代码替换成如下代码:
java
case MotionEvent.ACTION_MOVE:
// 计算移动距离
int offsetX = x - lastX;
int offsetY = y - lastY;
// 对 left 和 right 进行偏移
offsetLeftAndRight(offsetX);
// 对 top 和 bottom 进行偏移
offsetTopAndBottom(offsetY);
break;
3. LayoutParams(改变布局参数)
LayoutParams 主要保存了一个 View 的布局参数,因此我们可以通过 LayoutParams 来改变 View 的布局参数从而达到改变 View 位置的效果。同样,我们将 ACTION_MOVE 中的代码替换成如下代码:
java
int offsetX = x - lastX;
int offsetY = y - lastY;
LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) getLayoutParams();
layoutParams.leftMargin = getLeft() + offsetX;
layoutParams.topMargin = getTop() + offsetY;
setLayoutParams(layoutParams);
因为父控件是 LinearLayout,所以这里使用了 LinearLayout.LayoutParams。如果父控件是 RelativeLayout,则要使用 RelativeLayout.LayoutParams。除了使用布局的 LayoutParams 之外,还可以用 ViewGroup.MarginLayoutParams 来实现:
java
int offsetX = x - lastX;
int offsetY = y - lastY;
ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams) getLayoutParams();
layoutParams.leftMargin = getLeft() + offsetX;
layoutParams.topMargin = getTop() + offsetY;
setLayoutParams(layoutParams);
4. 动画
可以采用 View 动画来移动,在 res 目录新建 anim 文件夹并创建 translate.xml:
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:fromXDelta="0"
android:toXDelta="500"/>
</set>
这是一个持续 1 秒的平移动画,使控件向右移动 500px 并停留在终点位置。
然后在 Java 代码中调用:
java
customView.setAnimation(AnimationUtils.loadAnimation(this, R.anim.translate));
效果如下:
需要注意的是,View 动画并不能改变 View 的位置参数。如果对一个 Button 进行如上的平移滑动操作,当 Button 平移 500 像素停留在当前位置时,我们点击这个 Button 并不会触发点击事件,但在我们点击这个 Button 的原始位置时却触发了点击事件。对于系统来说这个 Button 没有改变原来的位置,所以点击其他位置当然不会触发它的点击事件。
**TranslateAnimation **只改变了 绘制时的位置 ,不会真正修改 View 的 布局坐标 (layout 参数没变)。所以动画结束后,再去获取这个 View 的
getLeft()
、getTop()
之类的坐标,值还是原来的。
而属性动画则解决了上述问题,因为它不仅可以执行动画,还能够改变 View 的位置参数。使用示例:
java
ObjectAnimator.ofFloat(customView, "translationX", 0, 500).setDuration(1000).start();
5. ScrollTo 和 scrollBy
scrollTo(x, y)
表示移动到一个具体的坐标点,而 scrollBy(dx, dy)
则表示移动的增量为 dx、dy。源码如下:
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);
}
可以看到,scrollBy 移动的时 View 的内容,如果在 ViewGroup 中使用,则是移动其所有的子 View。将ACTION_MOVE 中的代码修改如下:
java
((View)getParent()).scrollBy(-offsetX, -offsetY);
6. Scroller
在使用 scrollTo/scrollBy 方法进行滑动时,这个过程是瞬间完成的,所以用户体验不太好。这里可以使用 Scroller 来实现有过渡效果的滑动,这个过程不是瞬间完成的,而是在一定的时间间隔完成的。Scroller 本身是不能实现 View 的滑动的,他需要与 View 的 computeScroll()
方法配合才能实现弹性滑动的效果。这里我们实现 CustomView 平滑地向右移动。首先我们要初始化 Scroller,代码如下所示:
java
public CustomView(Context context, AttributeSet attrs) {
super(context, attrs);
mScroller = new Scroller(context);
}
接下来重写 computeScroll()
方法,系统会在绘制 View 的时候在 draw()
方法中调用该方法。在这个方法中,我们调用父类的 scrollTo()
方法并通过 Scroller 来不断获取当前的滚动值,每滑动一小段距离我们就调用 invalidate()
方法不断地进行重绘,重绘就会调用 computeScroll()
方法,这样通过不断地移动一个小的距离并连贯起来就实现了平滑移动的效果。
java
@Override
public void computeScroll() {
super.computeScroll();
if (mScroller.computeScrollOffset()) {
((View)getParent()).scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
invalidate();
}
}
我们在 CustomView 中写一个 smoothScrollTo
方法,调用 Scroller 的 startScroll()
方法,在 2000ms 内沿 X 轴平移 delta 像素,代码如下所示:
java
public void smoothScrollTo(int x, int y) {
int scrollX = getScrollX();
int delta = x - scrollX;
mScroller.startScroll(scrollX, 0, delta, 0, 2000);
invalidate();
}
最后在 Java 代码中调用 CustomView 的 smoothScrollTo() 方法。这里设定 CustomView 沿着 X 轴向右平移 500 像素。
java
customView.smoothScrollTo(-500, 0);
