14.转发触摸事件

14.1 问题

应用程序中的一些视图或触摸目标非常小,导致手指很难准确地触摸到。

14.2 解决方案

(API Level 1)
使用TouchDelegate指定任意的矩形区域来向小视图转发触摸事件。TouchDelegate的设计宗旨就是为父ViewGroup关联特定的区域,该区域侦测到触摸事件后会将该事件转发给它的某个子视图。TouchDelegate会发送每个事件到目标视图,就像触摸目标视图自己一样。

实现机制

以下两段代码清单演示了如何在自定义的父ViewGroup中使用TouchDelegate。
自定义父视图实现了TouchDelegate

public class TouchDelegateLayout extends FrameLayout {

    public TouchDelegateLayout(Context context) {
        super(context);
        init(context);
    }

    public TouchDelegateLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

    public TouchDelegateLayout(Context context, AttributeSet attrs, int defStyle){
        super(context, attrs, defStyle);
        init(context);
    }

    private CheckBox mButton;

    private void init(Context context) {
        // 创建一个很小的子视图,我们要将触摸事件转发给它
        mButton = new CheckBox(context);
        mButton.setText("Tap Anywhere");

        LayoutParams lp = new FrameLayout.LayoutParams(
                LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT,
                Gravity.CENTER);
        addView(mButton, lp);
    }

    /*
     * TouchDelegate 会将该视图(父视图)的某个特定矩形区域,将
     *所有触摸事件转发给CheckBox(子视图)。这里,矩形区域即为父视图的全部大小
     * 
     * 这个过程必须在视图确定了大小以后进行,这样才能知道矩形应该有多大,
     *所以我们选择在onSizeChanged()中添加代理区域
     */
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        if (w != oldw || h != oldh) {
            // 将该视图的整个区域作为代理区域
            Rect bounds = new Rect(0, 0, w, h);
            TouchDelegate delegate = new TouchDelegate(bounds, mButton);
            setTouchDelegate(delegate);
        }
    }
}

示例Activity

public class DelegateActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        TouchDelegateLayout layout = new TouchDelegateLayout(this);

        setContentView(layout);
    }
}

在这个示例中,我们创建了一个父视图,其中包含了一个居中显示的复选框。这个视图还包含一个TouchDelegate,它会将父视图区域内收到的触摸事件转发给复选框。因为我们想让父布局的整个区域转发触摸事件,所以会等到在视图上调用onSizeChanged()后再构建和关联TouchDelegate实例。如果在构造函数中,构建将不会生效,因为在执行构造函数时,视图还没有被测量,并且没有可以读取的尺寸大小。
Android框架会将没有处理的触摸事件自动从TouchDelegate分发到它的代理视图,因此无需额外代码即可转发这些事件。在图2-9中可以看到,应用程序在距离复选框很远的地方收到触摸事件后,复选框会做相应的响应,如同它自己直接被触摸了一样。

自定义触摸转发(远程滚动条)

TouchDelegate非常适合于转发触摸事件,但它有一个缺点,就是每个被转发的事件转发到代理视图后都会定位到代理视图的中间位置。这也意味着,如果想要通过TouchDelegate转发一系列ACTION_MOVE事件的话,结果将不会如你所愿,因为这时代理视图会显示手指并没有移动过(每次都定位到同一个点上)。
如果想要以一种更加精确的方式重新路由触摸事件,可以通过手动地调用目标视图的dispatchTouchEvent()方法来实现。参见以下两段代码清单以了解相应的实现机制。
res/layout/main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <TextView
        android:id="@+id/text_touch"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:gravity="center"
        android:text="Scroll Anywhere Here" />

    <HorizontalScrollView
        android:id="@+id/scroll_view"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:background="#CCC">
        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:orientation="horizontal" >
            <ImageView
                android:layout_width="250dp"
                android:layout_height="match_parent"
                android:scaleType="fitXY"
                android:src="@drawable/ic_launcher" />
            <ImageView
                android:layout_width="250dp"
                android:layout_height="match_parent"
                android:scaleType="fitXY"
                android:src="@drawable/ic_launcher" />
            <ImageView
                android:layout_width="250dp"
                android:layout_height="match_parent"
                android:scaleType="fitXY"
                android:src="@drawable/ic_launcher" />
            <ImageView
                android:layout_width="250dp"
                android:layout_height="match_parent"
                android:scaleType="fitXY"
                android:src="@drawable/ic_launcher" />
        </LinearLayout>
    </HorizontalScrollView>
