15.1 problem
Nested touch interaction is designed in the application view, which can not work well on the standard process of the touch hierarchy. In this hierarchy, the higher level container view handles touch events directly by stealing from the sub-view.
15.2 Solution
(API Level 1)
ViewGroup is the base class for all layouts and containers in the framework, which provides a descriptive naming method called requestDisallowTouchIntercept(). Setting this flag on any container view indicates that the frame will not intercept events entering its child view during the current gesture duration.
15.3 Implementation Mechanism
To demonstrate the practical use of this approach, we created an example where two competing touchable views are located in the same location. The externally contained view is ListView, which indicates vertical dragging of touch events by scrolling content responses. Inside the ListView is a ViewPager added as a header, which responds to horizontal dragging of touch events to sweep between pages. Essentially, this example raises the problem that attempts to sweep horizontally and remotely varying ViewPagers in the vertical direction are cancelled to support ListView scrolling because ListView monitors and intercepts these events. People can't drag in vertical or horizontal motion, so usability problems arise.
To create this example, you first need to declare a dimension resource (see the code below), and the code listing gives the complete Activity.
res/values/dimens.xml
<?xml version="1.0" encoding="utf-8"?> <resources> <dimen name="header_height">150dp</dimen> </resources>
Activeness for Managing Touch Interception
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); } } }
In this Activity, ListView, as the root view, contains a basic adapter for displaying a static list of string entries. Also in the onCreate() method, create a ViewPager instance and add it to the list as a header view. We'll discuss the way ViewPager works in more detail later in this chapter, just knowing that we're creating a simple ViewPager with a custom Pager Adapter that displays some color views as its pages for users to scan directly on these pages.
After creating the ViewPager, construct and apply a set of ListView.LayoutParams to control how the ViewPager is displayed as a header. This must be done because the ViewPager itself does not have an intrinsic content size, and lists do not work well with views that do not have a clear height. By applying a fixed height to the dimension resource, it is easy to get a properly scaled dp value, which is device independent. This is much simpler than fully constructing dp values from Java code.
The key to this example is the onPageChangeListener implemented by Activity (which will be used later with ViewPager). This callback is triggered when the user interacts with the ViewPager and sweeps left and right. Within the onPageScrollStateChanged() method, we pass an indication whether the ViewPager is idle or not.
Activity is scrolling or stopping to the value of a page after scrolling. This is the best place to control the touch interception behavior of the parent ListView. When the rolling state of ViewPager is not idle, we don't want ListView to steal the touch events that Viewager is using, so we set the corresponding flag in request DisallowTouch Intercept ().
There is another reason to trigger this value continuously. As mentioned in the original solution, this sign is valid for the current gesture. This means that every time a new ACTION_DOWN event occurs, we need to set the flag again. Without adding touch listeners to find specific events, we set the flag continuously based on the scrolling behavior of the sub-view, which achieves the same effect.