Android中View的知识体系之基础知识

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/wanliguodu/article/details/81412951

网上有很多写关于Android中view的文章,关于view 的知识比较多也比较杂,每次从网上找到相关文章后,都有新的收获。这次准备写关于view知识体系的文章,也吸取了网上许多优秀的文章的观点和案例并加上自己的理解和感悟,这系列的文章会从view的基础到自定义view。在总结中学习,在学习中总结。
这篇文章主要介绍view的基础知识。

1.View和ViewGroup

Android应用的所有UI组件都是继承了View类,View组件非常类似于Swing编程的JPanel,它表示一个空白的矩形区域。View类还有一个很重要的子类:ViewGroup,但是ViewGroup通常作为其他组件的容器使用。Android的所有UI组件都是建立在View、ViewGroup基础之上的,Android采用了“组合器”模式来设计View和ViewGroup:ViewGroup是View的子类,因此ViewGroup也可以被当做View使用。对于一个Android应用的图形用户界面来说,ViewGroup作为容器来盛装其他组件,而ViewGroup里除了可以包含普通View组件之外,还可以再次包含ViewGroup组件。
这里写图片描述
View和ViewGroup中有许多方法都值得我们去研究,不管是处理view的滑动冲突还是明白view的工作原理以至于我们要自定义view,都起到至关重要的作用。这里只是表明了它们之间的关系,具体到一些重要的方法函数,我会逐个介绍。

2.MotionEvent和TouchSlop

2.1 MotionEvent

当手指触摸屏幕是(View或ViewGroup派生的控件),将产生Touch事件Touch事件的相关细节(触摸发生的位置、事件及怎么的触摸)被封装成MotionEvent对象
在手指触摸屏幕后所产生的一系列Touch事件中,典型的事件类型如下几种:

事件类型 具体动作
MotionEvent.ACTION_DOWN 手指刚接触屏幕
MotionEvent.ACTION_UP 手指从屏幕上松开的一瞬间
MotionEvent.ACTION_MOVE 手指在屏幕上移动
MotionEvent.ACTION_CANCEL 动作取消(非人为)

正常情况下,一次手指触摸屏幕的行为会触发一系列点击事件,一般会有两种情况:
(1)点击屏幕后离开松开,事件序列为 DOWN -> UP;
(2)点击屏幕滑动一会再松开,事件序列为 DOWN -> MOVE ->….-> MOVE -> UP;
在上述典型的三种Tocuh事件,我们可以通过TotionEvent对象得到点击事件发生的x和y的坐标。为此,系统提供了两组方法:getX/getY和getRawX/getRawY。区别如下:
(1)getX/getY:返回的是相对于当前View左上角的 x 和 y 坐标
(2)getRawX/getRawY : 返回的是相对于手机屏幕左上角的 x 和 y 坐标

2.2 TouchSlop

TouchSlop是系统所能识别出的被认为是滑动的最小距离,即当手指在屏幕上滑动时,如果两次滑动之间的距离小于这个常量,那么系统就不认为你是在进行滑动操作。原因是:滑动的距离太短,系统不认为它不是滑动。这是个常量,和设备有关,在不同的设备上这个值是不同的,可通过:ViewConfiguration.get(getContext()).getScaledTouchSlop()来获取这个常量。
这个常量的意义在于:当我们在处理滑动时,可以利用这个常量来做一些过滤,比如当两次滑动事件的滑动距离小于这个值,我们就可以认为未达到滑动距离的临界值,因此就可以认为它们不是滑动,这样做可以有更好的用户体验。

3.手势(Gesture)

所谓手势,其实是指用户手指在触摸屏上的连续碰撞行为,比如在屏幕上从左至右划出的一个动作,就是手势;在比如在屏幕上画出一个圆圈也是手势。手势这种连续的碰撞会形成某个方向上的移动趋势,也会形成一个不规则的几何图形。Android 对于这两种手势行为都提供了支持。
对于第一种手势行为,Android 提供了手势检测,并为手势检测提供了相应的监听器。
对于第二种手势行为,Android 允许开发者添加手势,并提供相应的API识别用户手势。
下面,来介绍手势相关的重要的类。

3.1 VelocityTracker

速度追踪,用于追踪手指在滑动过程中的速度,包括水平和竖直方向的速度,过程也很简单。首先,在 View 的onTouchEvent 方法中追踪当前点击事件的速度。

        VelocityTracker tracker = VelocityTracker.obtain();
        tracker.addMovement(event);

