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