Android中View的知识体系——(2)View的滑动

作者:黑衣侠客


一、前言

在Android设备上,滑动几乎是应用的标配,不论是下拉刷新还是SlidingMenu,它们的基础都是滑动。所谓滑动效果,归根结底它们都是由不同的滑动外加一些特效所组成的。因此,掌握滑动的方法是实现绚丽自定义控件的基础。通常通过三种方法来实现View的滑动:
  • 通过View本身提供的scrollTo/scrollBy方法来实现滑动
  • 通过动画给View施加平移效果来实现滑动
  • 通过改变View的LayoutParams使得View重新布局从而实现滑动
下面我们就来一一分析学习。

二、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则是实现了基于所传递参数的绝对滑动,这个不难理解。重点是我们要明白mScrollX和mScrollY的改变规则:

这两个属性了可以通过getScrollX和getScrollY方法分别得到,在滑动过程中,mScrollX的值总是等于View左边缘和View内容左边缘在水平方向的距离,而mScrollY的值总是等于View上边缘和View内容上边缘在竖直方向的距离

View边缘是指View的位置,由四个顶点组成,而View内容边缘是指View中的内容的边缘,scrollTo和scrollBy只能改变View内容的位置而不能改变View在布局中的位置。mScrollX和mScrollY的单位为像素,当View左边缘在View内容左边缘右边时,mScrollX为正值,反之为负值;当View上边缘在View内容上边缘的下边时,mScrollY为正值,反之为负值。换句话说:当从左向右滑动时,mScrollX为负值,反之,为正值。当从上往下滑动时,mScrollY为负值,反之,为正值。

Android中View的知识体系——(2)View的滑动_第1张图片

2.使用动画

