android之滑动机制实现方式

自定义是进阶路上的一道炒鸡大门槛,要真正学好自定义View,还得学会View的滑动机制,事件分发机制,这些很多时候自定义View的时候都得用上,如果没用上,那只是最简单的自定义方式了。这篇文章单单说下滑动机制,事件分发机制和自定义VIew的实现在之前的文章写过了,如果定制复杂的自定义view,把三者结合一下就行了。

在之前的文章android之自定义View和ViewGroup(五)(代码篇,实现类似竖着的ViewPager引导页,竖向引导页)中用到过View的滑动事件,用的View的滑动辅助类Scroller实现的。如果不懂的看看接下来这篇文章讲的应该就能懂了。

在android中,比如实现一个最简单的滑动——view跟着手指的移动进行滑动。

在书写代码之前,需要了解下坐标系问题。在Android中,有两种坐标系,一种是视图坐标系,像平常用的什么getX,getY获取的坐标就是针对视图坐标系来说的,即相对坐标(相对于控件左上角所定义的坐标);另一种是Android坐标系,getRawX和getRawY获取的坐标就是针对Android坐标系了,即绝对坐标(相对于屏幕左上角所定义的坐标)。其实很好理解,一个是以控件的左上角为参考系,一个是以屏幕的左上角为参考系,如果要获取view在屏幕中的位置,那么不用想肯定得需要用绝对的Android坐标系了(getLocationOnScreen()方法获取View的左上角在Android坐标系中的位置)。

你可千万不要问我getLeft和getRight之类的是相对于什么坐标系的,你要是真问了咋办?

很简单,你再回小学去学学语文吧,阿西吧。相对于坐标系,什么叫坐标系?什么叫获取坐标?你x,y是坐标,难道left,right也是坐标吗?既然不是坐标那就跟什么坐标系没关系了,坐标系是用来获取坐标的时候才需要用。

那么现在我们实现下开始说的最简单的滑动事件,首先我们需要让View跟着我们手指滑动,那么我们肯定得监听View的touch事件,注意不是ViewGroup的touch事件,因为我们如果监听的是它的父布局ViewGroup的touch事件,那么我们根本没法判定何时开始滑动,因为我们滑动前提必须是手指放到View上面开始的,所以我们必须让down事件在View上。

首先我们继承View,然后重写它的onTouchEvent方法获取touch的事件中的坐标:

@Override
    public boolean onTouchEvent(MotionEvent event) {
        int x = (int) event.getX();  //获取x坐标
        int y = (int) event.getY();  //获取y坐标
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
               //当手指触碰view的时候,记下初始位置坐标downX和downY
                downX = x; 
                downY = y;
                break;
            case MotionEvent.ACTION_MOVE:
                //手指移动过程,将手指的实时坐标减去初始坐标就是滑动的距离
                int translationX = x - downX;
                int translationY = y - downY;
                //执行滑动
                method1(translationX, translationY); //方式1
                //method2(translationX, translationY); //方式2
                //method3(translationX, translationY); //方式3
                //method4(translationX, translationY); //方式4
                break;
            case MotionEvent.ACTION_UP:
                //滑动的方式5,这不是跟着手指滑动了,是手指离开屏幕后View返回原位置
                scroller.startScroll(((View) getParent()).getScrollX(), ((View) getParent()).getScrollY(), -((View) getParent()).getScrollX(), -((View) getParent()).getScrollY(), 500);
                invalidate();
                break;
        }
        return true;
    }

    @Override
    public void computeScroll() {  //下面会解释改方法(用于'滑动的方式5'的)
        super.computeScroll();
        if (scroller.computeScrollOffset()) {
            ((View) getParent()).scrollTo(scroller.getCurrX(), scroller.getCurrY());
            invalidate();
        }
    }

第一种滑动的实现方式
使用setLayoutParams动态修改布局,不停得设置控件距离父布局的左边和上边距。

//方式1,使用layoutparams动态修改布局(如果在布局中设置了gravity之类的用params方法会无效)
    private void method1(int translationX, int translationY) {
        LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) getLayoutParams();
        params.leftMargin = getLeft() + translationX;
        params.topMargin = getTop() + translationY;
        setLayoutParams(params);
    }

注意我注释中写的,如果在布局中使用了gravity,那么就不能用这种方式,你看看源码就知道了,每次调用父布局会根据gravity而在View的left和top上加上gravity所计算的距离。

第二种滑动实现方式:
使用view的layout方法重新设置自己的位置。

//方式2,使用view的layout方法重新设置自己的位置
    private void method2(int translationX, int translationY) {
        layout(getLeft() + translationX, getTop() + translationY, getRight() + translationX, getBottom() + translationY);
    }

了解过View绘制过程的都知道,父层会调用onLayout方法来设置子View的位置,而子View会调用layout方法来重绘自己的布局,所以我们可以设置View的边距然后调用它的layout方法来重新对自己布局,这样就可以达到我们的目的。

第三种滑动的实现方式:

