开发笔记:解决安卓GestureOverlayView手势和ListView点击事件、文本框获取焦点冲突的问题

要解决这个问题,首先要弄清楚几个问题:

1、onThouch事件的触发原理是怎样的?

2、GestureOverlayView的绘制手势的事件是在什么时候触发的?

3、父子嵌套的控件触发事件的顺序是怎样的?

4、父子控件获取焦点的顺序是怎样的?


第一个问题,安卓中任何控件的onThouch事件触发的时候,都经过了以下过程:

        public boolean dispatchTouchEvent(MotionEvent ev) ,该方法如果返回true,则事件在该位置被消费掉,不再向下传递,返回false则继续传递至

    public boolean onInterceptTouchEvent(MotionEvent ev)这个方法,该方法是事件拦截器,如果返回true,则触发该控件的onThouch事件,否则就将事件传递给该控件的子控件,

      public boolean onTouchEvent(MotionEvent ev),返回true的话就消费掉该事件,返回false就传递到该控件的父控件的onThouch事件

在网上找到一个图片,能简洁明了的反映以上关系:


第二个问题,查看GestureOverlayView源码可知道,手势绘制的监听触发时间是在dispatchTouchEvent(MotionEvent ev)这个事件中就完成的,所以只要有触摸屏幕的动作,就必然会被它先消费掉,这也是手势事件和其他控件冲突的根本原因~源码如下:

 @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        if (isEnabled()) {
            final boolean cancelDispatch = (mIsGesturing || (mCurrentGesture != null &&
                    mCurrentGesture.getStrokesCount() > 0 && mPreviousWasGesturing)) &&
                    mInterceptEvents;

            processEvent(event);

            if (cancelDispatch) {
                event.setAction(MotionEvent.ACTION_CANCEL);
            }

            super.dispatchTouchEvent(event);

            return true;
        }

        return super.dispatchTouchEvent(event);
    }

    private boolean processEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                touchDown(event);
                invalidate();
                return true;
            case MotionEvent.ACTION_MOVE:
                if (mIsListeningForGestures) {
                    Rect rect = touchMove(event);
                    if (rect != null) {
                        invalidate(rect);
                    }
                    return true;
                }
                break;
            case MotionEvent.ACTION_UP:
                if (mIsListeningForGestures) {
                    touchUp(event, false);
                    invalidate();
                    return true;
                }
                break;
            case MotionEvent.ACTION_CANCEL:
                if (mIsListeningForGestures) {
                    touchUp(event, true);
                    invalidate();
                    return true;
                }
        }

        return false;
    }

基于这个原因,可能很多人会考虑通过重写GestureOverlayView控件来解决冲突问题,我也试过了,但是依然不能解决较为复杂的问题,所以在次就不再赘述了。


第三个问题,借用一篇博客来说明问题,在此感谢博主 @浅秋http://blog.csdn.net/hyp712/article/details/8777835(博文写的很详细)

借用第三个问题,第四个问题就迎刃而解了,可以简单的理解为,正常情况下,最内层的控件是最先获取焦点的,

最外层的是最后获取焦点的;但是最外层的获取焦点的优先级是最高的,一旦它决定拦截并消费事件,那么它的子控件就不能再获取该事件;


解决冲突的方法:

我的需求是在一个ListView页面启用手势功能,

用户如果画了手势,则根据手势内容做不同的反应,但是手势不能影响ListView的滚动、点击、选中的操作;

ListView中包含文本框;如果点击的是文本框,不能影响文本框获取焦点进行编辑;

我重写GestureOverlayView的时候解决了前2个需求,但是文本框死活获取不了焦点;

布局文件如下,需要说明是,因为之前是通过重写来做的,但是最后没解决,于是重写的文件直接调用了super,等于没重写:

<ListView
android:id="@+id/list_aj"
android:layout_width="fill_parent"
android:layout_height="fill_parent" 
android:clickable="true"
android:longClickable="true"
/>

<TextView
android:id="@+id/textViewModel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_centerVertical="true"
/>

<EditText
android:id="@+id/textViewValueModel"
android:layout_width="300dp"
android:layout_height="40dip"
android:layout_centerVertical="true"
android:gravity="center_vertical"
android:layout_marginLeft="110dip"
android:textSize="17sp"
android:background="@null"
android:inputType="text"
android:focusable="false"
android:visibility="gone"
/>

<ImageView
android:id="@+id/imageViewModel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:contentDescription="@string/descTask"
android:layout_centerVertical="true"/>

