14.轉(zhuǎn)發(fā)觸摸事件

14.1 問(wèn)題

應(yīng)用程序中的一些視圖或觸摸目標(biāo)非常小,導(dǎo)致手指很難準(zhǔn)確地觸摸到。

14.2 解決方案

(API Level 1)
使用TouchDelegate指定任意的矩形區(qū)域來(lái)向小視圖轉(zhuǎn)發(fā)觸摸事件。TouchDelegate的設(shè)計(jì)宗旨就是為父ViewGroup關(guān)聯(lián)特定的區(qū)域,該區(qū)域偵測(cè)到觸摸事件后會(huì)將該事件轉(zhuǎn)發(fā)給它的某個(gè)子視圖。TouchDelegate會(huì)發(fā)送每個(gè)事件到目標(biāo)視圖,就像觸摸目標(biāo)視圖自己一樣。

實(shí)現(xiàn)機(jī)制

以下兩段代碼清單演示了如何在自定義的父ViewGroup中使用TouchDelegate。
自定義父視圖實(shí)現(xiàn)了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) {
        // 創(chuàng)建一個(gè)很小的子視圖,我們要將觸摸事件轉(zhuǎn)發(fā)給它
        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 會(huì)將該視圖(父視圖)的某個(gè)特定矩形區(qū)域,將
     *所有觸摸事件轉(zhuǎn)發(fā)給CheckBox(子視圖)。這里,矩形區(qū)域即為父視圖的全部大小
     * 
     * 這個(gè)過(guò)程必須在視圖確定了大小以后進(jìn)行,這樣才能知道矩形應(yīng)該有多大,
     *所以我們選擇在onSizeChanged()中添加代理區(qū)域
     */
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        if (w != oldw || h != oldh) {
            // 將該視圖的整個(gè)區(qū)域作為代理區(qū)域
            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);
    }
}

在這個(gè)示例中,我們創(chuàng)建了一個(gè)父視圖,其中包含了一個(gè)居中顯示的復(fù)選框。這個(gè)視圖還包含一個(gè)TouchDelegate,它會(huì)將父視圖區(qū)域內(nèi)收到的觸摸事件轉(zhuǎn)發(fā)給復(fù)選框。因?yàn)槲覀兿胱尭覆季值恼麄€(gè)區(qū)域轉(zhuǎn)發(fā)觸摸事件,所以會(huì)等到在視圖上調(diào)用onSizeChanged()后再構(gòu)建和關(guān)聯(lián)TouchDelegate實(shí)例。如果在構(gòu)造函數(shù)中,構(gòu)建將不會(huì)生效,因?yàn)樵趫?zhí)行構(gòu)造函數(shù)時(shí),視圖還沒(méi)有被測(cè)量,并且沒(méi)有可以讀取的尺寸大小。
Android框架會(huì)將沒(méi)有處理的觸摸事件自動(dòng)從TouchDelegate分發(fā)到它的代理視圖,因此無(wú)需額外代碼即可轉(zhuǎn)發(fā)這些事件。在圖2-9中可以看到,應(yīng)用程序在距離復(fù)選框很遠(yuǎn)的地方收到觸摸事件后,復(fù)選框會(huì)做相應(yīng)的響應(yīng),如同它自己直接被觸摸了一樣。

自定義觸摸轉(zhuǎn)發(fā)(遠(yuǎn)程滾動(dòng)條)

TouchDelegate非常適合于轉(zhuǎn)發(fā)觸摸事件,但它有一個(gè)缺點(diǎn),就是每個(gè)被轉(zhuǎn)發(fā)的事件轉(zhuǎn)發(fā)到代理視圖后都會(huì)定位到代理視圖的中間位置。這也意味著,如果想要通過(guò)TouchDelegate轉(zhuǎn)發(fā)一系列ACTION_MOVE事件的話,結(jié)果將不會(huì)如你所愿,因?yàn)檫@時(shí)代理視圖會(huì)顯示手指并沒(méi)有移動(dòng)過(guò)(每次都定位到同一個(gè)點(diǎn)上)。
如果想要以一種更加精確的方式重新路由觸摸事件,可以通過(guò)手動(dòng)地調(diào)用目標(biāo)視圖的dispatchTouchEvent()方法來(lái)實(shí)現(xiàn)。參見(jiàn)以下兩段代碼清單以了解相應(yīng)的實(shí)現(xiàn)機(jī)制。
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>

轉(zhuǎn)發(fā)觸摸事件的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);
        //為頂層視圖關(guān)聯(lián)觸摸事件的監(jiān)聽(tīng)器
        mTouchText.setOnTouchListener(this);
    }
    
    @Override
    public boolean onTouch(View v, MotionEvent event) {
        // 如果需要的話,可以修改事件位置
        // 這里我們將每個(gè)事件的垂直方向的位置都是相對(duì)于自己的坐標(biāo)
        //將Text View上的每個(gè)事件轉(zhuǎn)發(fā)到
        
        // 視圖需要的事件位置都是相對(duì)于自己的坐標(biāo)
        event.setLocation(event.getX(), mScrollView.getHeight() / 2);
        
       //將TextView上的每個(gè)事件轉(zhuǎn)發(fā)到HorizontalScrollView.
        mScrollView.dispatchTouchEvent(event);
        return true;
    }
}

這個(gè)示例將一個(gè)Activity一分為二。上半部分是一個(gè)TextView,它會(huì)提示你觸摸并滑動(dòng)它;而下半部分是一個(gè)內(nèi)部包含若干張圖片的HorizontalScrollView。Activity為T(mén)extView設(shè)置一個(gè)OntouchListener,這樣就可以將他接收的所有觸摸事件轉(zhuǎn)發(fā)給HorizontalScrollView。
我們希望觸摸事件就像發(fā)生在(從HorizontalScrollView的角度)HorizontalScrollView自己的視圖內(nèi)部一樣。所以在轉(zhuǎn)發(fā)事件之前,我們會(huì)調(diào)用setLocation()來(lái)修改x/y坐標(biāo)。在本例中,x坐標(biāo)就是原來(lái)的坐標(biāo),y坐標(biāo)則調(diào)整到了HorizontalScrollView的中間。這樣,當(dāng)用戶手指向前或向后滾動(dòng)時(shí),就好像在HorizontalScrollView的中間滾動(dòng)一樣。然后,調(diào)用dispatchTouchEvent()將修改后的事件交予HorizontalScrollView處理。

注意:
避免直接調(diào)用onTouchEvent()方法轉(zhuǎn)發(fā)觸摸事件。調(diào)用dispatchTouchEvent()可以使其像常規(guī)觸摸事件一樣處理目標(biāo)視圖的觸摸事件,包括必要時(shí)的事件攔截。

Demo下載地址:
[2.14 轉(zhuǎn)發(fā)觸摸事件]
https://download.csdn.net/download/qq_41121204/10764664

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容