View滑动的几种方式
1. 使用scrollTo/scrollBy
为了实现View的滑动,View提供了scrollTo和scrollBy这两个方法。它们的具体实现如下:
/**
* Move the scrolled position of your view. This will cause a call to onScrollChanged(int, int, int, int) and the view will be invalidated.
* Params:
* x – the amount of pixels to scroll by horizontally
* y – the amount of pixels to scroll by vertically
*/
public void scrollBy(int x, int y) {
scrollTo(mScrollX + x, mScrollY + y);
}
/**
* Set the scrolled position of your view. This will cause a call to
* {@link #onScrollChanged(int, int, int, int)} and the view will be
* invalidated.
* @param x the x position to scroll to
* @param y the y position to scroll to
*/
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();
}
}
}
从上面的源码可以看出,scrollBy实际上是调用了scrollTo方法。scrollTo方法中修改了mScrollX和mScrollY的值,下面对其进行简单介绍:
-
mScrollX:通过
getScrollX()获取。修改mScrollX的值会改变View内容的位置,但View本身的位置(left、top、right、bottom)不会改变。需要注意的是,mScrollX表示View内容的位置和View本身位置的距离,而translationX表示View本身相对于父容器的偏移量。在滑动过程中,mScrollX总是等于View左边缘和View内容左边缘在水平方向的距离。 当View左边缘在View内容左边缘的右边时,mScrollX为正值,反之为负值。 如果从左向右滑动,那么mScrollX为负值,反之为正值。 -
mScrollY:同
mScrollX。 -
scrollTo:实现基于绝对位置的滑动,通过修改
mScrollX和mScrollY来改变View内容的位置。注意,scrollTo只能改变View内容的位置,而不能改变View在布局中的位置,使用如下:// 将View内容滑动到View的原位置 view.scrollTo(0, 0); // 将View内容滑动到View原位置右方100px的位置 view.scrollTo(-100, 0); // 将View内容滑动到View原位置下方100px的位置 view.scrollTo(0, -100); -
scrollBy:实际也是调用了
scrollTo方法,实现基于当前位置的相对滑动,使用如下:// 基于View内容的当前位置,向右方滑动100px view.scrollBy(-100, 0); // 基于View内容的当前位置,向下方滑动100px view.scrollBy(0, -100);
2. 使用动画
2.1 View动画
通过改变translationX和translationY属性,使用View动画可以让View进行平移,示例如下:
// 将View向右平移100px,向下平移100px
TranslateAnimation animation = new TranslateAnimation(0f, 100f, 0f, 100f);
animation.setDuration(1000);
animation.setFillAfter(true);
animation.setInterpolator(new LinearInterpolator());
view.startAnimation(animation);
需要注意的是,View动画并没有真正改变View的位置参数,这会导致View显示位置改变了,但点击事件仍需在原位置触发。
2.2 属性动画
使用属性动画可以改变View的位置参数,解决View动画事件触发位置的问题,示例如下:
// 将View向右平移100px
ObjectAnimator.ofFloat(view, "translationX", 0f, 100f).start();
3. LayoutParams
通过改变LayoutParams的margin值也可以实现View滑动,示例如下:
// 通过设置leftMargin增加100px,让View向右滑动100px
ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) view.getLayoutParams();
params.leftMargin += 100;
view.setLayoutParams(params);
view.requestLayout();
通过以上几种方式,可以实现View的滑动效果,每种方式都有其适用场景和优缺点,根据具体需求选择合适的方法。
4. 使用layout方法
在自定义View中,可以通过调用View的layout(left, top, right, bottom)方法来改变View的位置,实现滑动,如下:
// 在自定义View中,计算滑动的距离,然后调用layout方法来重新设置View的位置
// 将View向右滑动100px
int offsetX = 100;
layout(getLeft() + offsetX, getTop(), getRight() + offsetX, getBottom());
接下来,我们实现一个自定义View,可以跟随手指滑动。这个View实现起来很简单,只需要重写它的onTouchEvent方法并处理ACTION_MOVE事件,根据两次滑动之间的距离就可以实现滑动。我们选择用layout方法来实现,核心代码如下:
// 分别记录上次滑动的坐标
private int mLastX = 0;
private int mLastY = 0;
@Override
public boolean onTouchEvent(MotionEvent event) {
// 获取手指当前的坐标
int x = (int) event.getRawX();
int y = (int) event.getRawY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
break;
case MotionEvent.ACTION_MOVE:
// 计算手指滑动的距离
int deltaX = x - mLastX;
int deltaY = y - mLastY;
Log.d(TAG, "move, deltaX:" + deltaX + " deltaY:" + deltaY);
// 重新计算View四个顶点的位置
int left = getLeft() + deltaX;
int top = getTop() + deltaY;
int right = getRight() + deltaX;
int bottom = getBottom() + deltaY;
// 调用layout方法来重新设置View的位置
layout(left, top, right, bottom);
break;
case MotionEvent.ACTION_UP:
break;
}
mLastX = x;
mLastY = y;
return true;
}
5. 使用动画实现View跟随手指滑动
除了使用layout方法,我们还可以通过动画来实现View跟随手指滑动。实现方式类似,需要重写View的onTouchEvent方法并处理ACTION_MOVE事件。核心代码如下:
// 分别记录上次滑动的坐标
private int mLastX = 0;
private int mLastY = 0;
@Override
public boolean onTouchEvent(MotionEvent event) {
// 获取手指当前的坐标
int x = (int) event.getRawX();
int y = (int) event.getRawY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
break;
case MotionEvent.ACTION_MOVE:
// 计算手指滑动的距离
int deltaX = x - mLastX;
int deltaY = y - mLastY;
Log.d(TAG, "move, deltaX:" + deltaX + " deltaY:" + deltaY);
// 根据当前translationX和translationY计算滑动后的值
float translationX = getTranslationX() + deltaX;
float translationY = getTranslationY() + deltaY;
// 使用动画完成View滑动
setTranslationX(translationX);
setTranslationY(translationY);
break;
case MotionEvent.ACTION_UP:
break;
}
mLastX = x;
mLastY = y;
return true;
}
6. 使用Scroller
Scroller用于实现View的弹性滑动。Scroller本身无法直接让View滑动,它需要和View的computeScroll方法配合使用。Scroller的工作过程如下:
- 创建
Scroller对象,并调用startScroll方法,传递滑动相关参数。Scroller内部会保存这些参数。 - 调用
invalidate方法触发View的重绘,重绘过程中会调用computeScroll方法。 - 重写
computeScroll方法,在其中实现View的弹性滑动。
使用方式如下:
/**
* 在View内部或外部调用,让View缓慢滚动到指定位置
*/
public void smoothScrollTo(int destX, int destY) {
int scrollX = getScrollX();
int delta = destX - scrollX;
// startScroll方法保存了滑动参数,具体滑动由invalidate方法发起
mScroller.startScroll(scrollX, 0, delta, 0, 1000);
// invalidate方法会导致View重绘,重绘过程中会调用computeScroll方法
invalidate();
}
/**
* 重写computeScroll方法,完成View的滑动
*/
@Override
public void computeScroll() {
// computeScrollOffset根据时间流逝计算当前scrollX和scrollY
if (mScroller.computeScrollOffset()) {
// 调用scrollTo滑动View,滑动距离由computeScrollOffset计算
scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
// postInvalidate继续调用computeScroll方法,逐步完成View的整个滑动过程
postInvalidate();
}
}
下面是Scroller的startScroll方法的实现:
/**
* 保存滑动相关参数
* @param startX 滑动起点的x坐标
* @param startY 滑动起点的y坐标
* @param dx x方向要滑动的距离
* @param dy y方向要滑动的距离
* @param duration 滑动过程的时间
*/
public void startScroll(int startX, int startY, int dx, int dy, int duration) {
mMode = SCROLL_MODE;
mFinished = false;
mDuration = duration;
mStartTime = AnimationUtils.currentAnimationTimeMillis();
mStartX = startX;
mStartY = startY;
mFinalX = startX + dx;
mFinalY = startY + dy;
mDeltaX = dx;
mDeltaY = dy;
mDurationReciprocal = 1.0f / (float) mDuration;
}
通过以上几种方式,可以实现View的不同滑动效果。每种方式都有其适用场景和优缺点,应根据具体需求选择合适的方法。