接着,当我们想知道当前的滑动速度时,这个时候可以采用如下方式来获取当前的速度:

        tracker.computeCurrentVelocity(1000);//时间间隔的单位是:毫秒(ms)
        float xVelocity = tracker.getXVelocity();
        float yVelocity = tracker.getYVelocity();

需要注意的是:
(1)获取速度之前必须先计算速度,即 getXVelocitygetYVelocity这两个方法的前面必须要调用 computeCurrentVelocity 方法;
(2)这里的速度是指一段时间内手指所滑动过的像素。
比如讲时间间隔设为 1000ms 时,在 1s 内,手指在水平方向从左向右滑动 100 像素,那么水平速度就是 100。注意速度可以是负数,当手指从右向左滑动时,水平方向速度即为负值。速度的计算公式可以表示如下:

速度 = (终点位置 - 起点位置) / 时间段

3.2 GestureDetector

3.2.1 概述

Android 为手势提供了一个 GestureDetector 类,GestureDetector 实例代表了一个手势检测器,用于辅助检测用户的单击、滑动、长按、双击等行为。GestureDetector内部有3个Listener接口,用来回调不同类型的触摸事件,我们来看看类图:
这里写图片描述
GestureDetector 这个类对外提供了3个接口和一个静态内部类,通过一个表格简单介绍这4个监听器:

监听器 简介
OnGestureListener 手势检测,主要有事件类型为:按下(Down)、快速滑动(Fling)、长按(LongPress)、滚动(Scroll)、触摸反馈(ShowPress)和单击抬起(SingleTapUp)
OnDoubleTapListener 双击检测,有三个回调类型:双击(DoubleTap)、单击确认(SingleTapConfirmed)和双击事件回调(DoubleTapEvent)
OnContextClickListener 这是Android6.0(23)才添加的,用于检测外部设备上的按钮是否按下,一般情况下可忽略
SimpleOnGestureListener 这个是上述三个接口的空实现,一般情况下使用这个比较多,也比较方便

3.2.2 构造函数

GestureDetector中有5中构造函数,其中我们关注其中的2个构造函数即可:

//第一种构造函数
public GestureDetector(Context context, OnGestureListener listener) {
        this(context, listener, null);
    }

//第二种构造函数
public GestureDetector(Context context, OnGestureListener listener, Handler handler) {
        if (handler != null) {
            mHandler = new GestureHandler(handler);
        } else {
            mHandler = new GestureHandler();
        }
        mListener = listener;
        if (listener instanceof OnDoubleTapListener) {
            setOnDoubleTapListener((OnDoubleTapListener) listener);
        }
        if (listener instanceof OnContextClickListener) {
            setContextClickListener((OnContextClickListener) listener);
        }
        init(context);
    }

第一种构造函数里面需要传递两个参数,上下文(Context)和手势监听器(OnGestureListener),这个很好理解,我们经常用的就是这种方式。
第二种构造函数则需要传递的参数多一个Handler。这Handler的作用是为了给 GestureDetector 提供了一个Looper。通常情况下是不需要Handler的,因为它会在内部自动创建一个 Handler 用于处理数据,如果你在主线程中创建 GestureDetector ,那么它内部创建的 Handler 会自动获取主线程的 Looper ,如果你在一个没有创建 Looper 的子线程中创建 GestureDetector 则需要传递一个带有 Looper 的 Handler 给它,否则就会因为无法获取到 Looper 导致创建失败。具体的 Handler 和 Looper 的关系可见 Android的消息机制

第二种构造函数使用方式如下(在子线程中创建 GestureDetector):

// 方式一、在主线程创建 Handler
final Handler handler = new Handler();
new Thread(new Runnable() {
    @Override public void run() {
        final GestureDetector detector = new GestureDetector(MainActivity.this, new
                GestureDetector.SimpleOnGestureListener() , handler);
        // ... 省略其它代码 ...
    }
}).start();

// 方式二、在子线程创建 Handler,并且指定主线程的Looper
new Thread(new Runnable() {
    @Override public void run() {
        final Handler handler = new Handler(Looper.getMainLooper());
        final GestureDetector detector = new GestureDetector(MainActivity.this, new
                GestureDetector.SimpleOnGestureListener() , handler);
        // ... 省略其它代码 ...
    }
}).start();

