Android custom control Series 10: handles touch event distribution by adding custom la S, and responds to events in specific directions by using specific controls in the combination interface. android Custom Controls

Source: Internet
Author: User

Android custom control Series 10: handles touch event distribution by adding custom la S, and responds to events in specific directions by using specific controls in the combination interface. android Custom Controls

This example is quite useful. It can be basically said that after writing this time, you can take it directly in many cases.AddViewAnd thenAddInterceptorViewYou can easily implement a specific control in the combination interface to respond to a touch event in a specific direction.


Please respect the original labor results. For reprinted results, please indicate the source.


In the process of writing Android applications, we often encounter this situation: the interface contains multiple controls, we hope that different sliding actions on the interface can be received by different controls, or the sliding action at different locations on the interface can be received by different controls. In other words,Can a special view respond to a specific touch event?? A typical example isListViewAndHeaderCombination:




Problems:


In this example, we will find a problem, that is, when the finger slides on the top carousel image, if we want to slide the carousel image, only when the finger is very horizontal can the carousel image be turned over, and when the finger sliding track is slightly skewed, it is found that the touch event isListViewThe response is changed to sliding up and down.ListViewThis experience is obviously not very good.


Let us assume that we want a simple implementation: the entire application may have many pages. Now we want to slide our fingers within the scope of the carousel image on this specific interface, when the finger trajectory angle is less than 45 degrees (relatively horizontal in the direction), let the carousel image respond to the touch event, so that the top image can slide horizontally; so that when the finger gesture trajectory angle is greater than 45 degrees (relatively vertical in the direction), The ListView can respond to the touch event, so that the entire ListView can slide up and down. How can this effect be achieved?


Solution:

In the previous article in the column, we analyzed in detail the distribution process of Android touch events and the source code of ViewGroup. (If you are not familiar with it, you can look at it:Android custom control Series 9: Android touch event distribution mechanism from the source code). After reading the previous article, we should know that the distribution of Andrioid events is carried out on a layer-by-layer basis. At the beginning, distribution always starts from the upper layer to the lower layer, from the Activity of the ActivityDecorViewAnd then to the layout we write, and then other components in the layout, the solution in this article is to customize a ViewGroup, wrapped outside the original ListView, on this specific interface. Since event distribution is carried out layer by layer, we rewrite the customization of this outer layer.ViewGroupOfDispatchTouchEventThe method can control the event distribution mechanism of all sub-views, so as to implement the desired touch Event Response Mechanism on this specific interface.


Write a customFrameLayoutCallInterceptorFrameLayout, RewriteDispatchTouchEvent (MotionEvent ev)Solution:


1. What we get during event distribution is the MotionEvent event. How can we determine whether the event falls in the desired control area?

Train of Thought: You canInterceptorFrameLayoutUsing a Map set to store the View we want to control the touch event and corresponding parameters representing the direction, exposing the externalAddAndRemoveMethod to add and remove the interceptedViewObject. And getEventAfter the event, callEvent. getRawXAndEvent. getRawYYou can get the absolute coordinates relative to the upper left corner of the screen, and then traverseViewMap set of all the judgment touch absolute coordinates are not inViewAnd whether the direction parameters to intercept match. You can useView. getLocationOnScreen (int [])Method to obtain the int array. The first element represents the x coordinate in the upper left corner of the view, and the second element representsViewIn the upper-right corner of the coordinate, the specific judgment method is as follows:

public static boolean isTouchInView (MotionEvent ev, View view) {// Determine whether ev occurs in the scope of view
static int [] touchLocation = new int [2];
view.getLocationOnScreen (touchLocation); // Get the coordinates of the upper left corner of the current child view through the getLocationOnScreen method
float motionX = ev.getRawX ();
float motionY = ev.getRawY ();

// Return whether it is within the range, judge whether it falls within the view by comparing the coordinates of the touch event with the coordinates of the upper left and lower four sides of the subview
return motionX> = touchLocation [0]
&& motionX <= (touchLocation [0] + view.getWidth ())
&& motionY> = touchLocation [1]
&& motionY <= (touchLocation [1] + view.getHeight ());
}

