15.阻止触摸窃贼

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事件发生时,我们需要再次设置该标志。没有添加触摸侦听器来查找特定的事件,我们基于子视图的滚动行为连续设置该标志,这就获得了相同的效果。

猜你喜欢

转载自blog.csdn.net/qq_41121204/article/details/83213436