[Android分享] 使用触摸手势(六)

来源:互联网 发布:表格文字识别软件 编辑:程序博客网 时间:2024/05/20 16:40
原文链接:http://developer.android.com/training/gestures/viewgroup.html

在ViewGroup中处理触摸事件

在一个ViewGroup中处理事件必须要非常小心,因为通常一个ViewGroup都有子View,它们都是不同触摸事件的的对象。为了确保每一个View都能正确接收意图作用于它的触摸事件,覆盖onInterceptTouchEvent()方法。

在一个ViewGroup中拦截触摸事件

每当在ViewGroup表面上监听到一个触摸事件时,onInterceptTouchEvent()方法就会被调用,包括他子View的表面。如果onInterceptTouchEvent()返回true, MotionEvents将被拦截,意味着这些事件将不会被传递到其子View,而是父View的onTouchEvent()方法。

onInterceptTouchEvent()方法使得父View在其子View之前接收触摸事件。如果在父View的onInterceptTouchEvent()方法中返回true,之前处理触摸事件的子View将会收到一个ACTION_CANCEL,从那一个时刻开始,之后的事件将会发送到父View的onTouchEvent方法来处理。onInterceptTouchEvent()也能返回false,触摸事件将会顺着view层级到通常接收并处理这些触摸事件的目标view。

在下面的代码中,MyViewGroup类继承于ViewGroup。MyViewGroup包含大量的子view。如果你在一个子view上水平拖动手指,子view将不会再获得触摸事件,MyViewGroup将会处理这些触摸事件,是它的内容滑动。然而,如果你点击子view中的一个Button,或者在垂直方向滑动子view,父view将不会拦截这些触摸事件。在这些情况下,onInterceptTouchEvent()将返回false,MyViewGroup的onTouchEvent()方法将不会被调用。
?
代码片段,双击复制
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
publicclass MyViewGroup extendsViewGroup {
 
   privateint mTouchSlop;
 
   ...
 
   ViewConfiguration vc = ViewConfiguration.get(view.getContext());
   mTouchSlop = vc.getScaledTouchSlop();
 
   ...
 
   @Override
   publicboolean onInterceptTouchEvent(MotionEvent ev) {
        /*
         * This method JUST determines whether we want to intercept the motion.
         * If we return true, onTouchEvent will be called and we do the actual
         * scrolling there.
         */
 
 
        finalint action = MotionEventCompat.getActionMasked(ev);
 
        // Always handle the case of the touch gesture being complete.
        if(action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
            // Release the scroll.
            mIsScrolling = false;
            returnfalse;// Do not intercept touch event, let the child handle it
        }
 
        switch(action) {
            caseMotionEvent.ACTION_MOVE: {
                if(mIsScrolling) {
                    // We're currently scrolling, so yes, intercept the
                    // touch event!
                    returntrue;
                }
 
                // If the user has dragged her finger horizontally more than
                // the touch slop, start the scroll
 
                // left as an exercise for the reader
                finalint xDiff = calculateDistanceX(ev);
 
                // Touch slop should be calculated using ViewConfiguration
                // constants.
                if(xDiff > mTouchSlop) {
                    // Start scrolling!
                    mIsScrolling = true;
                    returntrue;
                }
                break;
            }
            ...
        }
 
        // In general, we don't want to intercept touch events. They should be
        // handled by the child view.
        returnfalse;
   }
 
   @Override
   publicboolean onTouchEvent(MotionEvent ev) {
        // Here we actually handle the touch event (e.g. if the action is ACTION_MOVE,
        // scroll this container).
        // This method will only be called if the touch event was intercepted in
        // onInterceptTouchEvent
        ...
   }
}

注意ViewGroup也提供了一个requestDisallowInterceptTouchEvent()方法,当一个子View不想让其父View和祖先View在onInterceptTouchEvent()中拦截触摸事件时,会调用这个方法。

使用ViewConfiguration常量

能够使用ViewConfiguration类来访问一些Android系统使用的常量,例如距离、速度和时间。

“Touch slop”是当用户的触摸事件被解释为滑动需要移动的最小距离。

