Android View事件体系汇总其二

上一篇文章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事件体系汇总其三​​​​​​​

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值