15.阻止觸控竊賊

bluepeach發表於2021-09-09

15.1 問題

應用程式檢視中設計了巢狀的觸控互動,這些互動不能很好地作用於觸控層次結構 的標準流程,在此層次結構中,較高層的容器檢視透過子檢視進行竊取來直接處理觸控事件。

15.2 解決方案

(API Level 1)
ViewGroup是框架中所有佈局和容器的基類,它為此提供了描述性命名方法requestDisallowTouchIntercept()。在任何容器檢視上設定此標誌會指示框架,在當前手勢持續期間,我們更希望它們不會攔截進入其子檢視的事件。

15.3 實現機制

為展示此方法的實際使用,我們建立了一個示例,其中兩個互相競爭的可觸控檢視位於同一位置。外部包含檢視是ListView,它透過滾動內容響應指示垂直拖動的觸控事件。在ListView內部是作為頭部新增的ViewPager,它響應水平拖動觸控事件以在頁面之間輕掃。就其本質來說,該例帶來了一個問題,水平輕掃在垂直方向上遠距離變化的ViewPager的嘗試會為了支援ListView滾動而被取消,因為ListView會監控和攔截這些事件。人們無法在垂直或水平運動過程中進行拖動,因此這就產生了可用性問題。
為建立此例,首先需要宣告一個維度資源(參見以下程式碼),程式碼清單給出了完整的Activity。
res/values/dimens.xml

<?xml version="1.0" encoding="utf-8"?><resources>
    <dimen name="header_height">150dp</dimen></resources>

管理觸控攔截的Activity

public class DisallowActivity extends Activity implements
        ViewPager.OnPageChangeListener {    private static final String[] ITEMS = {            "Row One", "Row Two", "Row Three", "Row Four",            "Row Five", "Row Six", "Row Seven", "Row Eight",            "Row Nine", "Row Ten"
    };    private ViewPager mViewPager;    private ListView mListView;    @Override
    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        // Create a header view of horizontal swiping items
        mViewPager = new ViewPager(this);        // As a ListView header, ViewPager must be given a fixed height
        mViewPager.setLayoutParams(new ListView.LayoutParams(
                ListView.LayoutParams.MATCH_PARENT,
                getResources().getDimensionPixelSize(R.dimen.header_height)) );        // Listen for paging state changes to disable parent touches
        mViewPager.setOnPageChangeListener(this);
        mViewPager.setAdapter(new HeaderAdapter(this));        // Create a vertical scrolling list
        mListView = new ListView(this);        // Add the pager as the list header
        mListView.addHeaderView(mViewPager);        // Add list items
        mListView.setAdapter(new ArrayAdapter<String>(this,
                android.R.layout.simple_list_item_1, ITEMS));

        setContentView(mListView);
    }    /* OnPageChangeListener Methods */
    
    @Override
    public void onPageScrolled(int position,            float positionOffset, int positionOffsetPixels) { }    @Override
    public void onPageSelected(int position) { }    @Override
    public void onPageScrollStateChanged(int state) {        // While the ViewPager is scrolling, disable the ScrollView touch
        // intercept so it cannot take over and try to vertical scroll.
        // This flag must be set for each gesture you want to override.
        boolean isScrolling = state != ViewPager.SCROLL_STATE_IDLE;
        mListView.requestDisallowInterceptTouchEvent(isScrolling);
    }    private static class HeaderAdapter extends PagerAdapter {        private Context mContext;        public HeaderAdapter(Context context) {
            mContext = context;
        }        @Override
        public int getCount() {            return 5;
        }        @Override
        public Object instantiateItem(ViewGroup container,                int position) {            // Create a new page view
            TextView tv = new TextView(mContext);
            tv.setText(String.format("Page %d", position + 1));
            tv.setBackgroundColor((position % 2 == 0) ? Color.RED
                    : Color.GREEN);
            tv.setGravity(Gravity.CENTER);
            tv.setTextColor(Color.BLACK);            // Add as the view for this position, and return as the object for
            // this position
            container.addView(tv);            return tv;
        }        @Override
        public void destroyItem(ViewGroup container,                int position, Object object) {
            View page = (View) object;
            container.removeView(page);
        }        @Override
        public boolean isViewFromObject(View view, Object object) {            return (view == object);
        }
    }
}

在此Activity中,作為根檢視的ListView包含一個基本介面卡,用於顯示字串條目的靜態列表。同樣在onCreate()方法中,建立ViewPager例項並作為頭部檢視新增到列表中。我們將在本章後面更詳細地討論ViewPager的工作方式,此處只需要知道我們正在建立一個帶有自定義PagerAdapter的簡單ViewPager,它顯示了一些彩色檢視作為其頁面,以供使用者在這些頁面直接輕掃。
建立ViewPager之後,構造並應用一組ListView.LayoutParams來控制ViewPager如何作為頭部顯示。必須執行該操作,因為ViewPager自身沒有內在的內容大小,並且列表不能很好地作用於沒有明確高度的檢視。透過維度資源應用固定的高度,從而可以輕鬆獲得適當縮放的dp值,該值與裝置無關。這比完全透過Java程式碼全面構造dp值要簡單很多。
此例的關鍵之處在於Activity實現的onPageChangeListener(該回撥在後面會用於與ViewPager)。當使用者與ViewPager互動並左右輕掃時,就會觸發此回撥。在onPageScrollStateChanged()方法內部,我們傳遞一個指示ViewPager是否空閒、
Activity正在滾動或在滾動後停到某個頁面的值。這是控制父ListView的觸控攔截行為的最佳位置。當ViewPager的滾動狀態不是空閒時,我們不希望ListView竊取Viewager正在使用的觸控事件,因此在requestDisallowTouchIntercept()中設定相應的標誌。
連續觸發該值還要另一個原因。在原始解決方案中提及,該標誌對當前手勢有效。這意味著每次新的ACTION_DOWN事件發生時,我們需要再次設定該標誌。沒有新增觸控偵聽器來查詢特定的事件,我們基於子檢視的滾動行為連續設定該標誌,這就獲得了相同的效果。



作者:Jennyni1122
連結:


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/2558/viewspace-2821795/,如需轉載,請註明出處,否則將追究法律責任。

相關文章