自定义View系列教程01--常用工具介绍

heza19 8年前
   <p>在自定义View的时候,常常会用到一些Android系统提供的工具。这些工具封装了我们经常会用到的方法,比如拖拽View,计算滑动速度,View的滚动,手势处理等等。如果我们自己去实现这些方法会比较繁琐,而且容易出一些bug。所以,作为自定义View系列教程的开端,先介绍一下这些常用的工具,以便在后续的学习和工作中使用。</p>    <ul>     <li><strong>Configuration</strong></li>     <li><strong>ViewConfiguration</strong></li>     <li><strong>GestureDetector</strong></li>     <li><strong>VelocityTracker</strong></li>     <li><strong>Scroller</strong></li>     <li><strong>ViewDragHelper</strong></li>    </ul>    <p>嗯哼,它们都已经躺在这里了,我们就来挨个瞅瞅</p>    <h2><strong>Configuration</strong></h2>    <blockquote>     <p>This class describes all device configuration information that can<br> impact the resources the application retrieves.</p>    </blockquote>    <p>Configuration用来描述设备的配置信息。<br> 比如用户的配置信息:locale和scaling等等<br> 比如设备的相关信息:输入模式,屏幕大小, 屏幕方向等等<br> 我们经常采用如下方式来获取需要的相关信息:</p>    <pre>  <code class="language-java">Configuration configuration=getResources().getConfiguration();  //获取国家码  int countryCode=configuration.mcc;  //获取网络码  int networkCode=configuration.mnc;  //判断横竖屏  if(configuration.orientation==Configuration.ORIENTATION_PORTRAIT){       } else {    }</code></pre>    <h2><strong>ViewConfiguration</strong></h2>    <p>看完Configuration再来瞅ViewConfiguration。这两者的名字有些像,差了一个View;咋一看,还以为它俩是继承关系,其实不然。<br> 官方对于ViewConfiguration的描述是:</p>    <blockquote>     <p>Contains methods to standard constants used in the UI for timeouts,<br> sizes, and distances.</p>    </blockquote>    <p>ViewConfiguration提供了一些自定义控件用到的标准常量,比如尺寸大小,滑动距离,敏感度等等。<br> 可以利用ViewConfiguration的静态方法获取一个实例</p>    <blockquote>     <p>ViewConfiguration viewConfiguration=ViewConfiguration.get(context);</p>    </blockquote>    <p>在此介绍ViewConfiguration的几个对象方法。</p>    <pre>  <code class="language-java">ViewConfiguration  viewConfiguration=ViewConfiguration.get(context);  //获取touchSlop。该值表示系统所能识别出的被认为是滑动的最小距离  int touchSlop = viewConfiguration.getScaledTouchSlop();  //获取Fling速度的最小值和最大值  int minimumVelocity = viewConfiguration.getScaledMinimumFlingVelocity();  int maximumVelocity = viewConfiguration.getScaledMaximumFlingVelocity();  //判断是否有物理按键  boolean isHavePermanentMenuKey=viewConfiguration.hasPermanentMenuKey();</code></pre>    <p>ViewConfiguration还提供了一些非常有用的静态方法,比如:</p>    <pre>  <code class="language-java">//双击间隔时间.在该时间内是双击,否则是单击  int doubleTapTimeout=ViewConfiguration.getDoubleTapTimeout();  //按住状态转变为长按状态需要的时间  int longPressTimeout=ViewConfiguration.getLongPressTimeout();  //重复按键的时间  int keyRepeatTimeout=ViewConfiguration.getKeyRepeatTimeout();</code></pre>    <h2><strong>GestureDetector</strong></h2>    <p>大家都知道,我们可以在onTouchEvent()中自己处理手势。其实Android系统也给我们提供了一个手势处理的工具,这就是GestureDetector手势监听类。利用GestureDetector可以简化许多操作,轻松实现一些常用的功能。<br> 嗯哼,来吧,一起瞅瞅它是怎么使用的。</p>    <p>第一步:实现OnGestureListener</p>    <pre>  <code class="language-java">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;          }      }</code></pre>    <p>第二步:生成GestureDetector对象</p>    <pre>  <code class="language-java">GestureDetector gestureDetector = new GestureDetector(context,new  GestureListenerImpl());</code></pre>    <p>这里的GestureListenerImpl就是GestureListener监听器的实现。</p>    <p>第三步:将Touch事件交给GestureDetector处理<br> 比如将Activity的Touch事件交给GestureDetector处理</p>    <pre>  <code class="language-java">@Override    public boolean onTouchEvent(MotionEvent event) {         return mGestureDetector.onTouchEvent(event);    } </code></pre>    <p>比如将View的Touch事件交给GestureDetector处理</p>    <pre>  <code class="language-java">mButton=(Button) findViewById(R.id.button);    mButton.setOnTouchListener(new OnTouchListener() {                 @Override       public boolean onTouch(View arg0, MotionEvent event) {              return mGestureDetector.onTouchEvent(event);          }    });  </code></pre>    <h2><strong>VelocityTracker</strong></h2>    <p>这个玩意儿一看名字,大概就可以猜到意思了。嗯哼,速度追踪。<br> VelocityTracker用于跟踪触摸屏事件(比如,Flinging及其他Gestures手势事件等)的速率。<br> 简单说一下它的常用套路。</p>    <p>第一步:开始速度追踪</p>    <pre>  <code class="language-java">private void startVelocityTracker(MotionEvent event) {        if (mVelocityTracker == null) {             mVelocityTracker = VelocityTracker.obtain();         }         mVelocityTracker.addMovement(event);    }  </code></pre>    <p>在这里我们初始化VelocityTracker,并且把要追踪的MotionEvent注册到VelocityTracker的监听中。</p>    <p>第二步:获取追踪到的速度</p>    <pre>  <code class="language-java">private int getScrollVelocity() {         // 设置VelocityTracker单位.1000表示1秒时间内运动的像素        mVelocityTracker.computeCurrentVelocity(1000);         // 获取在1秒内X方向所滑动像素值        int xVelocity = (int) mVelocityTracker.getXVelocity();         return Math.abs(xVelocity);        } </code></pre>    <p>同理可以获取1秒内Y方向所滑动像素值</p>    <p>第三步:解除速度追踪</p>    <pre>  <code class="language-java">private void stopVelocityTracker() {          if (mVelocityTracker != null) {              mVelocityTracker.recycle();              mVelocityTracker = null;          }    }  </code></pre>    <p>以上就是VelocityTracker的常用使用方式。</p>    <h2><strong>Scroller</strong></h2>    <p>Scroller挺常见的,用的比较多了。在此只强调几个重要的问题,别的就不再赘述了。</p>    <p>第一点:scrollTo()和scrollBy()的关系<br> 先看scrollBy( )的源码</p>    <pre>  <code class="language-java">public void scrollBy(int x, int y) {            scrollTo(mScrollX + x, mScrollY + y);     } </code></pre>    <p>这就是说scrollBy( )调用了scrollTo( ),最终起作用的是scrollTo( )方法。</p>    <p>第二点:scroll的本质<br> scrollTo( )和scrollBy( )移动的只是View的内容,而且View的背景是不移动的。</p>    <p>第三点:scrollTo( )和scrollBy( )方法的坐标说明</p>    <p>比如我们对于一个TextView调用scrollTo(0,25) ;那么该TextView中的content(比如显示的文字:Hello)会怎么移动呢?<br> 向下移动25个单位?不!恰好相反!!这是为什么呢?<br> 因为调用该方法会导致视图重绘,即会调用</p>    <blockquote>     <p>public void invalidate(int l, int t, int r, int b)</p>    </blockquote>    <p>此处的l,t,r,b四个参数就表示View原来的坐标.<br> 在该方法中最终会调用:</p>    <blockquote>     <p>tmpr.set(l - scrollX, t - scrollY, r - scrollX, b - scrollY);<br> p.invalidateChild(this, tmpr);</p>    </blockquote>    <p>其中tmpr是一个Rect,this是原来的View;通过这两行代码就把View在一个Rect中重绘。<br> 请注意第一行代码:<br> 原来的l和r均减去了scrollX<br> 原来的t和b均减去了scrollY<br> 就是说scrollX如果是正值,那么重绘后的View的宽度反而减少了;反之同理<br> 就是说scrollY如果是正值,那么重绘后的View的高度反而减少了;反之同理<br> 所以,TextView调用scrollTo(0,25)和我们的理解相反</p>    <p>scrollBy(int x,int y)方法与上类似,不再多说了.</p>    <h2><strong>ViewDragHelper</strong></h2>    <p>在项目中很多场景需要用户手指拖动其内部的某个View,此时就需要在onInterceptTouchEvent()和onTouchEvent()这两个方法中写不少逻辑了,比如处理:拖拽移动,越界,多手指的按下,加速度检测等等。<br> ViewDragHelper可以极大的帮我们简化类似的处理,它提供了一系列用于处理用户拖拽子View的辅助方法和与其相关的状态记录。比较常见的:QQ侧滑菜单,Navigation Drawer的边缘滑动,都可以由它实现。</p>    <p>ViewDragHelper的使用并不复杂,在此通过一个示例展示其常用的用法。</p>    <pre>  <code class="language-java">/** * 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.getPaddingTop();                  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;      }  }  </code></pre>    <p>从这个例子可以看出来ViewDragHelper是作用在ViewGroup上的(比如LinearLayout)而不是直接作用到某个被拖拽的子View。其实这也不难理解,因为子View在布局中的位置是其所在的ViewGroup决定的。</p>    <p>在该例中ViewDragHelper做了如下主要操作:</p>    <p>(1) ViewDragHelper接管了ViewGroup的事件拦截,请参见代码第91-94行<br> (2) ViewDragHelper接管了ViewGroup的Touch事件,请参见代码第98-102行<br> (3) ViewDragHelper处理了拖拽子View时的边界越界,请参见代码第22-55行<br> (4) ViewDragHelper监听拖拽子View时的状态变化,请参见代码第58-72行</p>    <p>除了这些常见的操作,ViewDragHelper还可以实现:抽屉拉伸,拖拽结束松手后子View自动返回到原位等复杂操作。</p>    <p>好了,了解完这些非常有用的工具,我们就正式进入自定义View。</p>    <p>PS:<br> 1 这个系列大概有15篇<br> 2 onMeasure(),onLayout(),onDraw()源码分析和示例<br> 3 dispatchTouchEvent(),onInterceptTouchEvent(),onTouchEvent()源码分析和示例<br> 4 滑动冲突<br> 5 各种自定义View的实现方式和具体示例<br> 6 力求从源码角度解开心中的疑惑,知其所以然。避免人云亦云<br> 7 每周至少一篇</p>    <p>来自: http://blog.csdn.net/lfdfhl/article/details/51324275</p>