上一篇文章Android View事件体系总结其一主要介绍了View事件体系关联的一下基础参数与使用方法和场景。下面就开始进入正题,View的滑动相关。
在Android设备是上,滑动几乎是每个应用都会设计的问题。另外由于手机设别屏幕比较小,为了给用户展现更好的体验,就i需要使用滑动来显示或隐藏一些内容。
View滑动
实现滑动的效果主要有三种方式:
1,使用scrollTo/scrollBy
为了实现View的滑动,View本身提供了专门的方法来实现。那就是scrollTo和scrollBy
/**
* 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();
}
}
}
/**
* Move 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 amount of pixels to scroll by horizontally
* @param y the amount of pixels to scroll by vertically
*/
public void scrollBy(int x, int y) {
scrollTo(mScrollX + x, mScrollY + y);
}
从源码上来看,scrollBy实际上也是在方法内调用了scrollTo,它实现了基于当前位置的相对滑动。而scrollTo是显示了基于传递参数的绝对滑动。这其中需要关注的是View内部两个属性mScrollX和mScrollY的改变规则,这两个属性可以通过getScrollX(),getScrollY()方法获得。注意的是:在滑动过程中,mScrollX的值总是等于View左边缘和View内容左边缘在水平方向的距离,而mScrollY的值总是等于View上边缘和View内容上边缘在竖直方向的距离。mScrollX和mScrollY只能改变View内容的位置,而不能改变View在布局中的位置。
2,使用动画
使用平移动画也可以实现View的滑动,使用动画来移动View,主要操作View的translationX和translationY属性。既可以采用传统的View动画,也可以采用属性动画。但如果使用属性动画的话,为了兼容Android 3.0以下版本,需要使用开源动画库
implementation 'com.nineoldandroids:library:2.4.0'
采用View动画将一个View水平方向移动100像素。
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:duration="300"
android:fromXDelta="0"
android:interpolator="@android:anim/accelerate_decelerate_interpolator"
android:toXDelta="100" />
</set>
采用属性动画会更简单
ObjectAnimator.ofFloat(headerLayout, "translationX", 0, 100)
.setDuration(100).start();
采用动画实现View滑动,需要注意的是:
View动画是对View的影像做操作,它并不能真正改变View的位置参数,并且如果想要保持View动画后的状态,必须将fillAfter属性设置为true,否则动画在完成后其动画结果就会消失。使用属性动画并不会存在上述问题,但在Android 3.0之前无法实现属性动画,这时候我们可以使用动画兼容库 nineoldandroids来实现属性动画,尽管如此,在Android3.0以下版本的手机使用属性动画其本质仍是View动画。
另外就是我们刚才提到使用View动画,并不能真正改变View的位置,这样就会带来一个很严重的问题,试想我们使用View动画将一个Button想左移动了200像素,你会惊奇的发现,原先这个按钮上注册的点击事件,点击之后没有效果了,而点击动画之前的位置依旧会触发事件,这就搞笑啦!但我们仍能理解,因为View仅仅是镜像的改变,而它本身的位置信息并没有随着动画的滑动发生改变,可是引起的后果却是致命的。
解决的方法倒也不是没有,我们可以实现在View动画移动之后的位置创建一个一模一样的Button然后注册相同的事件,在View完成动画之后,将之前位置的Button隐藏,通过这种间接的方式解决上面的问题。
3,改变布局参数
最后一种方式就是改变View的布局参数,通过设变LayoutParams将headerLayout向右移动100像素,只需将marginLeft参数的值增加100像素即可。
ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) headerLayout.getLayoutParams();
//水平方向向右移动100像素
params.leftMargin+=100;
headerLayout.setLayoutParams(params);
//或headerLayout.requestLayout();
三种滑动方式对比
scrollTo/scrollBy : 操作简单,适合对View内容的移动。
动画:操作简单,主要适用于没有事件交互的View和实现复杂的动画效果。
改变布局参数:操作稍微复杂,适用于有交互的View。
View弹性滑动
通过上文我们我们知晓,通过三种当时可以实现View的滑动。但这些滑动方式多为瞬间完成的,看起了比较生硬,这种体验效果很差,因此通常我们使用渐进式的弹性滑动。实现弹性滑动的方式也有三种,下面我们就一一介绍:
1,使用Scroller
我们先来介绍一下Scroller的工作原理,当我们构造一个Scroller对象并调用其startScroll方法时,Scroller内部其实什么也没做,仅仅是保存了我们传递的几个参数。
Scroller scroller = new Scroller(this);
//调用此方法滚动到目标位置
public void smoothScrollTo(int fx, int fy) {
int dx = fx - mScroller.getFinalX();
int dy = fy - mScroller.getFinalY();
smoothScrollBy(dx, dy);
}
//调用此方法设置滚动的相对偏移
public void smoothScrollBy(int dx, int dy) {
//设置mScroller的滚动偏移量
mScroller.startScroll(mScroller.getFinalX(), mScroller.getFinalY(), dx, dy);
invalidate();
//这里必须调用invalidate()才能保证computeScroll()会被调用,否则不一定会刷新界面,看不到滚动效果
}
//在绘制View时,会在draw()过程调用该方法。
@Override
public void computeScroll() {
//先判断mScroller滚动是否完成
if (mScroller.computeScrollOffset()) {
//这里调用View的scrollTo()完成实际的滚动
scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
//必须调用该方法,否则不一定能看到滚动效果
postInvalidate();
}
super.computeScroll();
}
/**
* Start scrolling by providing a starting point, the distance to travel,
* and the duration of the scroll.
*
* @param startX Starting horizontal scroll offset in pixels. Positive
* numbers will scroll the content to the left.
* @param startY Starting vertical scroll offset in pixels. Positive numbers
* will scroll the content up.
* @param dx Horizontal distance to travel. Positive numbers will scroll the
* content to the left.
* @param dy Vertical distance to travel. Positive numbers will scroll the
* content up.
* @param duration Duration of the scroll in milliseconds.
*/
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方法的参数有五个,startX,startY表示滑动的起点坐标,dx,dy表示水平和竖直方向滑动的距离,而duration表示的是滑动时间。注意这里滑动的是指View的内容而非View本身位置信息的改变。
可以看出仅调用startScroll方法是无法让View滑动的,因为它内部并没有做滑动相关的事,最终显示滑动的方法是View的invalidate()方法。invalidate方法会导致View重绘,在View的draw方法中又会去调用computeScroll方法,computeScroll方法是一个空实现,因此需要我们自己去实现。正是因为computeScroll这个方法,View才能是实现弹性滑动,当View重绘后会在draw方法中调用computeScroll方法,而computeScroll方法中又获取向Scroller获取当前的scrollX和scrollY;然后通过scrollTo方法实现滑动;接着又调用postInvalidate方法来进行第二次重绘,这一次重绘和第一次重绘过程一样,还是会导致computeScroll被调用。如此反复,直至整个滑动过程结束。
/**
* Call this when you want to know the new location. If it returns true,
* the animation is not yet finished.
*/
public boolean computeScrollOffset() {
if (mFinished) {
return false;
}
int timePassed = (int)(AnimationUtils.currentAnimationTimeMillis() - mStartTime);
if (timePassed < mDuration) {
switch (mMode) {
case SCROLL_MODE:
final float x = mInterpolator.getInterpolation(timePassed * mDurationReciprocal);
mCurrX = mStartX + Math.round(x * mDeltaX);
mCurrY = mStartY + Math.round(x * mDeltaY);
break;
case FLING_MODE:
。。。
}
}
return true;
}
这个方式通过时间的流逝来计算当前scrollX和scrollY的值,通过时间流逝的百分比计算出scrollX和scrollY改变的百分比来计算出当前的值,类似与动画中插值器的概念。
总结概括一下Scroller的原理就是:
Scroller本身不能实现View的滑动,必须配合View的computeScroll方法才能完成弹性滑动的效果。他不断让View重绘,每次重绘距离启动时间会有一个时间间隔,通过这个时间间隔就可以得出View当前滑动的位置,知道了滑动位置就可以通过scrollTo方法来完成View的滑动。就这样,View的每一次重绘都会导致View进行小幅度的滑动,而多次小幅度滑动就组成了弹性滑动。
这就是Scroller的工作机制,可见Scroller的设计思想多么令人叫绝,整个过程对View没有丝毫的引用,甚至在它内部连计数器都没有。
2,通过动画
动画本身就是一种渐变的过程,它天然的是实现就具有弹性效果。
3,使用延时策略
其核心策略就是通过一系列延时消息从而达到一种渐进式的效果。具体来说可以使用Handler和View的postDelayed方法,也可以使用线程的sleep方法。通过不断发送的延时消息,实现View的分段滑动,实现这种弹性效果。
下一章 Android View事件体系汇总其三