两个被ViewConfiguration常用的方法是getScaledMinimumFlingVelocity()和getScaledMaximumFlingVelocity()。这两个方法返回最小和最大速度来启动一个fling,单位是像素每秒。

?
代码片段,双击复制
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
ViewConfiguration vc = ViewConfiguration.get(view.getContext());
privateint mSlop = vc.getScaledTouchSlop();
privateint mMinFlingVelocity = vc.getScaledMinimumFlingVelocity();
privateint mMaxFlingVelocity = vc.getScaledMaximumFlingVelocity();
 
...
 
caseMotionEvent.ACTION_MOVE: {
   ...
   floatdeltaX = motionEvent.getRawX() - mDownX;
   if(Math.abs(deltaX) > mSlop) {
        // A swipe occurred, do something
   }
 
...
 
caseMotionEvent.ACTION_UP: {
   ...
   }if(mMinFlingVelocity <= velocityX && velocityX <= mMaxFlingVelocity
            && velocityY < velocityX) {
        // The criteria have been satisfied, do something
   }
}


扩展子视图的可触摸区域

Android提供了TouchDelegate类,使得父View能够扩展子View的触摸区域。当一个子View所占区域非常小,需要一个大的区域让用户操作时,这是非常有用的。如果需要缩小子View的触摸区域,也可以使用这种方式。

?
代码片段,双击复制
01
02
03
04
05
06
07
08
09
10
11
12
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/parent_layout"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     tools:context=".MainActivity">
  
     <ImageButton android:id="@+id/button"
          android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:background="@null"
          android:src="@drawable/icon"/>
</RelativeLayout>

下面的代码做了以下一些事情:
1.获取父view,并在UI线程上post一个Runnable。这保证了父view在调用getHitRect()方法之前布局它的子视图。getHitRect()方法在父视图坐标系中获得子视图的矩形区域。
2.找到ImageButton子view并调用getHitRect()来获取子视图的可触摸区域。
3.扩展ImageButton的矩形边界。
4.实例化一个TouchDelegate,并将矩形区域和ImageButton子视图作为传递参数。
5.在父view上面设置TouchDelegate,则在扩咱区域上的触摸动作会传递到子视图。

父view将会代表ImageButton子view接受所有的触摸事件,如果触摸事件在子视图的扩展矩形中,则父view将会把触摸事件传递给子view去处理。
?
代码片段,双击复制
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
publicclass MainActivity extendsActivity {
 
   @Override
   protectedvoid onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        // Get the parent view
        View parentView = findViewById(R.id.parent_layout);
         
        parentView.post(newRunnable() {
            // Post in the parent's message queue to make sure the parent
            // lays out its children before you call getHitRect()
            @Override
            publicvoid run() {
                // The bounds for the delegate view (an ImageButton
                // in this example)
                Rect delegateArea = newRect();
                ImageButton myButton = (ImageButton) findViewById(R.id.button);
                myButton.setEnabled(true);
                myButton.setOnClickListener(newView.OnClickListener() {
                    @Override
                    publicvoid onClick(View view) {
                        Toast.makeText(MainActivity.this,
                                "Touch occurred within ImageButton touch region.",
                                Toast.LENGTH_SHORT).show();
                    }
                });
      
                // The hit rectangle for the ImageButton
                myButton.getHitRect(delegateArea);
             
                // Extend the touch area of the ImageButton beyond its bounds
                // on the right and bottom.
                delegateArea.right += 100;
                delegateArea.bottom += 100;
             
                // Instantiate a TouchDelegate.
                // "delegateArea" is the bounds in local coordinates of
                // the containing view to be mapped to the delegate view.
                // "myButton" is the child view that should receive motion
                // events.
                TouchDelegate touchDelegate = newTouchDelegate(delegateArea,
                        myButton);
      
                // Sets the TouchDelegate on the parent view, such that touches
                // within the touch delegate bounds are routed to the child.
                if(View.class.isInstance(myButton.getParent())) {
                    ((View) myButton.getParent()).setTouchDelegate(touchDelegate);
                }
            }
        });
   }
}