/ ** Find the view corresponding to the event and direction parameters in the collection, and return if found, or null if not found * /
private View findTargetView (MotionEvent ev, int orientation) {
// mViewAndOrientation is a collection of sub-views and corresponding orientation parameters for monitoring touch events
Set <View> keySet = mViewAndOrientation.keySet ();
for (View view: keySet) {
Integer ori = mViewAndOrientation.get (view);

// Because all the direction parameters are binary and the operation is 0
// So use AND operation to judge whether the direction is consistent
// All the judgment conditions here are:
// ①The subview is in the mViewAndOrientation collection
// ②The direction is the same
// ③ The touch event falls within the scope of the sub-view
// ④This subview can consume this event
// Satisfying the above four conditions at the same time means that the sub-view is the sub-view we are looking for, so it returns
if ((ori & orientation) == orientation && isTouchInView (ev, view)
&& view.dispatchTouchEvent (ev)) {
return view;
}
}
return null;
}


2. Rewrite the dispatchTouchEvent method:
① How to deal with the relationship between Down events and Move and Cancel and Up events.
The bond of this relationship is actually mFirstTouchTarget. If you read the previous blog post: Android Custom Control Series 9: From the source code, you can see the Android touch event distribution mechanism. If you have an impression, the source code mFirstTouchTarget will be able to consume events during the Down event. The subviews, and other event responses after the Down event, can be used to make further judgments based on the state of mFirstTouchTarget. Here we also imitate the source code, define a mFirstTarget. Every time you enter dispatchTouchEvent, you need to determine whether mFirstTarget is empty. If mFirstTarget is not empty, it means that a Down event can be consumed by a subview in a monitoring collection before, so we can continue to call boolean flag = The mFirstTarget.dispatchTouchEvent () method passes subsequent events (Move, Cancel, UP, etc.) to this corresponding subview--that is, mFirstTarget through dispatchTouchEvent; at this time, if the flag returns true, it means the subview (mFirstTarget) The event has been completely consumed, so you should reset mFirstTarget to be empty to facilitate the distribution of the next event; or the touch event is Cancel or Up, then it also indicates the termination of this event, so you must also empty mFirstTarget. Then return the value of flag.


@Override
public boolean dispatchTouchEvent (MotionEvent ev) {

int action = ev.getAction ();
// The meaning should be the shortest distance to trigger the mobile event, if it is less than this distance, the mobile control will not be triggered,
// For example, viewpager uses this distance to determine whether the user turns the page
mTouchSlop = configuration.getScaledTouchSlop ();

if (mFirstTarget! = null) {
// mFirstTarget is not empty, indicating that the most recent DOWN event has been responded to by a subview in the mViewAndOrientation collection
// So continue to distribute subsequent events to this subview
boolean flag = mFirstTarget.dispatchTouchEvent (ev);

// If flag = true, it means this event was consumed by child view, if the event is ACTION_CANCEL or ACTION_UP,
// Also represents the end of the event, so mFirstTarget is left blank to facilitate the response to the next DOWN event
if (flag
&& (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP)) {
mFirstTarget = null;
}
// return flag
return flag;
}
    ...
}


② Handle Down events:
When the Down event occurs, we do not know the direction of the next Move, so at this time, we can only pass the event and return the result of the view.dispatchTouchEvent () method of the eligible child view. If it can Find the subview in the set that meets the condition, and this subview.dispatchTouchEvent can return true, which means that the subview that meets the condition is found, so assign its value to mFirstTarget. During the Down event, you need to record the x and y coordinates of the Down event for the subsequent MOVE event to make judgments.


// Get the coordinates of this event, since only the difference needs to be calculated, so getX can also
final float currentX = ev.getX ();
final float currentY = ev.getY ();

switch (ev.getAction ()) {
case MotionEvent.ACTION_DOWN:
mFirstTarget = findTargetView (ev, ORIENTATION_ALL);
downX = currentX;
downY = currentY;
break;


③MOVE event:
When the MOVE event occurs, we get the current x, y coordinates again, and then compare it with the DOWN event to get the current sliding direction, and then we can use this direction and the touch event. Find if there is a sub-view that meets the requirements, if there is, assign it to mFirstTarget:


case MotionEvent.ACTION_MOVE:
if (Math.abs (currentX-downX)> Math.abs (currentY-downY)
&& Math.abs (currentX-downX)> mTouchSlop) {
System.out.println ("Slide left and right");
// swipe left and right
if (currentX-downX> 0) {
// slide right
mFirstTarget = findTargetView (ev, ORIENTATION_RIGHT);
} else {
// slide left
mFirstTarget = findTargetView (ev, ORIENTATION_LEFT);
}
} else if (Math.abs (currentY-downY)> Math.abs (currentX-downX)
&& Math.abs (currentY-downY)> mTouchSlop) {
System.out.println ("Slide up and down");
// slide up and down
if (currentY-downY> 0) {
// down
mFirstTarget = findTargetView (ev, ORIENTATION_DOWN);
} else {
// up
mFirstTarget = findTargetView (ev, ORIENTATION_UP);
}
mFirstTarget = null;
}
break;


④ Handle CANCEL or UP events:
If the event is Cancel or Up, it means that this touch event is over, then leave mFirstTarget blank to facilitate the next DOWN event:

case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
mFirstTarget = null;
break;
}