</LinearLayout>

转发触摸事件的Activity

public class RemoteScrollActivity extends Activity implements View.OnTouchListener {

    private TextView mTouchText;
    private HorizontalScrollView mScrollView;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        
        mTouchText = (TextView) findViewById(R.id.text_touch);
        mScrollView = (HorizontalScrollView) findViewById(R.id.scroll_view);
        //为顶层视图关联触摸事件的监听器
        mTouchText.setOnTouchListener(this);
    }
    
    @Override
    public boolean onTouch(View v, MotionEvent event) {
        // 如果需要的话,可以修改事件位置
        // 这里我们将每个事件的垂直方向的位置都是相对于自己的坐标
        //将Text View上的每个事件转发到
        
        // 视图需要的事件位置都是相对于自己的坐标
        event.setLocation(event.getX(), mScrollView.getHeight() / 2);
        
       //将TextView上的每个事件转发到HorizontalScrollView.
        mScrollView.dispatchTouchEvent(event);
        return true;
    }
}

这个示例将一个Activity一分为二。上半部分是一个TextView,它会提示你触摸并滑动它;而下半部分是一个内部包含若干张图片的HorizontalScrollView。Activity为TextView设置一个OntouchListener,这样就可以将他接收的所有触摸事件转发给HorizontalScrollView。
我们希望触摸事件就像发生在(从HorizontalScrollView的角度)HorizontalScrollView自己的视图内部一样。所以在转发事件之前,我们会调用setLocation()来修改x/y坐标。在本例中,x坐标就是原来的坐标,y坐标则调整到了HorizontalScrollView的中间。这样,当用户手指向前或向后滚动时,就好像在HorizontalScrollView的中间滚动一样。然后,调用dispatchTouchEvent()将修改后的事件交予HorizontalScrollView处理。

注意:
避免直接调用onTouchEvent()方法转发触摸事件。调用dispatchTouchEvent()可以使其像常规触摸事件一样处理目标视图的触摸事件,包括必要时的事件拦截。

Demo下载地址:
[2.14 转发触摸事件]
https://download.csdn.net/download/qq_41121204/10764664

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,734评论 6 505
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,931评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,133评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,532评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,585评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,462评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,262评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,153评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,587评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,792评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,919评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,635评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,237评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,855评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,983评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,048评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,864评论 2 354

推荐阅读更多精彩内容

  • 这几天反反复复看了圈妈第一节课的内容,体会一次比一次深刻~ 这几天她又闹了一回,带她出去玩,回家路上睡着了,一进屋...
    宋青_65cc阅读 182评论 0 0
  • 所有人都告訴我「他從頭到尾都是騙你的」 我想著以前的總總、點點滴滴 我不相信也不願相信 那要是真的這樣他幹嘛對我這...
    誰能愛自己阅读 192评论 0 0
  • 这是一个最好的时代,也是一个最坏的时代,这是一个英雄辈出的年代,也是一个偶像崩塌的年代。两个月前华为手机还是我们很...
    手撕牛魔王阅读 671评论 0 0
  • 第七章 用户输入和while循环 7.1 函数input()的工作原理 动手试一试: carname=input(...
    坚持做自己阅读 330评论 0 0
  • 周日到了,我很高兴,爸爸要回来了。我想爸爸了,因为飞机晚点,下午1点才到家。爸爸给我和妹妹都带了礼物。我的是巧...
    杨欣仪小朋友阅读 176评论 0 0