也可以用其他方式来创建 Handler ,重点是传递的 Handler 一定要用 Looper。

new Thread(new Runnable() {
    @Override public void run() {
        Looper.prepare(); // <- 重点在这里
        final GestureDetector detector = new GestureDetector(MainActivity.this, new
                GestureDetector.SimpleOnGestureListener());
        // ... 省略其它代码 ...
    }
}).start();

这种方式用到了 GestureDetector 的第一种构造函数,在子线程中准备了 Looper 也是可以的。

3.2.3 重要方法详细介绍

方法名 描述 所属接口
onDwon 手指触摸屏幕的一瞬间,由1个ACTION_DOWN触发 OnGestureListener
onShowPress 手指触摸屏幕,尚未松开或拖动,由1个ACTION_DOWN触发。注意:和onDwon的区别,它强调的是没有松开或拖动的状态 OnGestureListener
onSingleTapUp 手指触摸屏幕后松开,伴随着1个ACTION_UP而触发,这是单击行为 OnGestureListener
onScroll 手指按下屏幕并拖动,由一个ACTION_DOWN和多个ACTION_MOVE触发,这是拖动行为 OnGestureListener
onLongPress 用户长久按着屏幕不放,即长按 OnGestureListener
onFling 用户按下触摸屏、快速滑动后松开,由1个ACTION_DOWN、多个ACTION_MOVE和1个ACTION_UP触发,这是快速滑动行为 OnGestureListener
onDoubleTap 双击,有两次连续的单击组成,它不可能和 onSingleTapConfirmed共存 OnDoubleTapListener
onSingleTapConfirmed 严格的单击行为。注意:它和onSingleTapUp的区别,如果触发了onSingleTapConfirmed,那么后面不可能在紧跟着另一个单击行为,即这只可能是单击,而不可能是双击中的一次单击 OnDoubleTapListener
onDoubleTapEvent 表示发生了双击行为,在双击的期间,ACTION_DWON、ACTION_MOVE和ACTION_UP都会触发此回调 OnDoubleTapListener

4.View的坐标系

View 的位置主要由它的四个顶点来决定的,分别对应于 View 的四个属性:top、left、right、bottom。其中:
top是左上角纵坐标,left是左上角横坐标,right是右下角横坐标,bottom是右下角纵坐标。
需要注意的是:这些坐标都是相当于 View 的父容器来说的,这是一种相对坐标。View 的坐标和父容器的关系如下:
这里写图片描述
下面这张图是 父 View移除屏幕后子 View 的getLef()的示意图:
这里写图片描述

4.1静态坐标系

View坐标 参数含义
left = getLeft() View自身左侧到父View左侧的距离
top = getTop() View自身顶部到父View顶部的距离
right = getRight() View自身右侧到父View左侧的距离
botton = getBotton() View自身底部到父View顶部的距离
getTranslationX() View左上角相对父View的X轴偏移量
getTranslationY() View左上角相对父View的Y轴偏移量
getX() 其值为:getLeft()+getTranslationX(),当setTranslationX 时,getLeft()不会变,getX会变
getY() 其值为:getTop()+getTranslationY(),当setTranslationY 时,getTop()不会变,getY会变

MationEvent 触摸事件坐标:

MotionEvent坐标方法 参数含义
getX() 当前触摸点距离当前 View 自身左边的距离
getY() 当前触摸点距离当前 View 自身顶部的距离
getRawX() 当前触摸点距离屏幕左边的距离(Android绝对坐标系)
getRawY() 当前触摸点距离屏幕顶部的距离(Android绝对坐标系)

4.2 滑动坐标系

