View 的滑动在 Android 中非常广泛,例如我们在自定义控件,实现一些效果时候,常常需要使用到 View 的滑动,View 滑动方式包含如下七种。
View 在绘制时候,会调用 onLayout() 方法设置 View 的显示位置,因此,我们完全可以使用 layout() 函数重新对 View 位置进行布局,来实现滑动的效果。接下来,我们来自定义一个跟随手指移动的 View。
//1.自定义 View
public class CustomView extends View {
private int mLastX;
private int mLastY;
public CustomView(Context context) {
this(context, null);
}
public CustomView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public CustomView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
//获取 X坐标 Y坐标
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mLastX = x;
mLastY = y;
break;
case MotionEvent.ACTION_MOVE:
//计算移动的距离
int offsetX = x - mLastX;
int offsetY = y - mLastY;
//调用layout方法来重新布局
layout(getLeft() + offsetX, getTop() + offsetY,
getRight() + offsetX, getBottom() + offsetY);
break;
default:
break;
}
return true;
}
}
//2.在布局文件中使用
<com.yoyiyi.blob.CustomView
android:id="@+id/cv"
android:layout_width="40dp"
android:layout_height="40dp"
android:background="#000000" />
上面代码非常简单,在 onTouchEvent 中我们获取到手指点击的坐标,然后计算出其移动距离,最后是由 layout() 重新进行布局。
采用动画来进行移动,动画分为 View 动画和属性动画,最大区别是 View 动画只是对 View 的影像的变化,View 还是在原来位置,响应事件也在原来位置。
//1.创建一个在 X 轴上 平移到坐标 200 动画
TranslateAnimation translateAnimation = new TranslateAnimation(0, 200, 0, 0);
translateAnimation.setDuration(1000);
translateAnimation.setFillAfter(true);
cv.setAnimation(translateAnimation);
translateAnimation.start();
属性动画就更加简单了,代码如下。
ObjectAnimator translation = ObjectAnimator.ofFloat(cv, "translationX", 0, 200).setDuration(1000).start();
scollTo(x, y) 表示移动到具体的坐标点,scollBy(dx, dy) 表示移动增量 dx、dy,scollBy 最终也是调用 scollTo。需要注意的是这两个方法都是移动 View 的内容,如果是 ViewGroup 则移动子 View。
linearLayout.scollTo(300,0)
//需要注意,负数表示向正方向移动,这只是参考系不同而已
((View) getParent()).scrollBy(-offsetX, -offsetY);
这两个方法和 layout() 效果差不多,所以 2.1 的例子完全可以替换成如下方式。
offsetTopAndBottom(offsetY);
offsetLeftAndRight(offsetX);
LayoutParams 中保存了 View 的布局参数,我们可以通过获取到参数,再改变参数来实现滑动效果。
//1.使用父控件的 LayoutParams
LinearLayout.LayoutParams params= (LinearLayout.LayoutParams)getLayoutParams();
params.leftMargin = getLeft() + offsetX;
params.topMargin = getTop() + offsetY;
setLayoutParams(params)
//2.使用 ViewGroup.MarginLayoutParams
ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams) getLayoutParams();
layoutParams.leftMargin = getLeft() + offsetX;
layoutParams.topMargin = getTop() + offsetY;
setLayoutParams(layoutParams);
使用 scollTo 和 scollBy 方法,滑动是一瞬间完成,体验不好,这里就需要使用到 Scroller 来实现弹性滑动。我们自定义一个能实现滑动 View,如下所示。
//1.自定义 View
public class CustomView extends View {
private Scroller mScroller;
public CustomView(Context context) {
this(context, null);
}
public CustomView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public CustomView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mScroller = new Scroller(context);
}
public void smoothScrollTo(int dstX,int dstY){
int scrollX = getScrollX();
int deltaX = dstX - scrollX;
//6000 秒内滑向 destX
mScroller.startScroll(scrollX, 0, deltaX, 0, 6000);
invalidate();
}
@Override
public void computeScroll() {
super.computeScroll();
//computeScrollOffset() 根据时间的流逝,计算当前的 scrollX 和 scrollY 的值
if(mScroller.computeScrollOffset()){
((View) getParent()).scrollTo(mScroller.getCurrX(),mScroller.getCurrY());
//通过不断的重绘不断的调用computeScroll方法
invalidate();
}
}
}
//2.使用,向右滑动 300
cv.smoothScrollTo(-300,0);
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;
}
从上面代码看出,当我们创建一个 Scroller,并使用 startScroll(),只是保存了我们传递过来的参数,Scroller 就没有做其他的事情,那么它是如何工作的,在 startScroll() 方法后面我们调用 invalidate(),众所周知,这个方法会导致 View 重新绘制,也就是调用 View 的 draw(),而在 draw() 方法又会调用 computeScroll(),这是一个空实现,需要我们重写,上面的代码,我们调用 computeScrollOffset() ,这个方法作用根据时间的流逝,计算当前的 scrollX 和 scrollY 的值,接着获取 Scroller 当前的 scrollX 和 scrollY,然后通过,scrollTo 方法进行移动,接着又调用 invalidate() 进行第二次绘制,总结如下。
ViewDragHelper 这个类负责手势操作,它是官方写的一个专门为自定义ViewGroup处理拖拽的手势类,所以 2.1 随手势移动自定义控件可以用 ViewDragHelper 实现,这里我们实现在自定义控件里面子 View 可以自由拖拽。
//1.自定义子 View
public class CustomView extends LinearLayout {
private ViewDragHelper mHelper;
public CustomView(Context context) {
this(context, null);
}
public CustomView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public CustomView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mHelper = ViewDragHelper.create(this, new ViewDragHelper.Callback() {
@Override
public boolean tryCaptureView(@NonNull View child, int pointerId) {
//返回 true
return true;
}
@Override
public int clampViewPositionHorizontal(View child, int left, int dx) {
return left;
}
@Override
public int clampViewPositionVertical(View child, int top, int dy) {
return top;
}
});
}
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
//接管事件
return mHelper.shouldInterceptTouchEvent(event);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
mHelper.processTouchEvent(event);
//返回 true 消费事件
return true;
}
}
//2.使用自定义 View
<com.yoyiyi.blob.CustomView
android:id="@+id/cv"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:layout_width="40dp"
android:layout_height="40dp"
android:background="#000000"/>
</com.yoyiyi.blob.CustomView>
Android View滑动的七种方式总结
Android View体系(二)实现View滑动的六种方法