Android開發(fā)藝術(shù)探索-第三章-View的事件體系


layout: post
date: 2016-01-08
title: Android開發(fā)藝術(shù)探索-第三章-View的事件體系
categories: blog
tags: [Activity,Android,View,MotionEvent,TouchSlop]
category: Android
description:


本文首發(fā)于個人博客KuTear,轉(zhuǎn)載引用請注明原出處.謝謝!
另外,更多文章分享請查看博客KuTear

3.1 View的基礎(chǔ)知識

  • 位置參數(shù)

    top狐粱、left、right缀拭、bottom,在3.0之后增加了x杈绸、y辆布、translationX丘侠、translationY.這里的所有參數(shù)都是相對其父布局來說的.
    下面是具體的含義表示

    View參數(shù)

    其中參數(shù)的關(guān)系為

           x = left + translationX
           y = top + translationY
    
  • MontionEvent和TouchSlop

    MontionEvent代表著觸摸事件封裝的數(shù)據(jù),包括常用的Action和位置參數(shù)等.如上面圖示,注意函數(shù)getRaw*()是相對與屏幕的.
    TouchSlop表示滑動的最小常量.是常量(int),不是具體的類.獲取方式為:

           ViewConfiguration.get(getContext()).getScaledTouchSlop()
    
  • VelocityTracker,GestureDetector和Scroller

    VelocityTracker用于追蹤手指在滑動過程中的速度危彩,包括水平和垂直方向上的速度攒磨。
    速度計算公式:

           速度 = (終點位置 - 起點位置) / 時間段
    

    速度可能為負值,例如當手指從屏幕右邊往左邊滑動的時候汤徽。此外娩缰,速度是單位時間內(nèi)移動的像素數(shù),單位時間不一定是1秒鐘谒府,可以使用方法
    computeCurrentVelocity(xxx)指定單位時間是多少拼坎,單位是ms。例如通過computeCurrentVelocity(1000)來獲取速度完疫,手指在1s中
    滑動了100個像素泰鸡,那么速度是100,即100(像素/1000ms)壳鹤。如果computeCurrentVelocity(100)來獲取速度盛龄,在100ms內(nèi)手指只是滑動了
    10個像素,那么速度是10芳誓,即10(像素/100ms)余舶。
    VelocityTracker的使用方式:

           //初始化
           VelocityTracker mVelocityTracker = VelocityTracker.obtain();
           //在onTouchEvent方法中
           mVelocityTracker.addMovement(event);
           //獲取速度
           mVelocityTracker.computeCurrentVelocity(1000);
           float xVelocity = mVelocityTracker.getXVelocity();
           //重置和回收
           mVelocityTracker.clear(); //一般在MotionEvent.ACTION_UP的時候調(diào)用
           mVelocityTracker.recycle(); //一般在onDetachedFromWindow中調(diào)用
    

    GestureDetector用于輔助檢測用戶的單擊、滑動锹淌、長按匿值、雙擊等行為。GestureDetector的使用比較簡單葛圃,主要也是輔助檢測常見的觸屏事件千扔。
    作者建議:如果只是監(jiān)聽滑動相關(guān)的事件在onTouchEvent中實現(xiàn)憎妙;如果要監(jiān)聽雙擊這種行為的話,那么就使用GestureDetector曲楚。

    GestureDetector
    DoubleTabListener
    GestureListener
           //自定義的View,實現(xiàn)相關(guān)接口(onGestureListener,onDoubleTabListener)
           GestureDetector mGestureDetector = 
                   new GestureDetector(this/*context*/,listener/*onGestureListener*/);
           
           //function onTouchEvent(...)或onTouchListener的onTouch(...)中,直接返回
           return mGestureDetector.onTouchEvent(event)
    

    更多使用參見[參考2]

