Android expands the clickable area range of View

Sometimes we encounter this requirement: the control itself is displayed in a small range, but the clickable area is required to be expanded. According to the official document https://developer.android.com/develop/ui/views/touch-and-input/gestures/viewgroup?hl=zh-cn#delegate, we can know that through the TouchDelegate class, the parent view can transfer the child view to The touchable area extends beyond the bounds of the subview. This is useful when the child nodes must be small but require a larger touch area.
Give an example:
The layout file activity_main.xml is as follows: the width and height corresponding to the set TextView are only 10dp

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:app="http://schemas.android.com/apk/res-auto"
  xmlns:tools="http://schemas.android.com/tools"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  tools:context=".MainActivity">

  <TextView
    android:id="@ + id/tv_test"
    android:layout_width="10dp"
    android:layout_height="10dp"
    android:background="#e8e8e8"
    android:gravity="center"
    android:layout_centerInParent="true"
    android:text="Hello World" />

</RelativeLayout>

The activity file opposite is as follows:
Expand the up, down, left, and right click areas corresponding to textView to 500
● int paddingLeft = 500;
● int paddingRight = 500;
● int paddingTop = 500;
● int paddingBottom = 500;

package com.example.addview;

import androidx.appcompat.app.AppCompatActivity;

import android.graphics.Rect;
import android.os.Bundle;
import android.view.TouchDelegate;
import android.view.View;
import android.view.ViewTreeObserver;
import android.widget.TextView;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        final TextView testTextView = (TextView) findViewById(R.id.tv_test);
        testTextView.setOnClickListener(new View.OnClickListener() {
            int cnt = 0;
            @Override
            public void onClick(View v) {
                cnt + + ;
                Toast.makeText(MainActivity.this, "you clicked me" + " " + cnt + " times", Toast.LENGTH_SHORT).show();
            }
        });

        final View parent = (View) testTextView.getParent();
        int paddingLeft = 500;
        int paddingRight = 500;
        int paddingTop = 500;
        int paddingBottom = 500;
        testTextView.post(new Runnable() {
            @Override
            public void run() {
                Rect bounds = new Rect();
                testTextView.getHitRect(bounds);
                bounds.left -= paddingLeft;
                bounds.top -= paddingTop;
                bounds.right + = paddingRight;
                bounds.bottom + = paddingBottom;
                TouchDelegate mTouchDelegate = new TouchDelegate(bounds,testTextView);
                //Set the parent's TouchDelegate. When the parent executes the onTouchEvent method of the TouchDelegate, it will call the dispatchTouchEvent method of the proxy's TextView.
                parent.setTouchDelegate(mTouchDelegate);
            }
        });
    }
}

Then you can take a screenshot and see that clicking on a blank area that is not displayed by the control can also respond to the click event of the view.

Summarize:
The steps to expand the clickable area of Android controls through PaddingLeft are as follows:
● First, add your control in the xml file and set its paddingLeft property value. This value should be set to the pixel value you wish to expand.
● Use the TouchDelegate class to create a proxy object and then bind it to the control. The proxy object tells the Android system the range in which click events are triggered.
● Then use the View.post method to place the code that expands the width and height of the control in the message queue.

So why can the proxy object created through the TouchDelegate class expand the width and height of the control after being bound to the control?
You can see from the source code that if TouchDelegate is set, touchEvent will be given priority to TouchDelegate for processing.

public class TouchDelegate {

    /**
     * View that should receive forwarded touch events
     */
    private View mDelegateView;

    /**
     * Bounds in local coordinates of the containing view that should be mapped to the delegate
     * view. This rect is used for initial hit testing.
     */
    // Pass in a Rect object
    private Rect mBounds;

    /**
     * mBounds inflated to include some slop. This rect is to track whether the motion events
     * should be considered to be within the delegate view.
     */
    private Rect mSlopBounds;

    /**
     * True if the delegate had been targeted on a down event (intersected mBounds).
     */
    @UnsupportedAppUsage
    private boolean mDelegateTargeted;

    /**
     * The touchable region of the View extends above its actual extent.
     */
    public static final int ABOVE = 1;

    /**
     * The touchable region of the View extends below its actual extent.
     */
    public static final int BELOW = 2;

    /**
     * The touchable region of the View extends to the left of its actual extent.
     */
    public static final int TO_LEFT = 4;

    /**
     * The touchable region of the View extends to the right of its actual extent.
     */
    public static final int TO_RIGHT = 8;

    private int mSlop;

    /**
     * Touch delegate information for accessibility
     */
    private TouchDelegateInfo mTouchDelegateInfo;

    /**
     *Constructor
     *
     * @param bounds Bounds in local coordinates of the containing view that should be mapped to
     * the delegate view
     * @param delegateView The view that should receive motion events
     */
    public TouchDelegate(Rect bounds, View delegateView) {
        // Receive the corresponding view control and expanded area
        mBounds = bounds;

        mSlop = ViewConfiguration.get(delegateView.getContext()).getScaledTouchSlop();
        mSlopBounds = new Rect(bounds);
        mSlopBounds.inset(-mSlop, -mSlop);
        mDelegateView = delegateView;
    }

    /**
     * Forward touch events to the delegate view if the event is within the bounds
     * specified in the constructor.
     *
     * @param event The touch event to forward
     * @return True if the event was consumed by the delegate, false otherwise.
     */
    public boolean onTouchEvent(@NonNull MotionEvent event) {
        // Get the coordinates x,y of the event
        int x = (int)event.getX();
        int y = (int)event.getY();
        boolean sendToDelegate = false;
        boolean hit = true;
        boolean handled = false;

        switch (event.getActionMasked()) {
            // If it is an ACTION_DOWN event, determine whether the location x, y of the event falls within the expanded area mBounds
            case MotionEvent.ACTION_DOWN:
                mDelegateTargeted = mBounds.contains(x, y);
                sendToDelegate = mDelegateTargeted;
                break;
            case MotionEvent.ACTION_POINTER_DOWN:
            case MotionEvent.ACTION_POINTER_UP:
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_MOVE:
                sendToDelegate = mDelegateTargeted;
                if (sendToDelegate) {
                    Rect slopBounds = mSlopBounds;
                    if (!slopBounds.contains(x, y)) {
                        hit = false;
                    }
                }
                break;
            case MotionEvent.ACTION_CANCEL:
                sendToDelegate = mDelegateTargeted;
                mDelegateTargeted = false;
                break;
        }
        // If it falls within the expanded area
        if (sendToDelegate) {
            if (hit) {
                //Set the trigger position corresponding to the event
                // Offset event coordinates to be inside the target view
                event.setLocation(mDelegateView.getWidth() / 2, mDelegateView.getHeight() / 2);
            } else {
                // Offset event coordinates to be outside the target view (in case it does
                // something like tracking pressed state)
                int slop = mSlop;
                event.setLocation(-(slop * 2), -(slop * 2));
            }
            //Intercept click event
            handled = mDelegateView.dispatchTouchEvent(event);
        }
        return handled;
    }
    ...
}

As you can see from the source code, creating a TouchDelegate requires passing in a Rect(left,top,right,bottom) and delegateView. When onTouchEvent is triggered, this Rect will be used to determine whether the click event falls within the area, and if so, it will be forwarded to the agent. view to handle the event.