通过动画,我们能够让一个View进行平移,而平移就是一种滑动,使用动画来移动View,主要是操作View的translationX和translationY属性,既可以采用传统的View动画,也可以采用属性动画,如果采用属性动画的话,为了能够兼容3.0以下的版本,需要采用开源动画库nineoldandroids(http://nineoldandroids.com/)。
<?xml version="1.0" encoding="utf-8"?><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>

属性动画:

 ObjectAnimator.ofFloat(targetView,"translationX",0,100).setDuration(100).start();

作用:将一个View在100ms内从原始位置向右平移100像素
关于动画的详细介绍请参考:

注意:

View动画是对View的影像做操作,它并不能真正改变View的位置参数,包括宽/高,并且如果希望动画后的状态保留下来,还必须将fillAfter的属性设置为true,否则,动画效果会消失


比如:

  • fillAfter为false,那么在动画完成的一刹那,View会瞬间恢复到动画前的状态
  • fillAfter为true,在动画完成后,View会停留在距原始位置100像素的右边

使用属性动画并不会出现上述问题,但是在Android3.0以下无法使用属性动画,这时我们可以使用动画兼容库nineoldandroids来实现属性动画,尽管如此,Android3.0以下的手机上通过nineoldandroids来实现的属性动画本质上仍是View动画。

但是现在还存在一个问题:

假如我们通过View动画将一个Button向右移动100px,并且这个View设置有点击事件,然后你会发现,点击新位置无法触发onClick事件,而点击原位置仍然可以触发onClick事件,尽管Button已经不再原位置了。在系统眼里,这个Button没有发生任何变化,而新位置只是Button的影像而已

3.改变布局参数

改变布局参数,即改变LayoutParams

比如:我们想把一个Button向右平移100px,我们只需将这个Button的LayoutParams里的marginLeft参数的值增加100px即可

MarginLayoutParams params = (MarginLayoutparamsmButton1.getLayoutParams();params.width + = 100;params.leftMargin + = 100;mButton1.requestLayout();//或者 mButton1.setLayoutParams(params);

同时,还有一种方法:

  • 在将要移动的View前面,设置一个空的,默认宽度为0的View,若想要平均移动View,只需要设置空View的宽度即可
改变LayoutParams的方式去实现View的滑动是一种很灵活的方法,需要根据不同的情况去做不同的处理

4.各类滑动的区别

  • scrollTo/scrollBy:它是View提供的原生方法,其作用是专门用于View的滑动,它可以比较方便的实现滑动效果,并且不影响内部元素的单击事件。但是他的缺点在于:它只能滑动View的内容,并不能滑动View本身。
  • 动画:在Android3.0以上并采用属性动画,那么采用这种方式没有明显的缺点;如果是使用View动画或者在Android3.0以下使用属性动画,均不能改变View本身的属性。在实际使用中,如果动画元素不需要响应用户的交互,那么使用动画来做滑动是比较合适的,反之,就不大合适。但是动画有一个明显的优点,那就是,一些复杂的效果必须要通过动画才能实现
  • 改变布局:除了操作稍微复杂,也没有什么明显的缺点。它主要适用的对象是一些具有交互性的View,因为这些View需要和用户交互,直接通过动画实现会有很大的问题,这时,我们可以使用直接改变布局参数的方式去实现
总结:
  • scrollTo/scrollBy:操作简单,适合对View内容的滑动;
  • 动画:操作简单,主要适用于没有交互的View和实现复杂的动画效果;
  • 改变布局参数:操作稍微复杂,适用于有交互的View;6

三、弹性滑动

所有滑动都有一个共同的思想:

将一次大的滑动分成若干次小的滑动并在一个时间段内完成,弹性滑动的具体实现方法有很多,比如通过Scroller、Handler#postDelayed以及Thread#sleep等。

1.使用Scroller

Scroller源码:

Scroller scroller = new Scroller(mContext);//缓慢滚动到指定位置private void smoothScrollTo(int destX,int destY){int scrollerX = getScrollX();int deltaX = destX - scrollX;//1000ms内滑向destX,效果就是慢慢滑动mScroller.startScroll(scrollX,0,deltaX,0,1000);invalidate();}@Overridepublic void computeScroll(){if(mScroller.computeScrollOffset()){scrollTo(mScroller.getCurrX(),mScroller.getCurrY());postInvalidate();}}

上面是Scroller的典型使用方法

startScroll方法:
public void startScroller(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;}
startX和startY表示的是滑动的起点,dx和dy表示的是要滑动的距离,而duration表示的是滑动的时间,即整个滑动过程完成所需要的时间,注意:这里的滑动指的是View内容的滑动,而非是View本身位置的改变
但是,仅仅调用startScroll方法是无法让View滑动的,因为它内部并没有做滑动相关的事。
接下来我们说一下Scroller究竟是如何让View滑动的:

原因在于startScroll方法下的invalidate方法。
invalidate方法会导致View重绘,在View的draw方法中又会去调用computeScroll方法,computeScroll方法在View中是一个空实现,因此需要我们自己去实现,上面的代码已经实现了computeScroll方法。也正是这个computeScroll方法,View才能实现弹性滑动。

当View重绘后,会在draw方法中调用computeScroll,而computeScroll又会去向Scroller获取当前的scrollX和scrollY;然后通过scrollTo方法实现滑动;接着又调用postInvalidate方法被调用;然后继续向Scroller获取当前的scrollX和scrollY,并通过scrollTo方法滑动到新位置,如此反复,直到整个滑动过程结束。

Scroller的computeScrollOffset方法:

public boolean computeScrollOffset(){...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;... }}return true;}

这个方法会根据时间的流逝来计算出当前的scrollX和 scrollY的值。
根据时间流逝的百分比来算出scrollX和scrollY改变的百分比并计算出当前的值,这个过程类似于动画中的插值器的概念。方法的返回值也很重要,它返回true时,表示滑动还未结束,返回false则表示滑动已经结束,因此,当方法返回true时,我们还要继续进行View的滑动。

总结:

Scroller本身并不能实现View的滑动,它需要配合View的computeScroll方法才能完成弹性滑动的效果,它不断地让View重绘,而每一次重绘距滑动起始时间有一个时间间隔,通过这个时间间隔Scroller就可以得出View当前的滑动位置,知道了滑动位置就可以通过scrollTo方法来完成View的滑动,就这样,View的每一次重绘都会导致View进行小幅度的滑动,而多次的小幅度滑动就组成了弹性滑动,这就是Scroller的工作机制。

  • 由此可见,Scroller的设计思想是多么值得称赞,整个过程中它对View没有丝毫的引用,甚至在它内部连计时器都没有。