3.2 View的滑動

  • layout

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

    參數(shù)都是相對與父布局.

           @Override
           public boolean onTouchEvent(MotionEvent event) {
               int rawX = (int) (event.getRawX()); //相對與屏幕的坐標
               int rawY = (int) (event.getRawY());
               switch (event.getAction()) {
                   case MotionEvent.ACTION_DOWN:
                       // 記錄觸摸點坐標
                       lastX = rawX;
                       lastY = rawY;
                       break;
                   case MotionEvent.ACTION_MOVE:
                       // 計算偏移量
                       int offsetX = rawX - lastX;
                       int offsetY = rawY - lastY;
                       // 在當前l(fā)eft厘唾、top、right龙誊、bottom的基礎(chǔ)上加上偏移量
                       layout(getLeft() + offsetX,
                               getTop() + offsetY,
                               getRight() + offsetX,
                               getBottom() + offsetY);
                       // 重新設(shè)置初始坐標
                       lastX = rawX;
                       lastY = rawY;
                       break;
               }
               return true;
           }
    
  • offsetLeftAndRight和offsetTopAndBottom

    使用方法同上幾乎一致

           //直接在onTouchEvent中調(diào)用,替換上面的layout(...)部分
           offsetLeftAndRight(offestX);
           offsetTopAndBottom(offestY);
    
  • LayoutParams

    這個方式在平時開發(fā)中應(yīng)該使用的比較多.使用也是很簡單,就是修改params的某些參數(shù)

           //ViewGroup.MarginLayoutParams layoutParams = 
           //               (ViewGroup.MarginLayoutParams) getLayoutParams();
           //LinearLayout.LayoutParams extends ViewGroup.MarginLayoutParams,
           //幾乎所有的LayoutParms都是繼承至
           //ViewGroup.MarginLayoutParams,
           //所以ViewGroup.MarginLayoutParams是通用的...
           LinearLayout.LayoutParams layoutParams = 
                                 (LinearLayout.LayoutParams) getLayoutParams();
           layoutParams.leftMargin = getLeft() + offsetX;
           layoutParams.topMargin = getTop() + offsetY;
           setLayoutParams(layoutParams);
           //requestLayout();效果和上面這一句一樣
    
  • 動畫

    動畫部分在Android群英傳-第七章 Android動畫機制與使用技巧中已經(jīng)有比較詳細的說明,在這里就不做說明.

  • ViewDragHelper

    ViewDragHelper的使用過程其實也是比較簡單的,主要用戶控制部分都在Callback中.CallBack中的函數(shù)比較多

    CallBack

    下面是一個簡單的栗子:

           //初始化
           mDragHelper = ViewDragHelper.create(this/*要處理的ViewGroup*/, 
                          1.0f/*敏感度*/, new DragHelperCallback()/*前面說的Callback*/);
           
           //復(fù)寫一些函數(shù),代碼幾乎固定
           @Override
           public boolean onInterceptTouchEvent(MotionEvent ev) {
             final int action = MotionEventCompat.getActionMasked(ev);
             if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
                 mDragHelper.cancel();
                 return false;
             }
             return mDragHelper.shouldInterceptTouchEvent(ev);
           }
           @Override
           public boolean onTouchEvent(MotionEvent ev) {
             mDragHelper.processTouchEvent(ev);
             return true;
           }
    

    這里沒有詳細寫出CallBack的代碼,可以在這里查看.

  • ScrollTo和ScrollBy

    根據(jù)函數(shù)名稱就知道這兩個函數(shù)的區(qū)別,To是到具體的點,by只是與當前的偏移.
    這兩個函數(shù)不是針對view本身,而是針對其內(nèi)容,具體來說就是ViewGroup調(diào)用這兩函數(shù),是其內(nèi)部的view在移動,view調(diào)用是其內(nèi)容在動(TextView-->文本,ImageView-->圖像)
    另一方面就是他的參數(shù)不同與其他,正數(shù)X往左,正數(shù)Y往上.原因查看這里,
    如果想要移動View,就需要在她的parent上調(diào)用這函數(shù),下面是個栗子

         //替換上文onTouchEvent中的layout(...)
         ((ViewGroup) getParent()).scrollBy(-offsetX, -offsetY);
    
  • Scroller

    在以前都不知道有這個類,哎,基礎(chǔ)不夠誒.下面一個栗子說明

         //初始化,還可以使用插值器
         Scroller mScroller = new Scroller(mContext,interpolator/*插值器,可以不用*/);
         
         //View的computescroll()
         @Override
         public void computeScroll() {
             super.computeScroll();
             // 判斷Scroller是否執(zhí)行完畢
             if (mScroller.computeScrollOffset()) {
                 ((View) getParent()).scrollTo( mScroller.getCurrX(), mScroller.getCurrY());
                 // 通過重繪來不斷調(diào)用computeScroll
                 invalidate();//很重要
             }
         }
         
         //啟動
         mScroller.startScroll(startX,startY,dX,dY,duration);
    

    本質(zhì)上Scroller不能移動View,在我看來她同屬性動畫中的ValueAnimator是一樣的,因為他們都只是按照某種插值器產(chǎn)生數(shù)值,需要自己把數(shù)值同移動
    相聯(lián)系.

