Android View的滑动和滑动冲突
一、View的滑动
可以通过三种方式实现滑动。
- 通过View本身提供的scrollTo和scrollBy方法来实现滑动
- 通过动画给View施加平移效果来实现滑动
- 通过改变View的LayoutParams使得View重新布局从而实现滑动
1.使用scrollTo和scrollBy
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实际调用了scrollTo,实现了相对于当前位置的相对滑动。scrollTo实现了基于所传递参数的绝对滑动。
scrollTo和scrollBy只能改变View的内容位置,而不能改变View在布局中的位置。
mScrollX的值暂时等于View左边缘和View内容左边缘在水平方向的距离,内容在View的左边,为正值,反之为负值
mScrollY的值总是等于View上边缘和View内容上边缘在竖直方向的距离,内容在View的上边,为正值,反之为负值
2.使用动画
使用动画来移动移动View,主要是操作View的translationX和translationY属性。可以选择传动的View动画,也可以选择属性动画。
加载xml动画100ms把View移动到右下角100个像素,新位置只是View的影像,点击目标view会触发click。
<?xml version="1.0" encoding="utf-8"?>
<!--fillAfter = true设置停留在动画后的音像-->
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:fillAfter="true"
android:zAdjustment="normal">
<translate
android:duration="100"
android:fromXDelta="0"
android:fromYDelta="0"
android:interpolator="@android:anim/linear_interpolator"
android:toXDelta="100"
android:toYDelta="100" />
</set>
val animation = AnimationUtils.loadAnimation(this@MainActivity, R.anim.translate_100)
v.startAnimation(animation)
属性动画,将View右移动100像素,点击目标View,不会触发click。
ObjectAnimator.ofFloat(v, "translationX", 0f, 100f).setDuration(100).start()
3.改变布局参数
修改LayoutPrams的值达到滑动效果,右移100px
val params: ViewGroup.MarginLayoutParams =
textView.layoutParams as ViewGroup.MarginLayoutParams
params.leftMargin += 100
v.requestLayout()
//v.layoutParams = params
4.对比总结
- scrollTo和scrollBy:操作简单,View提供的原生方法,适合View内容的滑动。不影响元素的单击事件。缺点是只能移动内容,不能移动View本身。
- 动画:属性动画没有明显缺点,如果是使用xml动画,不能改变View本身的属性。如果动画元素不需要响应用户的交互,使用动画来做滑动比较合适。并且还能实现复杂的效果。
- 改变布局参数:使用麻烦一点,没有明显缺点,适用于动画元素是一些具有交互性的View。
二、弹性滑动
1.使用Scroller
public void smoothScrollTo(int destX, int destY) {
int scrollX = getScrollX();
int scrollY = getScrollY();
mScroller.startScroll(scrollX, scrollY, destX, destY, 1000);
invalidate();
}
public void computeScroll() {
super.computeScroll();
if (mScroller.computeScrollOffset()) {
scrollTo(mScroller.getCurrX(), mScroller.getCurrX());
postInvalidate();
}
}
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;
}
startScroll是无法让View滑动的,因为它内部并没有做滑动相关的事情。startScroll方法下面有一个invalidate方法,会导致View重绘,在View的onDraw方法就会去调用computeScroll方法,computeScroll在View中是一个空的实现。我们在实现时,又去向scroller获取当前的scrollX和scrollY,再用scrollTo方法实现滚动,接着又调用postInvalidate方法进行第二次重绘,如此反复,就完成了滑动的整个过程。
2.通过动画
动画本身就是一个渐进的过程,天然的具有弹性效果,如上面的属性动画。
ObjectAnimator.ofFloat(v, "translationX", 0f, 100f).setDuration(100).start()
3.使用延迟策略
核心思想是通过发送一系列的延迟消息从而达到渐进时的效果。可以使用Handler或者View的postDelayed方法,也可以使用线程的sleep方法。
三、View的滑动冲突
1.常见的滑动冲突场景
- 外部滑动方向和内部滑动方向不一致
- 外部滑动方向和内部滑动方向一致
- 以上两种情况嵌套
2.滑动冲突的解决方式
- 外部拦截法,父容器需要此事件就拦截,不需要就不拦截。重写父容器的onInterceptTouch
Event方法就行。 - 内部拦截法,父容器不拦截任何事件,所有事件都传递给子元素,如果子元素需要此事件就直接消耗掉,否则就交由父容器进行处理。需要配合requestDisallowInterceptTouchEvent方法使用。