15. Stop touching thieves

Keywords: xml encoding Android Java

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.

Posted by Sarok on Tue, 29 Jan 2019 08:48:15 -0800