<com.zbtc_it.tcis.Util.MGestureOverlayView 
calss="com.zbtc_it.tcis.Util.MGestureOverlayView"
android:id="@+id/gesture"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:gravity="center"
>
</com.zbtc_it.tcis.Util.MGestureOverlayView>

重写的时候,我用MGestureOverlayView包含了ListView,

但是后来一想,因为MGestureOverlayView每次都会最先拦截,并且它必然会消费事件,所以这样肯定是不行的

于是就换成并列的,解决方法就是,在MGestureOverlayView的onThouch事件中,手动赋予ListView的事件源,代码如下

overlays = (MGestureOverlayView) layout.findViewById(R.id.gesture);
overlays.setGestureStrokeType(GestureOverlayView.GESTURE_STROKE_TYPE_MULTIPLE);
overlays.setFadeOffset(1000);// 多笔画2笔之间的时间间隔

overlays.setOnTouchListener(new View.OnTouchListener() {

@Override
public boolean onTouch(View v, MotionEvent event) {
itemListView.dispatchTouchEvent(event);//赋予ListView事件源
return false;//消费掉事件
}
});

同时重写ListView的onInterceptTouchEvent方法,让其直接返回false

(这步好像不是必须的,忘记了 >_< ,默认的貌似就是返回false)


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在 Android 中,AccessibilityService 可以用来实现手势录制和播放的功能。下面简要介绍一下实现的步骤: 1. 创建一个继承自 AccessibilityService 的服务类,并在 AndroidManifest.xml 中注册该服务。 ```xml <service android:name=".MyAccessibilityService" android:label="@string/accessibility_service_label" android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"> <intent-filter> <action android:name="android.accessibilityservice.AccessibilityService" /> </intent-filter> <meta-data android:name="android.accessibilityservice" android:resource="@xml/accessibility_service_config" /> </service> ``` 2. 在服务类中重写 onServiceConnected 方法,获取 AccessibilityServiceInfo 对象并设置相关参数。 ```java @Override public void onServiceConnected() { AccessibilityServiceInfo info = getServiceInfo(); info.eventTypes = AccessibilityEvent.TYPE_VIEW_CLICKED | AccessibilityEvent.TYPE_VIEW_SCROLLED; info.feedbackType = AccessibilityServiceInfo.FEEDBACK_GENERIC; info.flags |= AccessibilityServiceInfo.FLAG_REQUEST_FILTER_KEY_EVENTS; setServiceInfo(info); } ``` 3. 实现手势录制和播放的逻辑。 - 录制手势:在 AccessibilityService 中,可以通过 AccessibilityEvent 获取用户操作的事件类型、源控件、坐标等信息,从而实现手势的录制。 ```java @Override public void onAccessibilityEvent(AccessibilityEvent event) { switch (event.getEventType()) { case AccessibilityEvent.TYPE_VIEW_CLICKED: // 记录点击事件的坐标 int x = (int) event.getX(); int y = (int) event.getY(); // 将坐标记录到手势序列中 gestureBuilder.addStroke(new GesturePoint(x, y, SystemClock.uptimeMillis())); break; case AccessibilityEvent.TYPE_VIEW_SCROLLED: // 记录滑动事件的坐标 int scrollX = (int) event.getScrollX(); int scrollY = (int) event.getScrollY(); // 将坐标记录到手势序列中 gestureBuilder.addStroke(new GesturePoint(scrollX, scrollY, SystemClock.uptimeMillis())); break; } } ``` - 播放手势:通过 GestureLibrary 类加载手势序列,并使用 GestureOverlayView 类播放手势序列。 ```java // 加载手势序列 GestureLibrary gestureLibrary = GestureLibraries.fromFile(gestureFile); gestureLibrary.load(); // 播放手势序列 GestureOverlayView gestureOverlayView = new GestureOverlayView(context); gestureOverlayView.setGestureStrokeType(GestureOverlayView.GESTURE_STROKE_TYPE_MULTIPLE); gestureOverlayView.addOnGesturePerformedListener(new GestureOverlayView.OnGesturePerformedListener() { @Override public void onGesturePerformed(GestureOverlayView overlay, Gesture gesture) { // 执行手势序列 gestureLibrary.recognize(gesture); } }); gestureOverlayView.gesture = gesture; gestureOverlayView.performGesture(); ``` 需要注意的是,在 Android 6.0 及以上的版本中,需要动态申请录制和读取外部存储的权限。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值