Subsequently, if mFirstTarget is not empty, it means that the corresponding subview has been found to receive, and it does not need to continue to distribute the event, it returns true; if mFirstTarget is empty at this time, it means that there is no subview in the collection that can respond to this event. Then hand it to super.dispatchTouchEvent (ev) for processing:

// Here, as long as mFirstTarget is not empty, the corresponding child view is found in the collection,
// returns true, indicating that this event was consumed and will not continue to be distributed
if (mFirstTarget! = null) {
return true;
} else {
return super.dispatchTouchEvent (ev);
}


After rewriting, you can add the InterceptorFrameLayout where we originally added the ListView, and then add the ListView as the child of the InterceptorFrameLayout through addview. So that you can achieve your goal, let's take a look at the effect:





The following is the complete code of InterceptorFrameLayout:

package com.example.viewpagerlistview.view;

import java.util.HashMap;
import java.util.Set;

import com.example.viewpagerlistview.application.BaseApplication;

import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.widget.FrameLayout;

/ **
 * @author : bitter coffee
 *
 * @version: 1.0
 *
 * @date: April 19, 2015
 *
 * @blog: http://blog.csdn.net/cyp331203
 *
 * @desc:
 * /
public class InterceptorFrameLayout extends FrameLayout {

/ ** represents the sliding direction upward * /
public static final int ORIENTATION_UP = 0x1; // 0000 0001
/ ** represents the sliding direction downward * /
public static final int ORIENTATION_DOWN = 0x2; // 0000 0010
/ ** represents the sliding direction to the left * /
public static final int ORIENTATION_LEFT = 0x4; // 0000 0100
/ ** represents the sliding direction to the right * /
public static final int ORIENTATION_RIGHT = 0x8; // 0000 1000
/ ** All directions representing the sliding direction * /
public static final int ORIENTATION_ALL = 0x10; // 0001 0000

/ ** Store the x and y coordinates of the upper left corner of the view * /
static int [] touchLocation = new int [2];

/ ** Used to represent the shortest distance to trigger a mobile event. If it is less than this distance, the mobile control is not triggered. For example, viewpager uses this distance to determine whether the user turns the page * /
private int mTouchSlop;

/ ** Used to record the x coordinate of the Down event * /
private float downX;
/ ** Used to record the y coordinate of the Down event * /
private float downY;
/ ** Used to store sub-views that need to control event distribution autonomously, and their corresponding sliding directions * /
private HashMap <View, Integer> mViewAndOrientation = new HashMap <View, Integer> ();
/ ** indicates that the subviews found in mViewAndOrientation that meet the conditions when an event occurs * /
private View mFirstTarget = null;
private ViewConfiguration configuration;

public InterceptorFrameLayout (Context context, AttributeSet attrs,
int defStyleAttr) {
super (context, attrs, defStyleAttr);
init ();
}

public InterceptorFrameLayout (Context context, AttributeSet attrs) {
super (context, attrs);
init ();
}

public InterceptorFrameLayout (Context context) {
super (context);
init ();
}

private void init () {
configuration = ViewConfiguration.get (getContext ());
}

@Override
public boolean dispatchTouchEvent (MotionEvent ev) {

int action = ev.getAction ();
// The meaning should be the shortest distance to trigger the mobile event, if it is less than this distance, the mobile control will not be triggered,
// For example, viewpager uses this distance to determine whether the user turns the page
mTouchSlop = configuration.getScaledTouchSlop ();

if (mFirstTarget! = null) {
// mFirstTarget is not empty, indicating that the most recent DOWN event has been responded to by a subview in the mViewAndOrientation collection
// So continue to distribute subsequent events to this subview
boolean flag = mFirstTarget.dispatchTouchEvent (ev);

// If flag = true, it means the event has been completely consumed and ended, if the event is ACTION_CANCEL or ACTION_UP,
// Also represents the end of the event, so mFirstTarget is left blank to facilitate the response to the next DOWN event
if (flag
&& (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP)) {
mFirstTarget = null;
}
// return flag
return flag;
}

// Get the coordinates of this event, since only the difference needs to be calculated, so getX can also
final float currentX = ev.getX ();
final float currentY = ev.getY ();

switch (ev.getAction ()) {
case MotionEvent.ACTION_DOWN:
mFirstTarget = findTargetView (ev, ORIENTATION_ALL);
downX = currentX;
downY = currentY;
break;
case MotionEvent.ACTION_MOVE:
if (Math.abs (currentX-downX) / Math.abs (currentY-downY)> 0.5f
&& Math.abs (currentX-downX)> mTouchSlop) {
System.out.print ("Slide left and right");
// swipe left and right
if (currentX-downX> 0) {
// slide right
mFirstTarget = findTargetView (ev, ORIENTATION_RIGHT);
System.out.println ("mFirstTarget =" + mFirstTarget);
} else {
// slide left
mFirstTarget = findTargetView (ev, ORIENTATION_LEFT);
System.out.println ("mFirstTarget =" + mFirstTarget);
}
} else if (Math.abs (currentY-downY) / Math.abs (currentX-downX)> 0.5f
&& Math.abs (currentY-downY)> mTouchSlop) {
System.out.print ("Slide up and down");
// slide up and down
if (currentY-downY> 0) {
// down
mFirstTarget = findTargetView (ev, ORIENTATION_DOWN);
System.out.println ("mFirstTarget =" + mFirstTarget);
} else {
// up
mFirstTarget = findTargetView (ev, ORIENTATION_UP);
System.out.println ("mFirstTarget =" + mFirstTarget);
}
mFirstTarget = null;
}
break;

case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
mFirstTarget = null;
break;
}

// Here, as long as mFirstTarget is not empty, the corresponding child view is found in the collection,
// returns true, indicating that this event was consumed and will not continue to be distributed
if (mFirstTarget! = null) {
return true;
} else {
return super.dispatchTouchEvent (ev);
}
}

/ ** Find the view corresponding to the event and direction parameters in the collection, and return if found, or null if not found * /
private View findTargetView (MotionEvent ev, int orientation) {
// mViewAndOrientation is a collection of sub-views and corresponding orientation parameters for monitoring touch events
Set <View> keySet = mViewAndOrientation.keySet ();
for (View view: keySet) {
Integer ori = mViewAndOrientation.get (view);

// Because all the direction parameters are binary and the operation is 0
// So use AND operation to judge whether the direction is consistent
// All the judgment conditions here are:
// ①The subview is in the mViewAndOrientation collection
// ②The direction is the same
// ③ The touch event falls within the scope of the sub-view
// ④This subview can consume this event
// Satisfying the above four conditions at the same time means that the sub-view is the sub-view we are looking for, so it returns
if ((ori & orientation) == orientation && isTouchInView (ev, view)
&& view.dispatchTouchEvent (ev)) {
return view;
}
}
return null;
}

public static boolean isTouchInView (MotionEvent ev, View view) {
view.getLocationOnScreen (touchLocation);
float motionX = ev.getRawX ();
float motionY = ev.getRawY ();

// returns whether it is in range
return motionX> = touchLocation [0]
&& motionX <= (touchLocation [0] + view.getWidth ())
&& motionY> = touchLocation [1]
&& motionY <= (touchLocation [1] + view.getHeight ());
}

/ ** Add interception * /
public void addInterceptorView (final View view, final int orientation) {
// Go to the main thread
BaseApplication.getMainThreadHandler (). Post (new Runnable () {

@Override
public void run () {
if (! mViewAndOrientation.con
tainsKey (view)) {
mViewAndOrientation.put (view, orientation);
}
}
});
}

/ ** Remove the blocking effect * /
public void removeInterceptorView (final View v) {
// Go to the main thread
BaseApplication.getMainThreadHandler (). Post (new Runnable () {
@Override
public void run () {
if (! mViewAndOrientation.containsKey (v)) {
mViewAndOrientation.remove (v);
}
}
});
}
}


Demo project source code download: http://download.csdn.net/detail/cyp331203/8621903



Please respect the original labor achievements, please indicate the source when reprinting: http://blog.csdn.net/cyp331203/article/details/45198549, please do not use it for commercial or profit-making purposes if you are not allowed.

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.