//方式3,使用scrollBy方法移动
    private void method3(int translationX, int translationY) {
        ((View) getParent()).scrollBy(-translationX, -translationY);
    }

都知道,系统提供了scrollTo和scrollBy方法来移动View,而scrollTo方法是用于开头和结尾,而scrollBy是用于过程,怎么说呢,意思就是,scrollTo是指从某个位置移动到某个具体的位置,而scrollBy是从某个位置移动多少距离,增量移动,即移动过程需要移动多少距离。举例来说,如果你想移动到(x,y)那么你就用scrollTo方法,因为已经知道需要移动的终点的坐标了;如果你想x方向移动x,y方向移动y,那么你就只能用scrollBy方法了,因为你只知道移动的增量(即移动多少距离),并不知道你的终点,当然如果你计算出终点在哪也可以直接用scrollTo方法了。
而我们是整个过程不停地计算移动了多少距离(即增量),所以我们使用scrollBy方法来实现移动。scrollBy(dx,dy)两个参数分别是你x方向和y方向移动的增量。如果你不懂可能你会问了,为什么会是负的?还有为什么是调用的(View) getParent()).scrollBy(调用的父布局进行scrollBy而不是View进行scrollBy)?如果你不明白,看下面一张图应该就明白了:
这里写图片描述
看上图,首先scrollBy方法我们需要知道一件事,它移动的效果并不是整个都移动,其实它的效果是移动的内容,ViewGroup移动的是子View,View移动的是它的内容。如上图所示:
假设父scrollBy(50,0),看最后的效果,真实效果缺是“子”向左移动了50;这就是因为参考系的原因,可以看成父是屏幕,所以你看位置是看的相对于屏幕的位置,所以你即使可视区域(屏幕)移动了,但是其他是它里面的东西反着移动的。所以使用scrollBy()方法的时候传的移动距离(增量)是相反的。

第四种滑动的实现方式:

//方式4,系统封装的方法
    private void method4(int translationX, int translationY) {
        offsetLeftAndRight(translationX);
        offsetTopAndBottom(translationY);
    }

offsetLeftAndRight和offsetTopAndBottom这两个方法是安卓系统自己封装的方法,直接传入移动距离(增量)就可以移动了。

第五种滑动的实现方式(弹性滑动,也有叫做:平滑移动):
前面4中方式都是滑动过程中利用增量进行滑动的,而第5种方式是终点到起点的滑动,即从结束位置返回初始位置。当然我们也可以直接使用scrollTo的方式,但是效果却是一瞬间的,并不是慢慢移动回去的,要慢慢移动回去(即弹性滑动),那么如果要弹性滑动就需要用到滑动的辅助类Scroller了。毕竟瞬间就回去了,太突然,体验不好,所以才有个了Scroller,其实他相当于是分成了多个短暂短距离滑动,这样一点一点的滑动,就给人一种比较平和的印象了。
Scroller的初始化:

    private Scroller scroller;  //声明

    scroller = new Scroller(context);  //构造方法中进行初始化

滑动方法:

                scroller.startScroll(((View) getParent()).getScrollX(), ((View) getParent()).getScrollY(), -((View) getParent()).getScrollX(), -((View) getParent()).getScrollY(), 500);
                invalidate();

参数分别表示,滑动的起点x和起点y,滑动的距离x和滑动的距离y,持续时间(就跟动画的时间类似)。
通过getParent.getScrollX和getScrollY可以获取子View滑动的距离,由于我们返回去原位置,所以距离值是负的。执行startScroll方法过后需要刷新View,只有刷新重绘View才会去执行下面的computeScroll方法,真正执行滑动的其实是computeScroll中的scollTo方法,startScroll主要是拿来知道需要滑动的距离,滑动的起点,滑动的持续时间,然后通过这些计算每个时间段内滑动多少距离等,comuteScrol获取当前的scrollX和scrollY然后通过scrollTo进行短距离滑动,短距离滑动后又调用invalidate进行重绘,然后又调用computeScroll()又获取当前的scollX和ScollY,然后调用scrollTo滑动到新的位置,这样不断地循环直到滑动结束,scroller.computeScrllOffset如果返回false则代表滑动结束,所以为true的时候就不断地循环滑动。

@Override
    public void computeScroll() {
        super.computeScroll();
        if (scroller.computeScrollOffset()) {
            ((View) getParent()).scrollTo(scroller.getCurrX(), scroller.getCurrY());
            invalidate();
        }
    }

第六种实现方式:
动画,动画这个想必大家都清楚,直接使用平移动画返回当初位置就可以了,补间动画和属性动画都可以,至于动画的实现我就不多说了,会单独写动画的文章来说明动画的使用。

滑动的基本方式就说到这里了,肯定不全,以后会补上,还望轻喷~

总结:前四种方式是跟着手指滑动,后两种方式是返回初始位置。

发布了33 篇原创文章 · 获赞 49 · 访问量 14万+

猜你喜欢

转载自blog.csdn.net/gsw333/article/details/53928413
今日推荐