3.3 View的事件分發(fā)機制

  1. 事件分發(fā)過程的三個重要方法

    • dispatchTouchEvent

      函數(shù)原型

         public boolean dispatchTouchEvent(MotionEvent ev)
      

      主要的功能是負責事件的分發(fā).
      返回值:
      true: 表示向下分發(fā)中斷
      false: 表示繼續(xù)向下分發(fā)

    • onInterceptTouchEvent

      函數(shù)原型

         public boolean onInterceptTouchEvent(MotionEvent event)
      

      主要功能是負責事件的攔截
      返回值:
      true:攔截,事件交由自己(View/ViewGroup)的onTouchEvent(...)處理
      false:不攔截,事件繼續(xù)向下分發(fā).

    • onTouchEvent

      函數(shù)原型

         public boolean onTouchEvent(MotionEvent event)
      

      主要功能是處理觸摸事件
      返回值:
      true:表示消費了這個事件.
      false:表示沒有消費該事件,返回到上級處理.如果一直得不到處理,最終反饋到Activity的onTouchEvent(...)

  2. 函數(shù)之間的邏輯關(guān)系

    • 以上三個函數(shù)的偽代碼

      類似于遞歸調(diào)用的方式

        public boolean dispatchTouchEvent(MotionEvent ev) {
            boolean consume = false;
            if (onInterceptTouchEvent(ev)) {
                consume = onTouchEvent(ev);
            } else {
                consume = child.dispatchTouchEvent(ev);
            }
            return consume;
        }
      
    • 函數(shù)與監(jiān)聽接口

      在通常情況下,我們?yōu)锽utton等組件設(shè)置了onClickListener接口,有時也會設(shè)置onTouchListener接口,但在什么時候接口中的方法才會執(zhí)行呢?如果設(shè)置了onTouchListener接口監(jiān)聽,會對View(ViewGroup)的onTouchEvent有一定的影響.如果設(shè)置了onTouchListener,她的onTouch的返回值會影響view中onTouchEvent的調(diào)用與否,onTouch返回值的含義與onTouchEvent一樣,表示是否消費了該事件.onTouch會先于onTouchEvent執(zhí)行.偽代碼為

         //true表示消費掉
         if(!listener.onTouch(ev)){
             onTouchEvent(ev);
         }
      

      對于onClickListener接口,他內(nèi)部方法onCLick的調(diào)用是在onTouchEvent中(根據(jù)上面就知道如果在onTouchListener的onTouch中返回true,onclick就不會再執(zhí)行了),其內(nèi)部部分代碼如下.

         //View#onTouchEvent(...)
         if (mPerformClick == null) {
            mPerformClick = new PerformClick();
         }
         if (!post(mPerformClick)) {
            performClick();
         }
         
         //點擊事件的處理者 
         private final class PerformClick implements Runnable {
            @Override
            public void run() {
                performClick();
            }
        }
        
        //點擊調(diào)用onClick函數(shù)
        public boolean performClick() {
            //ListenerInfo封裝了各種監(jiān)聽
            final ListenerInfo li = mListenerInfo;
            if (...) {
                //調(diào)用部分
                li.mOnClickListener.onClick(this);
                result = true;
            }
            ...
            return result;
        }
      

      根據(jù)上面的描述,知道調(diào)用順序為onTouchListener#onTouch,返回值決定是否繼續(xù)執(zhí)行view的onTouchEvent,最后在onTouchEvent中執(zhí)行onClickListener的onClick方法.

  3. 分發(fā)過程

    • Activity分發(fā)

      觸摸事件最先到達Activity,所以首先會在Activity中分發(fā)

             //Activity#dispatchTouchEvent()
             public boolean dispatchTouchEvent(MotionEvent ev) {
                 if (ev.getAction() == MotionEvent.ACTION_DOWN) {
                     onUserInteraction();
                 }
                 //分發(fā)到Window.
                 if (getWindow().superDispatchTouchEvent(ev)) {
                     //true表示不再向下分發(fā)
                     return true;
                 }
                 return onTouchEvent(ev);
             }
      

      在getWindow()中返回mWindow,最終在函數(shù)attach(...)中發(fā)現(xiàn)

             mWindow = new PhoneWindow(this);
      

      PhoneWindow不在SDK中,在在線源碼(Android源碼)網(wǎng)站上可以找到相關(guān)的代碼

             public boolean superDispatchTouchEvent(MotionEvent event ) {
                 //DecorView extends FrameLayout 
                 //       DecorView#superDispatchTouchEvent(ev)
                 //       public boolean superDispatchTouchEvent(MotionEvent event) {
                 //               //來到了ViewGroup
                 //               return super.dispatchTouchEvent(event);
                 //       }
                 return mDecorView.superDispatchTouchEvent(event);
             }
      

      由此就把事件分發(fā)到了ViewGroup,接下來就是在VieGroup中分發(fā).

  • View分發(fā)

    函數(shù)dispatchTouchEvent(...)中的部分代碼

           ...
           if (onFilterTouchEventForSecurity(event)) {
               //noinspection SimplifiableIfStatement
               ListenerInfo li = mListenerInfo;
               if (li != null && li.mOnTouchListener != null
                       && (mViewFlags & ENABLED_MASK) == ENABLED
                       && li.mOnTouchListener.onTouch(this, event)) {
                   result = true;
               }
               // result==true,函數(shù)onTouchEvent(...)就執(zhí)行不到了,而影想result的主要就是
               //li.mOnTouchListener.onTouch(this, // event)的返回值,返回true,
               //表示事件被處理了,自然不需要在調(diào)用onTouchEvent(...)來重新處理
               // 前面說過onClick(...)是在onTouchEvent(...)中調(diào)用的.即優(yōu)先級小于onTouch()
               if (!result && onTouchEvent(event)) {
                   result = true;
               }
           }
           ... 
    

    函數(shù)onTouchEvent(...)主要就是處理事件,前面已經(jīng)說過onClick的執(zhí)行過程了.這里就不說了.

  • ViewGroup分發(fā)

    函數(shù)dispatchTouchEvent(...)中的部分代碼

           // Check for interception.
           final boolean intercepted;
           // 事件為ACTION_DOWN或者mFirstTouchTarget不為null
           //(即已經(jīng)找到能夠接收touch事件的目標組件)時if成立
           if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {
               //判斷disallowIntercept(禁止攔截)標志位
               //因為在其他地方可能調(diào)用了
               //requestDisallowInterceptTouchEvent(boolean disallowIntercept)
               //從而禁止執(zhí)行是否需要攔截的判斷
               //(有點拗口~其實看requestDisallowInterceptTouchEvent()方法名就可明白)
               final boolean disallowIntercept = 
                                  (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
               //補充:根據(jù)下面的代碼可以發(fā)現(xiàn), disallowIntercept 的值等于函數(shù)
               //requestDisallowInterceptTouchEvent的參數(shù).                 
               if (!disallowIntercept) {
                   intercepted = onInterceptTouchEvent(ev);
                   ev.setAction(action); // restore action in case it was changed
               } else {
                   intercepted = false;
               }
           } else {
               // There are no touch targets and this action is not an initial down
               // so this view group continues to intercept touches.
               intercepted = true;
           }
    

    注意上文代碼中的注釋部分,這里看一下部分requesrDisallowInterceptTouchEvent(...)的部分源碼

         public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
                   //更具這里可以看出,當disallowIntercept=true時,
                   //(mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0 成立,
                   //這就意味著上面一段代碼中的disallowIntercept=true;
                   if (disallowIntercept == ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0)) {
                       // We're already in this state, assume our ancestors are too
                       return;
                   }
                   ...
          }
    

    由此可見VIewGroup只會在ACTION=ACTION_DOWN或者mFirstTouchTarget != null時才判斷是否攔截事件,因為一個事件序列(DOWN->MOVE->...->UP)只能有一個View處理.但是mFirstTouchTarget != null表示什么呢?

    當事件被ViewGroup的子元素成功處理了(子View的onTouchEvent/onTouch返回了true??),mFirstTouchTarget被賦值指向子元素(即!=null)

    函數(shù)dispatchTouchEvent(...)的部分實現(xiàn).

       final View[] children = mChildren;
       for (int i = childrenCount - 1; i >= 0; i--) {
           final int childIndex = customOrder
                   ? getChildDrawingOrder(childrenCount, i) : i;
           final View child = (preorderedList == null)
                   ? children[childIndex] : preorderedList.get(childIndex);
           // If there is a view that has accessibility focus we want it
           // to get the event first and if not handled we will perform a
           // normal dispatch. We may do a double iteration but this is
           // safer given the timeframe.
           if (childWithAccessibilityFocus != null) {
               if (childWithAccessibilityFocus != child) {
                   continue;
               }
               childWithAccessibilityFocus = null;
               i = childrenCount - 1;
           }
    
           if (!canViewReceivePointerEvents(child)
                   || !isTransformedTouchPointInView(x, y, child, null)) {
               ev.setTargetAccessibilityFocus(false);
               continue;
           }
           newTouchTarget = getTouchTarget(child);
           if (newTouchTarget != null) {
               // Child is already receiving touch within its bounds.
               // Give it the new pointer in addition to the ones it is handling.
               // 找到接收Touch事件的子View!!!!!!!即為newTouchTarget.
               newTouchTarget.pointerIdBits |= idBitsToAssign;
               break;
           }
    
           resetCancelNextUpFlag(child);
           //注意這個方法,再后面再看看..根據(jù)源碼,
           //可以知道它返回的是子View(child)的dispatchTouchEvent(...)
           //當child==null,返回super.dispatchTouchEvent(...),
           //即View的dispatchTouchEvent(...)
           if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
               // Child wants to receive touch within its bounds.
               mLastTouchDownTime = ev.getDownTime();
               if (preorderedList != null) {
                   // childIndex points into presorted list, find original index
                   for (int j = 0; j < childrenCount; j++) {
                       if (children[childIndex] == mChildren[j]) {
                           mLastTouchDownIndex = j;
                           break;
                       }
                   }
               } else {
                   mLastTouchDownIndex = childIndex;
               }
               mLastTouchDownX = ev.getX();
               mLastTouchDownY = ev.getY();
               //找到了事件的處理者,終止循環(huán)
               newTouchTarget = addTouchTarget(child, idBitsToAssign);
               alreadyDispatchedToNewTouchTarget = true;
               break;
           }
    
           // The accessibility focus didn't handle the event, so clear
           // the flag and do a normal dispatch to all children.
           ev.setTargetAccessibilityFocus(false);
       }
    

