Scoller 弹性滑动对象,用于实现View的弹性滑动。在说弹性滑动之前,我们先来看一下View常用的滑动方式。
- 第一种:通过View本身提供的scrollTo / scrollBy 方法来实现滑动。
- 第二种:通过动画给View 施加平移效果来实现滑动。
- 第三种:通过改变View的LayoutParams 使得View 重新布局从而实现滑动
1.使用scrollTo /scrollBy
View提供了专门的方法来实现这个功能,那就是scrollerTo 和 scrollerBy,我们先来看一下这俩个方法的实现
/**设置View的滚动位置,
*这会调用 onScrollChanged(int,int,int,int)并且视图会失效
*
* @param x 要滚动到的X位置
* @param y 要滚动到的y位置
*/
public void scrollTo(int x,int y){
if(mScrollX !=x || mScrollY !=y){
int oldX= mScrollX; //当前控件x,y的位置,移动前
int oldY= mScrollY;
mScrollX=x;//x,y将要移动到的位置,传入值
mScrollY=y;
invalidateParentCaches();
//进行滑动改变
onScrollChanged(mScrollX,mScrollY,oldX,oldY);
if(!awakenScrollBars()){
postInvalidateOnAnimation();
}
}
}
/**
* 移动视图的滚动位置。
* 这将导致调用onScrollChanged(int,int,int,int)
* 并且视图会失效
*
* @param x 水平滚动的像素数量
* @param y 垂直滚动的像素数量
*/
public void scrollBy(int x, int y) {
scrollTo(mScrollX + x, mScrollY + y);
}
从源码可以看出:scrollBy 也调用了scrollTo方法,只不过
- scrollTo实现了基于所传参数的滑动
- scrollBy实现了基于当前位置的相对滑动
在滑动过程中
- mScrollX:它的值总是等于View左边缘和View内容左边缘在水平方向上的距
- mScrollY:它的值总是等于View上边缘和View内容上边缘在竖直方向上的距离
View边缘是指View的位置,由四个顶点组成
View内容边缘是指View中内容的边缘
即只能将View的内容进行移动,并不能将View本身进行移动
mScrollX,mScrollY 的单位是像素,可以通过getScrollX / getScrollY 获得。并且
当View左边缘在View内容左边缘的右边时,mScrollX 为正值,反之为负
当View上边缘在View内容上边缘的下边时,mScrollY为正值,反之为负
从传值角度来讲,如果你想让
View从左往右移动,那么mScrollX传负值,即scrollTo / scrollBy 第一个参数传负值
View从上往下移动,那么mScrollY传负值,即scrollTo / scrollBy 第而个参数传负值
反之,为正值。如下图
贴上代码
2.使用动画
通过动画我们能让一个View进行平移,而平移就是滑动,使用动画来移动View,主要是操作View的translationX和 translationY 属性,既可以采用传统的View动画,也可以采用属性动画,如果采用属性动画的话,为了兼容3.0一下版本,需要采用开源动画库nineoldandroids。动画相关可以看这里
compile'com.nineoldandroids:library:2.4.0'
采用View动画的代码,如下
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:fillAfter="true"
>
<!--fillAfter 为true表示 保留动画后的状态-->
<!--interpolator 插值器-->
<translate
android:duration="3000"
android:toXDelta="200"
android:toYDelta="0"
android:fromXDelta="0"
android:fromYDelta="0"
android:interpolator="@android:anim/linear_interpolator"
>
</translate>
</set>
使用View动画的缺点:
View 动画是对View 的影像做操作,它并不能正真改变View的位置参数,包括宽/高。假如我们通过View动画将一个Button向右移动100px,并且这个View设置的有单击事件,你会惊奇的发现,单击新位置无法触发onClick事件,而单击原始位置则可以触发onClick事件。因为不管Button怎么做变换,但是他的位置信息(四个顶点和宽 / 高)并不会随着动画而改变,因此在系统眼里,这个Button没有发生任何改变,它的真身仍然在原始位置
上述平移如果采用属性动画,就更简单了,
ObjectAnimator.ofFloat(tv2,"translationX",0,100)
.setDuration(3000).start();
贴上完整代码
/**
* View 的滑动 2
* 动画
* 操作简单,主要用于没有否交互的View 和实现复杂的动画效果
*/
tv2=findViewById(R.id.tv2);
tv2.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
tv2.startAnimation(AnimationUtils.loadAnimation(MainActivity.this,R.anim.translate_1));
//或
ObjectAnimator.ofFloat(tv2,"translationX",0,100).setDuration(3000).start();
}
});
3.改变布局参数
改变布局参数,即改变LayouParams.比如我们想把一个Button向右平移100px,我们只需要将这个Button的LayoutParams 里的 marginLeft 参数的值增加100px 即可。我们还可以再Button左边放一个View 默认宽度为0,当需要Button右移时,给这个View设置宽度。
tv3.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) tv3.getLayoutParams();
params.leftMargin += 100;
tv3.setLayoutParams(params);
}
});
各种滑动方式对比
- scrollTo /scrollBy :操作简单,适合对View内容的滑动
- 动画:操作简单,主要适用于没有交互的View和实现复杂的动画效果
- 改变布局参数:操作 稍微复杂,适用于有交互的View
Scroller
应用场景:当我们需要实现有过度效果的动画时就可以使用 Scroller。应为scrollTo /scrollBy 的滑动过程是瞬间完成的。
先看一下Scroller的 典型代码它是固定的
Scroller mScroller=new Scroller(mContent);
//缓慢滚动到指定位置
private void smoothScrollTo(int destX,int destY){
int scrollX=getScrollX();
int deltaX=destX-scrollX;
//1000ms 内滑向destX,效果就是慢慢滑动
mScroller.startScroll(scrollX,0,deltaX,0,1000);
invalidate();
}
@Override
public void computeScroll() {
if(mScroller.computeScrollOffset()){
scrollTo(mScroller.getCurrX(),mScroller.getCurrY());
postInvalidate();
}
}
接下来我们再看一下startScroll 方法。
/**
* startX ,startY 表示的是滑动的起点
*
* dx,dy 表示的是要滑动的距离
*
* 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;
}
我们看到startScroll 方法内部只是保留了我们传递的几个参数而已,并没有做滑动操作,那么Scroller是如何实现弹性滑动的呢 ?
其答案就在 startScroll 下面的 invalidate 方法。invalidate 方法导致View重绘,在View的 draw 方法中又回去调用computeScroll 方法,(computeScroll需要我们自己去实现) 而computeScroll 方法又会去向Scroller 获取当前的scrollX 和scrollY ,然后通过scrollTo 方法实现滑动,接着有调用 postInvalidate 方法来进行第二次重绘,这一次重绘的过程和第一次重绘一样,还是会导致computeScroll 方法被调用;然后继续向Scroller 获取当前的scrollX 和 scrollY ,并通过scrollTo 方法滑动到新的位置。View每一次的重绘都会导致View进行小幅度的滑动,而多次的小幅度滑动就组成了弹性滑动,这就是Scroller 的工作机制。如此反复,直到整个滑动过程结束。
接下来我们看一下Scroller 的 computeScrollOffset 方法的实现,如下
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;
......
}
}
}
可以看到,这个方法是根据时间流逝百分比来算出scrollX 和 scrollY 改变的百分比并计算出当前的值。这个过程类似动画中的插值器。 动画相关可以看这里
同样的,可以使用动画来实现View的弹性滑动,代码如下
private void performAnimation(final View target, final int start, final int end) {
ValueAnimator valueAnimator = ValueAnimator.ofInt(1, 100);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
//整型估值器
private IntEvaluator intEvaluator = new IntEvaluator();
@RequiresApi(api = Build.VERSION_CODES.HONEYCOMB_MR1)
@Override
public void onAnimationUpdate(ValueAnimator animation) {
Log.e(TAG, "onAnimationUpdate: 进度:" + animation.getAnimatedValue());
//获取当前进度占整个动画的比例,浮点型 0-1之间
float fraction = animation.getAnimatedFraction();
target.getLayoutParams().width = intEvaluator.evaluate(fraction, start, end);
target.requestLayout();
}
});
valueAnimator.setDuration(5000).start();
}