zoukankan      html  css  js  c++  java
  • 自定义View系列教程01--常用工具介绍

    站在源码的肩膀上全解Scroller工作机制


    Android多分辨率适配框架(1)— 核心基础
    Android多分辨率适配框架(2)— 原理剖析
    Android多分辨率适配框架(3)— 使用指南


    自定义View系列教程00–推翻自己和过往,重学自定义View
    自定义View系列教程01–常用工具介绍
    自定义View系列教程02–onMeasure源码详尽分析
    自定义View系列教程03–onLayout源码详尽分析
    自定义View系列教程04–Draw源码分析及其实践
    自定义View系列教程05–示例分析
    自定义View系列教程06–详解View的Touch事件处理
    自定义View系列教程07–详解ViewGroup分发Touch事件
    自定义View系列教程08–滑动冲突的产生及其处理


    PS:如果觉得文章太长,那就直接看视频


    在自定义View的时候,常常会用到一些Android系统提供的工具。这些工具封装了我们经常会用到的方法,比如拖拽View,计算滑动速度,View的滚动,手势处理等等。如果我们自己去实现这些方法会比较繁琐,而且容易出一些bug。所以,作为自定义View系列教程的开端,先介绍一下这些常用的工具,以便在后续的学习和工作中使用。

    • Configuration
    • ViewConfiguration
    • GestureDetector
    • VelocityTracker
    • Scroller
    • ViewDragHelper

    嗯哼,它们都已经躺在这里了,我们就来挨个瞅瞅


    Configuration

    This class describes all device configuration information that can
    impact the resources the application retrieves.

    Configuration用来描述设备的配置信息。
    比如用户的配置信息:locale和scaling等等
    比如设备的相关信息:输入模式,屏幕大小, 屏幕方向等等
    我们经常采用如下方式来获取需要的相关信息:

    Configuration configuration=getResources().getConfiguration();
    //获取国家码
    int countryCode=configuration.mcc;
    //获取网络码
    int networkCode=configuration.mnc;
    //判断横竖屏
    if(configuration.orientation==Configuration.ORIENTATION_PORTRAIT){
    
       } else {
    
    }

    ViewConfiguration
    看完Configuration再来瞅ViewConfiguration。这两者的名字有些像,差了一个View;咋一看,还以为它俩是继承关系,其实不然。
    官方对于ViewConfiguration的描述是:

    Contains methods to standard constants used in the UI for timeouts,
    sizes, and distances.

    ViewConfiguration提供了一些自定义控件用到的标准常量,比如尺寸大小,滑动距离,敏感度等等。
    可以利用ViewConfiguration的静态方法获取一个实例

    ViewConfiguration viewConfiguration=ViewConfiguration.get(context);

    在此介绍ViewConfiguration的几个对象方法。

    ViewConfiguration  viewConfiguration=ViewConfiguration.get(context);
    //获取touchSlop。该值表示系统所能识别出的被认为是滑动的最小距离
    int touchSlop = viewConfiguration.getScaledTouchSlop();
    //获取Fling速度的最小值和最大值
    int minimumVelocity = viewConfiguration.getScaledMinimumFlingVelocity();
    int maximumVelocity = viewConfiguration.getScaledMaximumFlingVelocity();
    //判断是否有物理按键
    boolean isHavePermanentMenuKey=viewConfiguration.hasPermanentMenuKey();

    ViewConfiguration还提供了一些非常有用的静态方法,比如:

    //双击间隔时间.在该时间内是双击,否则是单击
    int doubleTapTimeout=ViewConfiguration.getDoubleTapTimeout();
    //按住状态转变为长按状态需要的时间
    int longPressTimeout=ViewConfiguration.getLongPressTimeout();
    //重复按键的时间
    int keyRepeatTimeout=ViewConfiguration.getKeyRepeatTimeout();

    GestureDetector
    大家都知道,我们可以在onTouchEvent()中自己处理手势。其实Android系统也给我们提供了一个手势处理的工具,这就是GestureDetector手势监听类。利用GestureDetector可以简化许多操作,轻松实现一些常用的功能。
    嗯哼,来吧,一起瞅瞅它是怎么使用的。

    第一步:实现OnGestureListener

    private class GestureListenerImpl implements GestureDetector.OnGestureListener {
            //触摸屏幕时均会调用该方法
            @Override
            public boolean onDown(MotionEvent e) {
                System.out.println("---> 手势中的onDown方法");
                return false;
            }
    
            //手指在屏幕上拖动时会调用该方法
            @Override
            public boolean onFling(MotionEvent e1,MotionEvent e2, float velocityX,float velocityY) {
                System.out.println("---> 手势中的onFling方法");
                return false;
            }
    
            //手指长按屏幕时均会调用该方法
            @Override
            public void onLongPress(MotionEvent e) {
                System.out.println("---> 手势中的onLongPress方法");
            }
    
            //手指在屏幕上滚动时会调用该方法
            @Override
            public boolean onScroll(MotionEvent e1,MotionEvent e2, float distanceX,float distanceY) {
                System.out.println("---> 手势中的onScroll方法");
                return false;
            }
    
            //手指在屏幕上按下,且未移动和松开时调用该方法
            @Override
            public void onShowPress(MotionEvent e) {
                System.out.println("---> 手势中的onShowPress方法");
            }
    
            //轻击屏幕时调用该方法
            @Override
            public boolean onSingleTapUp(MotionEvent e) {
                System.out.println("---> 手势中的onSingleTapUp方法");
                return false;
            }
        }

    第二步:生成GestureDetector对象

    GestureDetector gestureDetector = new GestureDetector(context,new
    GestureListenerImpl());

    这里的GestureListenerImpl就是GestureListener监听器的实现。

    第三步:将Touch事件交给GestureDetector处理
    比如将Activity的Touch事件交给GestureDetector处理

    @Override  
    public boolean onTouchEvent(MotionEvent event) {  
         return mGestureDetector.onTouchEvent(event);  
    } 

    比如将View的Touch事件交给GestureDetector处理

    mButton=(Button) findViewById(R.id.button);  
    mButton.setOnTouchListener(new OnTouchListener() {            
       @Override  
       public boolean onTouch(View arg0, MotionEvent event) {  
              return mGestureDetector.onTouchEvent(event);  
          }  
    });  

    VelocityTracker
    这个玩意儿一看名字,大概就可以猜到意思了。嗯哼,速度追踪。
    VelocityTracker用于跟踪触摸屏事件(比如,Flinging及其他Gestures手势事件等)的速率。
    简单说一下它的常用套路。

    第一步:开始速度追踪

    private void startVelocityTracker(MotionEvent event) {  
        if (mVelocityTracker == null) {  
             mVelocityTracker = VelocityTracker.obtain();  
         }  
         mVelocityTracker.addMovement(event);  
    }  

    在这里我们初始化VelocityTracker,并且把要追踪的MotionEvent注册到VelocityTracker的监听中。

    第二步:获取追踪到的速度

    private int getScrollVelocity() {  
         // 设置VelocityTracker单位.1000表示1秒时间内运动的像素  
         mVelocityTracker.computeCurrentVelocity(1000);  
         // 获取在1秒内X方向所滑动像素值  
         int xVelocity = (int) mVelocityTracker.getXVelocity();  
         return Math.abs(xVelocity);  
        } 

    同理可以获取1秒内Y方向所滑动像素值

    第三步:解除速度追踪

    private void stopVelocityTracker() {  
          if (mVelocityTracker != null) {  
              mVelocityTracker.recycle();  
              mVelocityTracker = null;  
          }  
    }  

    以上就是VelocityTracker的常用使用方式。


    Scroller

    Scroller挺常见的,用的比较多了。在此只强调几个重要的问题,别的就不再赘述了。

    第一点:scrollTo()和scrollBy()的关系
    先看scrollBy( )的源码

    public void scrollBy(int x, int y) {   
           scrollTo(mScrollX + x, mScrollY + y);   
    } 

    这就是说scrollBy( )调用了scrollTo( ),最终起作用的是scrollTo( )方法。

    第二点:scroll的本质
    scrollTo( )和scrollBy( )移动的只是View的内容,而且View的背景是不移动的。

    第三点:scrollTo( )和scrollBy( )方法的坐标说明

    比如我们对于一个TextView调用scrollTo(0,25) ;那么该TextView中的content(比如显示的文字:Hello)会怎么移动呢?
    向下移动25个单位?不!恰好相反!!这是为什么呢?
    因为调用该方法会导致视图重绘,即会调用

    public void invalidate(int l, int t, int r, int b)

    此处的l,t,r,b四个参数就表示View原来的坐标.
    在该方法中最终会调用:

    tmpr.set(l - scrollX, t - scrollY, r - scrollX, b - scrollY);
    p.invalidateChild(this, tmpr);

    其中tmpr是一个Rect,this是原来的View;通过这两行代码就把View在一个Rect中重绘。
    请注意第一行代码:
    原来的l和r均减去了scrollX
    原来的t和b均减去了scrollY
    就是说scrollX如果是正值,那么重绘后的View的宽度反而减少了;反之同理
    就是说scrollY如果是正值,那么重绘后的View的高度反而减少了;反之同理
    所以,TextView调用scrollTo(0,25)和我们的理解相反

    scrollBy(int x,int y)方法与上类似,不再多说了.


    ViewDragHelper
    在项目中很多场景需要用户手指拖动其内部的某个View,此时就需要在onInterceptTouchEvent()和onTouchEvent()这两个方法中写不少逻辑了,比如处理:拖拽移动,越界,多手指的按下,加速度检测等等。
    ViewDragHelper可以极大的帮我们简化类似的处理,它提供了一系列用于处理用户拖拽子View的辅助方法和与其相关的状态记录。比较常见的:QQ侧滑菜单,Navigation Drawer的边缘滑动,都可以由它实现。

    ViewDragHelper的使用并不复杂,在此通过一个示例展示其常用的用法。

    /**
     * ViewDragHelper使用示例
     * 原创作者:谷哥的小弟
     * 原创地址:http://blog.csdn.net/lfdfhl
     */
    public class MyLinearLayout extends LinearLayout {
        private ViewDragHelper mViewDragHelper;
    
        public MyLinearLayout(Context context, AttributeSet attrs) {
            super(context, attrs);
            initViewDragHelper();
        }
    
        //初始化ViewDragHelper
        private void initViewDragHelper() {
            mViewDragHelper = ViewDragHelper.create(this, 1.0f, new ViewDragHelper.Callback() {
                @Override
                public boolean tryCaptureView(View child, int pointerId) {
                    return true;
                }
    
                //处理水平方向的越界
                @Override
                public int clampViewPositionHorizontal(View child, int left, int dx) {
                    int fixedLeft;
                    View parent = (View) child.getParent();
                    int leftBound = parent.getPaddingLeft();
                    int rightBound = parent.getWidth() - child.getWidth() - parent.getPaddingRight();
    
                    if (left < leftBound) {
                        fixedLeft = leftBound;
                    } else if (left > rightBound) {
                        fixedLeft = rightBound;
                    } else {
                        fixedLeft = left;
                    }
                    return fixedLeft;
                }
    
                //处理垂直方向的越界
                @Override
                public int clampViewPositionVertical(View child, int top, int dy) {
                    int fixedTop;
                    View parent = (View) child.getParent();
                    int topBound = getPaddingTop();
                    int bottomBound = getHeight() - child.getHeight() - parent.getPaddingBottom();
                    if (top < topBound) {
                        fixedTop = topBound;
                    } else if (top > bottomBound) {
                        fixedTop = bottomBound;
                    } else {
                        fixedTop = top;
                    }
                    return fixedTop;
                }
    
                //监听拖动状态的改变
                @Override
                public void onViewDragStateChanged(int state) {
                    super.onViewDragStateChanged(state);
                    switch (state) {
                        case ViewDragHelper.STATE_DRAGGING:
                            System.out.println("STATE_DRAGGING");
                            break;
                        case ViewDragHelper.STATE_IDLE:
                            System.out.println("STATE_IDLE");
                            break;
                        case ViewDragHelper.STATE_SETTLING:
                            System.out.println("STATE_SETTLING");
                            break;
                    }
                }
    
                //捕获View
                @Override
                public void onViewCaptured(View capturedChild, int activePointerId) {
                    super.onViewCaptured(capturedChild, activePointerId);
                    System.out.println("ViewCaptured");
                }
    
                //释放View
                @Override
                public void onViewReleased(View releasedChild, float xvel, float yvel) {
                    super.onViewReleased(releasedChild, xvel, yvel);
                    System.out.println("ViewReleased");
                }
            });
        }
    
        //将事件拦截交给ViewDragHelper处理
        @Override
        public boolean onInterceptTouchEvent(MotionEvent ev) {
            return mViewDragHelper.shouldInterceptTouchEvent(ev);
        }
    
    
        //将Touch事件交给ViewDragHelper处理
        @Override
        public boolean onTouchEvent(MotionEvent ev) {
            mViewDragHelper.processTouchEvent(ev);
            return true;
        }
    }
    

    从这个例子可以看出来ViewDragHelper是作用在ViewGroup上的(比如LinearLayout)而不是直接作用到某个被拖拽的子View。其实这也不难理解,因为子View在布局中的位置是其所在的ViewGroup决定的。

    在该例中ViewDragHelper做了如下主要操作:

    (1) ViewDragHelper接管了ViewGroup的事件拦截,请参见代码第91-94行
    (2) ViewDragHelper接管了ViewGroup的Touch事件,请参见代码第98-102行
    (3) ViewDragHelper处理了拖拽子View时的边界越界,请参见代码第22-55行
    (4) ViewDragHelper监听拖拽子View时的状态变化,请参见代码第58-72行

    除了这些常见的操作,ViewDragHelper还可以实现:抽屉拉伸,拖拽结束松手后子View自动返回到原位等复杂操作。


    好了,了解完这些非常有用的工具,我们就正式进入自定义View。


    PS:
    0 如果觉得文章太长,那就直接看视频
    1 这个系列大概有8篇
    2 onMeasure(),onLayout(),onDraw()源码分析和示例
    3 dispatchTouchEvent(),onInterceptTouchEvent(),onTouchEvent()源码分析和示例
    4 滑动冲突
    5 各种自定义View的实现方式和具体示例
    6 力求从源码角度解开心中的疑惑,知其所以然。避免人云亦云
    7 每周至少一篇

  • 相关阅读:
    STM32 时钟配置分析
    STM32 开发板资源梳理
    STM32 摄像头实验OV2640
    STM32 TFT液晶屏与FSMC
    STM32 开发板电源与供电方式
    视觉里程计07 Qt的一些bug修改记录
    解决wireshark检测不到网卡的问题
    gdb 脚本调试
    [转] GCC 中的编译器堆栈保护技术
    使用gdbserver远程调试
  • 原文地址:https://www.cnblogs.com/hehe520/p/6147701.html
Copyright © 2011-2022 走看看