同樣是dispatchTouchEvent(...)的部分代碼

            // Dispatch to touch targets.
            if (mFirstTouchTarget == null) {
                // No touch targets so treat this as an ordinary view.
                //這里說明沒有子View處理該事件,只得有View的dispatchTouchEvent(...)來處理.
                //關(guān)于該函數(shù)的部分源碼在后面介紹.
                handled = dispatchTransformedTouchEvent(ev, canceled, null/*child*/,
                        TouchTarget.ALL_POINTER_IDS);
            } else {
             ...   
            }
    
 函數(shù)addTouchTarget(...)的具體實現(xiàn).
            
            private TouchTarget addTouchTarget(View child, int pointerIdBits) {
                TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
                target.next = mFirstTouchTarget;
                mFirstTouchTarget = target;
                return target;
            }
            
  函數(shù)dispatchTransformedTouchEvent(...)的部分實現(xiàn).
  
        ....
        if (child == null) {
            handled = super.dispatchTouchEvent(event);
        } else {
            handled = child.dispatchTouchEvent(event);
        }
        ....
        return handled.

3.4 View的滑動沖突

  • 常見的滑動沖突的場景:

    1. 外部滑動方向和內(nèi)部滑動方向不一致抚垃,例如viewpager中包含listview;
    2. 外部滑動方向和內(nèi)部滑動方向一致趟大,例如viewpager的單頁中存在可以滑動的bannerview鹤树;
    3. 上面兩種情況的嵌套,例如viewpager的單個頁面中包含了bannerview和listview逊朽。
  • 滑動沖突處理規(guī)則

    可以根據(jù)滑動距離和水平方向形成的夾角罕伯;或者根絕水平和豎直方向滑動的距離差;或者兩個方向上的速度差等

  • 解決方式

    1. 外部攔截法:點擊事件都先經(jīng)過父容器的攔截處理叽讳,如果父容器需要此事件就攔截追他,如果不需要就不攔截。該方法需要重寫父容器的onInterceptTouchEvent方法岛蚤,在內(nèi)部做相應(yīng)的攔截即可邑狸,其他均不需要做修改。偽代碼如下:

       public boolean onInterceptTouchEvent(MotionEvent event) {
           boolean intercepted = false;
           int x = (int) event.getX();
           int y = (int) event.getY();
       
           switch (event.getAction()) {
           case MotionEvent.ACTION_DOWN: {
               intercepted = false;
               break;
           }
           case MotionEvent.ACTION_MOVE: {
               int deltaX = x - mLastXIntercept;
               int deltaY = y - mLastYIntercept;
               if (父容器需要攔截當前點擊事件的條件涤妒,例如:Math.abs(deltaX) > Math.abs(deltaY)) {
                   intercepted = true;
               } else {
                   intercepted = false;
               }
               break;
           }
           case MotionEvent.ACTION_UP: {
               intercepted = false;
               break;
           }
           default:
               break;
           }
       
           mLastXIntercept = x;
           mLastYIntercept = y;
       
           return intercepted;
       }
      
    2. 內(nèi)部攔截法:父容器不攔截任何事件单雾,所有的事件都傳遞給子元素,如果子元素需要此事件就直接消耗掉她紫,否則就交給父容器來處理硅堆。這種方法和Android中的事件分發(fā)機制不一致,需要配合requestDisallowInterceptTouchEvent方法才能正常工作贿讹。

         public boolean dispatchTouchEvent(MotionEvent event) {
             int x = (int) event.getX();
             int y = (int) event.getY();
         
             switch (event.getAction()) {
             case MotionEvent.ACTION_DOWN: {
                 getParent().requestDisallowInterceptTouchEvent(true);
                 break;
             }
             case MotionEvent.ACTION_MOVE: {
                 int deltaX = x - mLastX;
                 int deltaY = y - mLastY;
                 if (當前view需要攔截當前點擊事件的條件硬萍,例如:Math.abs(deltaX) > Math.abs(deltaY)) {
                     getParent().requestDisallowInterceptTouchEvent(false);
                 }
                 break;
             }
             case MotionEvent.ACTION_UP: {
                 break;
             }
             default:
                 break;
             }
             mLastX = x;
             mLastY = y;
             return super.dispatchTouchEvent(event);
         }
      

      父View的onInterceptTouchEvent(...)偽代碼

         public boolean  onInterceptTouchEvent(MotionEvent ev){
             if(ev.getAction() == MotionEvent.ACTION_DOWN){
                 retuen false;
             }else{
                 retuen true;
             }
         }        
      

      內(nèi)部攔截法過程說明,父類在ACTION_DOWN時不攔截,子類在ACTION_DOWN時攔截,這時mFirstTouchTarget!=null, disallowIntercept = true,這意味著父類的onInterceptTouchEvent(...)不會再被執(zhí)行,并且一個事件序列只有一個View來處理,則所有的后續(xù)ACTION_MOVE都會傳到子View,當在子View中判斷到某個事件應(yīng)該由父View處理,只需重置disallowIntercept=false即可,即調(diào)用函數(shù)requestDisallowInterceptTouchEvent(false),這時事件就到父View的onTouchEvent(...)處理的(因為onInterceptionTouchEvent在非ACTION_DOWN時都返回true).如果父類沒有在設(shè)置requestDisallowInterceptTouchEvent(true)的話,這個事件就會一直都在父View中做處理了.(注:為個人理解,若有不對,望其指出)