关于View提供的与坐标息息相关的另一组常用的重要方法就是滚动或者滑动相关的方法,为了实现 View 的滑动,View 提供了专门的方法来实现这个功能,那就是 scrollToscrollBy,我们先来看看这两个方法的源码:

 /**
     * 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 内部的两个属性 mScrollXmScrollY 的改变规则,这两个属性可以通过 getScrollXgetScrollY 方法分别获取到。简单概括一下,在滑动过程中,mScrollX 的值总是等于 View 左边缘View 内容左边缘在水平方向的距离,而 mScrollY 的值总是等于View 上边缘View 内容上边缘在竖直方向的距离。View 边缘是指 View 的位置,由四个顶点组成,而 View 内容边缘是指 View 中的内容的边缘。 scrollTo 和 scrollBy 只能改变 View 内容的位置而不能改变 View 在布局中的位置。 mScrollX 和 mScrollY 单位是像素,并且当 View 左边缘在 View 内容左边缘的右侧是,mScrollX 为正值,反之为负值;当 View 上边缘在 View 内容上边缘的下边时,mScrollY 为正值,反之为负值。换句话说,如果从左向右滑动,那么 mScrollX 为负值,反之为正值;如果从上往下滑动,那么 mScrollY 为负值,反之为正值。

下面我们先来看看关于滑动的坐标系:
这里写图片描述

用一张表格具体看看相关参数的含义:

View滑动坐标 参数含义
offsetLeftAndRight(int offset) 水平方向挪动View,offset 为正则向x轴正向移动,移动的是整个View,getLeft()会变,自定义view很有用
offsetTopAndBottom(int offset) 垂直方向挪动View,offset为正则向y轴正向移动,移动的是整个View,getTop()会变,自定义View很有用
scrollTo(int x,int y) View内容(不是整个View)滑动到相应的位置,参考坐标原点为ParentView左上角,x,y为正则向x,y轴反向移动,反之为负
scrollBy(int x,int y) 在scrollTo()的基础上继续滑动x,y
setScrollX(int value) 实质为scrollTo(),只是改变x轴滑动
setScrollY(int value) 实质为scrollTo(),只是改变y轴滑动
getScrollX()/getScrollY() 获得当前滑动位置偏移量。分别对应着mScrollX,mScrollY。

5.View的滑动

在Android设备上,滑动几乎是应用的标配,不管是下拉刷新还是 SlidingMenu ,它们的基础都是滑动。从另一方面来说,Android 手机由于屏幕比较小,为了给用户呈现更多内容,就需要使用滑动来隐藏和显示一些内容。滑动在 Android 开发中具有很重要的作用,不管一些滑动效果多么酷炫,归根到底,它们都是由不同的滑动外加一些特效所组成的。在了解了 Android 坐标系和触控事件后,我们开看看如何使用系统提供的 API 来实现动态修改一个 View 的坐标,即实现滑动效果。而不管采用哪种方式,是实现的思想基本是一致的,当触摸 View 时,系统记下当前触摸点坐标;当手指移动时,系统记下移动后的触摸点坐标,从而获取到相对于前一次坐标点的偏移量,并通过偏移量来修改 View 的坐标,这样不断重复,从而实现滑动过程。

5.1 scrollTo与scrollBy

为了实现 View 的滑动,View 提供了专门的方法来实现这个功能,那就是 scrollTo 和 scrollBy 。实际上在 4.2滑动坐标系 中大体介绍的了相关内容。这里我们详细说明一下。

  1. scrollTo(int x,int y):
    如果偏移位置发生了改变,就会给 mScrollX 和 mScrollY 赋新值,mScrollX表示里视图起始位置的x水平放的偏移量(getScrollX()可以获取到),mScrollY 表示离视图起始位置的y垂直方向的偏移量(getScrollY()可以获取到)。改变当前位置。

    注意:x , y 代表的不是坐标点,而是偏移量

  2. scrollBy(int x,int y):
    从源码中看出,它实际上是调用 了 scrollTo(mScrollX + x,mScrollY + y);

    注意:mScrollX + x 和 mScrollY + y ,即代表在原先偏移的基础上再发生偏移,通俗的讲就是相对我们当前位置的偏移。

scrollTo、scrollBy 方法移动的都是 View 的 content,即让 View 的内容移动。如果在 ViewGroup 中使用 scrollTo 、scrollBy 方法,那么移动的将是所有子 View ;但如果在 View 中使用,那么移动的将是 View 的内容。例如TextView ,content 就是它的文本,ImageView,content 就是它的 drawable 对象。
如果我们想拖动整个 View,这两个方法可以用吗?其实我们可以在该 View 所在的 ViewGroup 中来调用 scrollTo和scrollBy 方法,已达到来移动 ViewGroup 子 View 的效果。代码如下:

((View) getParent()).scrollBy(offsetX, offsetY);

让自定义的 View 跟随手指移动的核心代码为:

    // 分别记录上次滑动的坐标
    private int mLastX = 0;
    private int mLastY = 0;

    @Override
    public boolean onTouchEvent(MotionEvent event) {

        int rawX = (int)event.getRawX();
        int rawY = (int)event.getRawY();

        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                Log.i("MotionEvent","MotionEvent.ACTION_DOWN");
                break;
            case MotionEvent.ACTION_MOVE:
                Log.i("MotionEvent","MotionEvent.ACTION_MOVE");
                int offsetX = rawX - mLastX;
                int offsetY = rawY - mLastY;
                //View内容移动
                scrollBy(-offsetX,-offsetY);
                //View整体移动
                //((View) getParent()).scrollBy(offsetX, offsetY);
                break;
            case MotionEvent.ACTION_UP:
                Log.i("MotionEvent","MotionEvent.ACTION_UP");
                break;
        }
        mLastX = rawX;
        mLastY = rawY;
       return super.onTouchEvent(event);
    }

5.2 Scroller

使用scrollTo()scrollBy()来实现 View 的滑动的时候并不多,因为这两个方法产生的滑动时不连贯的,跳跃的,最终的效果也不够平滑。所以,Android提供了Scroller这个类来实现平滑的滑动。下面的也是让自定义的 View 跟随手指移动的核心代码为:

//初始化Scroller
Scroller scroller = new Scroller(context);

// 分别记录上次滑动的坐标
 private int mLastX = 0;
 private int mLastY = 0;

    @Override
    public boolean onTouchEvent(MotionEvent event) {

        int rawX = (int)event.getRawX();
        int rawY = (int)event.getRawY();

        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                Log.i("MotionEvent","MotionEvent.ACTION_DOWN");
                break;
            case MotionEvent.ACTION_MOVE:
                Log.i("MotionEvent","MotionEvent.ACTION_MOVE");
                int offsetX = rawX - mLastX;
                int offsetY = rawY - mLastY;

                smoothScrollTo(-offsetX,-offsetY);
                break;
            case MotionEvent.ACTION_UP:
                Log.i("MotionEvent","MotionEvent.ACTION_UP");
                break;
        }
        mLastX = rawX;
        mLastY = rawY;
       return super.onTouchEvent(event);
    }

 private void smoothScrollTo(int destX,int  destY){
        scroller.startScroll(0,0,destX,destY,1000);
        invalidate();
 }

 @Override
 public void computeScroll() {
        // 判断Scroller是否执行完毕
        if(scroller.computeScrollOffset()){
             //使整个控件都滑动
            ((View)getParent()).scrollBy(scroller.getCurrX(),scroller.getCurrY());
            // 通过重绘来不断调用computeScroll
            invalidate();
        }
 }

startScroll()来开启平滑移动过程,前两个参数的意思是起始坐标,接下来的两个坐标是偏移量,最后一个是显示的时长。Scroller类提供了 computeScrollOffset()方法来判断是否完成了滑动,同时也提供了 getCurrX()getCurrY()来获取当前滑动坐标。需要注意的是,invalidate()方法,因为只能在computeScroll()方法中获取滑动过程中的scrollX和scrollY的坐标。但是computeScroll()方法是不会自动调用的,只能通过 invalidate() –>draw() –> computeScroll() 来间接调用computeScroll()方法,所以,需要在上述代码中调用 invalidate(),实现循环获取 scrollX和scrollY的目的。当滑动结束后,scroller.computeScrollOffset()方法会返回false,从而实现整个平滑移动的过程。

5.3 layout

在View进行绘制时,会调用 onLayout()方法来设置显示的位置。当热,可以通过修改View的 left,top,right,bottom 四个属性来控制View 的坐标。在onTouchEvent()方法时调用onLayout()方法。下面的也是让自定义的 View 跟随手指移动的核心代码为:


// 分别记录上次滑动的坐标
 private int mLastX = 0;
 private int mLastY = 0;

    @Override
    public boolean onTouchEvent(MotionEvent event) {

        int rawX = (int)event.getRawX();
        int rawY = (int)event.getRawY();

        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                Log.i("MotionEvent","MotionEvent.ACTION_DOWN");
                break;
            case MotionEvent.ACTION_MOVE:
                Log.i("MotionEvent","MotionEvent.ACTION_MOVE");
                int offsetX = rawX - mLastX;
                int offsetY = rawY - mLastY;

                layout(getLeft()+offsetX,
                        getTop()+offsetY,
                        getRight()+offsetX,
                        getBottom()+offsetY);
                break;
            case MotionEvent.ACTION_UP:
                Log.i("MotionEvent","MotionEvent.ACTION_UP");
                break;
        }
        mLastX = rawX;
        mLastY = rawY;
       return super.onTouchEvent(event);
    }

5.4 offsetLeftAndRight 和 offsetTopAndBottom

这个方法相当于系统提供的一个对上下、左右移动的封装。当计算出偏移量后,只要使用如下的自定义View 也可以跟随手指移动:


// 分别记录上次滑动的坐标
 private int mLastX = 0;
 private int mLastY = 0;

    @Override
    public boolean onTouchEvent(MotionEvent event) {

        int rawX = (int)event.getRawX();
        int rawY = (int)event.getRawY();

        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                Log.i("MotionEvent","MotionEvent.ACTION_DOWN");
                break;
            case MotionEvent.ACTION_MOVE:
                Log.i("MotionEvent","MotionEvent.ACTION_MOVE");
                int offsetX = rawX - mLastX;
                int offsetY = rawY - mLastY;

                // 同时对left和right进行偏移
                offsetLeftAndRight(offsetX);
                // 同时对top和bottom进行偏移
                offsetTopAndBottom(offsetY);
                break;
            case MotionEvent.ACTION_UP:
                Log.i("MotionEvent","MotionEvent.ACTION_UP");
                break;
        }
        mLastX = rawX;
        mLastY = rawY;
       return super.onTouchEvent(event);
    }

5.5 LayoutParams

LayoutParams保存了一个View的布局参数。因此可以在程序中,通过改变LayoutParams来动态地修改一个布局的位置参数,从而达到改变View位置的效果。我们可以很方便地在程序中使用getLayoutParams()来获取一个View的LayouParams。当然,计算偏移量的方法与在Layout方法中计算offset也是一样。当获取到偏移量之后,就可以通过setLayoutParams来改变其LayoutParams,代码如下所示。

// 分别记录上次滑动的坐标
 private int mLastX = 0;
 private int mLastY = 0;

    @Override
    public boolean onTouchEvent(MotionEvent event) {

        int rawX = (int)event.getRawX();
        int rawY = (int)event.getRawY();

        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                Log.i("MotionEvent","MotionEvent.ACTION_DOWN");
                break;
            case MotionEvent.ACTION_MOVE:
                Log.i("MotionEvent","MotionEvent.ACTION_MOVE");
                int offsetX = rawX - mLastX;
                int offsetY = rawY - mLastY;

//ConstraintLayout.LayoutParams layoutParams = (ConstraintLayout.LayoutParams) getLayoutParams();①
//ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams) getLayoutParams();②

                LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) getLayoutParams();
                layoutParams.leftMargin = getLeft() + offsetX;
                layoutParams.topMargin = getTop() + offsetY;
                Log.i("MotionEvent","MotionEvent.ACTION_MOVE leftMargin:"+(getLeft() + offsetX));
                Log.i("MotionEvent","MotionEvent.ACTION_MOVE topMargin:"+(getTop() + offsetY));
                setLayoutParams(layoutParams);
                break;
            case MotionEvent.ACTION_UP:
                Log.i("MotionEvent","MotionEvent.ACTION_UP");
                break;
        }
        mLastX = rawX;
        mLastY = rawY;
       return super.onTouchEvent(event);
    }

通过getLayoutParams()获取LayoutParams时,需要根据View所在父布局的类型来设置不同的类型,比如这里将View放在LinearLayout中,那么就可以使用LinearLayout.LayoutParams。类似地,如果在RelativeLayout中,就要使用RelativeLayout.LayoutParams。当然,这一切的前提是你必须要有一个父布局,不然系统不法获取LayoutParams。不过这里需要注意的是,①和②两种方式的布局不能移动自定义View。正在解决这个问题…..

5.6 其他方式

包括使用延时策略使用动画ViewDragHelper ,这几种方式我会单独再分开来总结一下。

站在巨人的肩膀上:
Android开发艺术探究——任玉刚
View坐标系
安卓自定义View进阶-手势检测(GestureDecetor)
实现滑动的七种方法(Android群英传)

猜你喜欢

转载自blog.csdn.net/wanliguodu/article/details/81412951