2.通过动画

动画本身就是一种渐进的过程,因此通过它来实现的滑动,天然就具有弹性效果,例如:
//让一个View的内容在100ms内向左移动100像素ObjectAnimator.ofFloat(targetView,"translationX",0,100).setDuration(100).start();
拿scrollTo来说,我们也想模仿Scroller来实现View的弹性滑动,利用动画的特性:
final int startX = 0;final int deltaX = 100;ValueAnimator animator = ValueAnimator.ofInt(0,1).setDuration(1000);animator.addUpdateListener(new AnimatorUpdateListener)(){@Overridepublic void onAnimationUpdate(ValueAnimator animator){float fraction = animator.getAnimatedFraction();mButton1.scrollTo(startX + (int) (deltaX * fraction),0);}});animator.start();
我们的动画本质上没有作用于任何对象上,它只是在1000ms内完成了整个动画过程。

利用这个特性,我们就可以在动画的每一帧到来时获取动画完成的比例,然后再根据这个比例计算出当前View所要滑动的距离。注意,这里的滑动针对的是View的内容而非是View本身。可以发现,这个方法的思想其实和Scroller比较类似,都是通过改变一个百分比配合scrollTo方法来完成View的滑动。

  • 需要说明的是:采用这种方法除了能够完成弹性滑动以外,还可以实现其他动画效果,我们完全可以在onAnimationUpdate方法中加上我们想要的其他操作。

3.延时策略

使用延时策略实现弹性滑动

核心思想:

通过发送一系列延时消息从而达到一种渐进式的效果,具体说可以使用Handler或View的postDelayed方法,也可以使用线程的sleep方法。对于postDelayed方法来说,我们可以通过它来延时发送一个消息,然后在消息中来进行View的滑动,如果接连不断地发送这种延时消息,那么就可以实现弹性滑动的效果,对于sleep方法来说,通过在while循环中不断地滑动View和sleep,就可以实现弹性滑动的效果。

下面采用Handler做个示例:

在1000ms内将View的内容向左移动了100个像素
之所以说1000ms内,是因为采用这种方式无法精确地定时,原因是系统的消息调度也是需要时间的,并且所需时间不定。

private static final int MESSAGE_SCROLL_TO = 1;private static final int FRAME_COUNT = 30;private static final int DELAYED_TIME = 33;private int mCount = 0;@SuppressLint("HandlerLeak")private Handler mHandler = new Handler() {    public void handleMessage(Message msg){        switch(msg.what){            case MESSAGE_SCROLL_TO: {                mCount++;                if(mCount <= FRAME_COUNT){                    float fraction = mCount / (float) FRAME_COUNT;                    int scrollX = (int) (fraction * 100);                    mButton1.scrollTo(scrollX,0);                    mHandler.sendEmptyMessageDelayed(MESSAGE_SCROLL_TO,DELAYED_TIME);                }                break;            }            default:                break;        }    };};

感谢收看

更多相关文章

  1. Systrace 分析性能工具使用方法详解
  2. 简单的Android ROM制作方法:创建刷机包 备份ROM
  3. Android属性动画实战教程中篇
  4. Android原生Switch控件滑块thumb卡住问题的解决方法
  5. Android 静默安装和智能安装的实现方法
  6. Android中Handler的使用方法——在子线程中更新界面
  7. Android网络编程之通过Get方法实现
  8. android采用MVP漫画APP、适配刘海屏、小黄车主界面、录音波浪动
  9. 自定义RadioButton样式并去除默认样式位置【Android】

随机推荐

  1. Android中Task、Activity、BackStack(栈)的
  2. CSDN日报190904:Android(安卓)10正式版发
  3. 对Android系统权限的认识(包含如何获得roo
  4. Android之使用SAX方式解析XML文件
  5. Android EditText example
  6. 转:获取android联系人信息
  7. Android的文件存储(写入和读取)
  8. Android 监听wifi广播的两种方式
  9. Android UI - ListView下拉刷新的实现
  10. android Shape Drawable创建两边半圆的按