在日常开发中,有时会遇到需要对View进行滑动处理的情况,今天我们就一起来看一下如何实现View的滑动。

不管采用什么方式,实现思路基本是一致的:当触摸到View时,记下当前触摸点的坐标;当手指移动时,记下移动后触摸点的坐标,然后用两个坐标算出移动的偏移量,再利用偏移量来修改View的坐标。

下面分别来讲解实现View滑动的6种方式。

一、layout()

layout()方法是View在进行布局流程时调用的一个方法,我们可以在移动View时调用次方法,不断的进行View的布局,达到滑动View的目的。具体过程如下:

(1)先自定义一个View,重写onTouchEvent(MotionEvent event)方法,在ACTION_DOWN时获取按下时的坐标。代码如下:

1
2
3
4
5
6
7
8
9
10
11
@Override
public boolean onTouchEvent(MotionEvent event) {
int x = (int) event.getX();
int y = (int) event.getY();

switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
lastX = x;
lastY = y;
break;
...

(2)在手指移动时,计算偏移量并调用layout()方法进行重新布局。

1
2
3
4
5
6
...
case MotionEvent.ACTION_MOVE:
int offsetX = x - lastX;
int offsetY = y - lastY;
way1(offsetX, offsetY);
...

其中way1()方法的定义如下:

1
2
3
4
5
6
private void way1(int offsetX, int offsetY) {
layout(getLeft() + offsetX,
getTop() + offsetY,
getRight() + offsetX,
getBottom() + offsetY);
}

二、offsetLeftAndRight()与offsetTopAndBottom()

这两个方法的使用与效果和layout()方法差不多,将ACTION_MOVE中的代码替换为way2()即可,其中way2()定义如下:

1
2
3
4
private void way2(int offsetX, int offsetY) {
offsetLeftAndRight(offsetX);
offsetTopAndBottom(offsetY);
}

三、LayoutParams

LayoutParams保存了View的布局参数,因此我们可以通过改变布局参数的值来改变View的位置。将ACTION_MOVE中的代码替换为way3_1()即可,其中way3_1()定义如下:

1
2
3
4
5
6
private void way3_1(int offsetX, int offsetY) {
LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) getLayoutParams();
layoutParams.leftMargin = getLeft() + offsetX;
layoutParams.topMargin = getTop() + offsetY;
setLayoutParams(layoutParams);
}

因为父控件是LinearLayout,所以用了LinearLayout.LayoutParams,除了使用布局的LayoutParams,还可以使用ViewGroup.MarginLayoutParams来改变View的位置。将ACTION_MOVE中的代码替换为way3_2()即可,其中way3_2()定义如下:

1
2
3
4
5
6
private void way3_2(int offsetX, int offsetY) {
ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams) getLayoutParams();
layoutParams.leftMargin = getLeft() + offsetX;
layoutParams.topMargin = getTop() + offsetY;
setLayoutParams(layoutParams);
}

四、scrollTo与scrollBy

scollTo(x,y)表示移动到一个具体的坐标点,而scollBy(dx,dy)则表示移动的增量为dxdy。其中scollBy最终也是要调用scollTo的。scollToscollBy移动的是View的内容,如果在ViewGroup中使用则是移动他所有的子View。将ACTION_MOVE中的代码替换为way4()即可,其中way4()定义如下:

1
2
3
private void way4(int offsetX, int offsetY) {
((View) getParent()).scrollBy(-offsetX, -offsetY);
}

这里要实现View随着我们手指移动的效果的话,我们就需要将偏移量设置为负值。

五、Scroller

Scroller本身是不能实现View的滑动的,它需要配合View的computeScroll()方法才能弹性滑动的效果。

(1)初始化Scroller

在构造函数中初始化Scroller

1
2
3
4
public MyScrollView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
scroller = new Scroller(context);
}

(2)重写computeScroll()

系统会在绘制View的时候在draw()方法中调用该方法,这个方法中我们调用父类的scrollTo()方法并通过Scroller来不断获取当前的滚动值,每滑动一小段距离我们就调用invalidate()方法不断的进行重绘,重绘就会调用computeScroll()方法,这样我们就通过不断的移动一个小的距离并连贯起来就实现了平滑移动的效果:

1
2
3
4
5
6
7
8
@Override
public void computeScroll() {
super.computeScroll();
if (scroller.computeScrollOffset()) {
((View) getParent()).scrollTo(scroller.getCurrX(), scroller.getCurrY());
invalidate();
}
}

(3)调用Scroller.startScroll()方法

我们在MyScrollView中写一个smoothScrollTo()方法,调用Scroller.startScroll()方法,在2000毫秒内沿X轴和Y轴移动x和y个像素:

1
2
3
4
5
6
7
8
public void smoothScrollTo(int x, int y) {
int scrollX = getScrollX();
int scrollY = getScrollY();
int offsetX = scrollX - x;
int offsetY = scrollY - y;
scroller.startScroll(scrollX, scrollY, offsetX, offsetY, 2000);
invalidate();
}

(4)调用MyScrollViewsmoothScrollTo()方法

1
view.smoothScrollTo(200, 300);

六、属性动画

最后就是定义一个属性动画进行View的滑动,属性动画的具体使用将放在后续章节中进行讲解,这里直接贴出实现代码:

1
2
3
ObjectAnimator animator = ObjectAnimator.ofFloat(view, "translationX", 400);
animator.setDuration(2000);
animator.start();

源码见Github

参考文献:
《Android进阶之光》
《Android开发艺术探索》

关注我