參考

  1. Art of Android Development Reading Notes 3

  2. 用戶手勢檢測-GestureDetector使用詳解

  3. ViewDragHelper詳解

  4. Android觸摸屏事件派發(fā)機制詳解與源碼分析一(View篇)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市围详,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌祖屏,老刑警劉巖助赞,帶你破解...
    沈念sama閱讀 206,126評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異袁勺,居然都是意外死亡雹食,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評論 2 382
  • 文/潘曉璐 我一進店門期丰,熙熙樓的掌柜王于貴愁眉苦臉地迎上來群叶,“玉大人吃挑,你說我怎么就攤上這事〗至ⅲ” “怎么了舶衬?”我有些...
    開封第一講書人閱讀 152,445評論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長赎离。 經(jīng)常有香客問我逛犹,道長,這世上最難降的妖魔是什么梁剔? 我笑而不...
    開封第一講書人閱讀 55,185評論 1 278
  • 正文 為了忘掉前任虽画,我火速辦了婚禮,結(jié)果婚禮上荣病,老公的妹妹穿的比我還像新娘码撰。我一直安慰自己,他們只是感情好个盆,可當我...
    茶點故事閱讀 64,178評論 5 371
  • 文/花漫 我一把揭開白布脖岛。 她就那樣靜靜地躺著,像睡著了一般砾省。 火紅的嫁衣襯著肌膚如雪鸡岗。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 48,970評論 1 284
  • 那天编兄,我揣著相機與錄音轩性,去河邊找鬼。 笑死狠鸳,一個胖子當著我的面吹牛揣苏,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播件舵,決...
    沈念sama閱讀 38,276評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼卸察,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了铅祸?” 一聲冷哼從身側(cè)響起坑质,我...
    開封第一講書人閱讀 36,927評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎临梗,沒想到半個月后涡扼,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,400評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡盟庞,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,883評論 2 323
  • 正文 我和宋清朗相戀三年吃沪,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片什猖。...
    茶點故事閱讀 37,997評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡票彪,死狀恐怖红淡,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情降铸,我是刑警寧澤在旱,帶...
    沈念sama閱讀 33,646評論 4 322
  • 正文 年R本政府宣布,位于F島的核電站垮耳,受9級特大地震影響颈渊,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜终佛,卻給世界環(huán)境...
    茶點故事閱讀 39,213評論 3 307
  • 文/蒙蒙 一俊嗽、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧铃彰,春花似錦绍豁、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至邪铲,卻和暖如春芬位,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背带到。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評論 1 260
  • 我被黑心中介騙來泰國打工昧碉, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人揽惹。 一個月前我還...
    沈念sama閱讀 45,423評論 2 352
  • 正文 我出身青樓被饿,卻偏偏與公主長得像,于是被迫代替她去往敵國和親搪搏。 傳聞我的和親對象是個殘疾皇子狭握,可洞房花燭夜當晚...
    茶點故事閱讀 42,722評論 2 345

推薦閱讀更多精彩內(nèi)容