Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit b11e4d51 authored by Tony Wickham's avatar Tony Wickham
Browse files

Delegate Taskbar touches to nearest view to ensure 48x48dp touch size

In TaskbarView#onTouchEvent(), which is only reached if a Taskbar
icon didn't already consuem the event, check each child to see if
the event occurs within a 48x48dp bounding box, and delegate the
event and subsequent events to it until UP or CANCEL.

Bug: 171917176
Change-Id: I7afafe0835828ab9213ec6abfe4e88ad7b9af3c4
parent 794fe4f5
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -123,6 +123,7 @@
    <!-- Taskbar -->
    <dimen name="taskbar_size">48dp</dimen>
    <dimen name="taskbar_icon_size">32dp</dimen>
    <dimen name="taskbar_icon_touch_size">48dp</dimen>
    <!-- Note that this applies to both sides of all icons, so visible space is double this. -->
    <dimen name="taskbar_icon_spacing">14dp</dimen>
</resources>
+81 −0
Original line number Diff line number Diff line
@@ -17,10 +17,13 @@ package com.android.launcher3.taskbar;

import android.content.Context;
import android.content.res.Resources;
import android.graphics.RectF;
import android.graphics.drawable.ColorDrawable;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.widget.LinearLayout;

import androidx.annotation.LayoutRes;
@@ -39,6 +42,10 @@ public class TaskbarView extends LinearLayout {

    private final ColorDrawable mBackgroundDrawable;
    private final int mItemMarginLeftRight;
    private final int mIconTouchSize;
    private final int mTouchSlop;
    private final RectF mTempDelegateBounds = new RectF();
    private final RectF mDelegateSlopBounds = new RectF();

    // Initialized in init().
    private int mHotseatStartIndex;
@@ -46,6 +53,10 @@ public class TaskbarView extends LinearLayout {

    private TaskbarController.TaskbarViewCallbacks mControllerCallbacks;

    // Delegate touches to the closest view if within mIconTouchSize.
    private boolean mDelegateTargeted;
    private View mDelegateView;

    public TaskbarView(@NonNull Context context) {
        this(context, null);
    }
@@ -66,6 +77,8 @@ public class TaskbarView extends LinearLayout {
        Resources resources = getResources();
        mBackgroundDrawable = (ColorDrawable) getBackground();
        mItemMarginLeftRight = resources.getDimensionPixelSize(R.dimen.taskbar_icon_spacing);
        mIconTouchSize = resources.getDimensionPixelSize(R.dimen.taskbar_icon_touch_size);
        mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
    }

    protected void setCallbacks(TaskbarController.TaskbarViewCallbacks taskbarViewCallbacks) {
@@ -129,6 +142,74 @@ public class TaskbarView extends LinearLayout {
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        boolean handled = delegateTouchIfNecessary(event);
        return super.onTouchEvent(event) || handled;
    }

    /**
     * User touched the Taskbar background. Determine whether the touch is close enough to a view
     * that we should forward the touches to it.
     * @return Whether a delegate view was chosen and it handled the touch event.
     */
    private boolean delegateTouchIfNecessary(MotionEvent event) {
        final float x = event.getX();
        final float y = event.getY();
        if (mDelegateView == null && event.getAction() == MotionEvent.ACTION_DOWN) {
            for (int i = 0; i < getChildCount(); i++) {
                View child = getChildAt(i);
                if (!child.isShown() || !child.isClickable()) {
                    continue;
                }
                int childCenterX = child.getLeft() + child.getWidth() / 2;
                int childCenterY = child.getTop() + child.getHeight() / 2;
                mTempDelegateBounds.set(
                        childCenterX - mIconTouchSize / 2f,
                        childCenterY - mIconTouchSize / 2f,
                        childCenterX + mIconTouchSize / 2f,
                        childCenterY + mIconTouchSize / 2f);
                mDelegateTargeted = mTempDelegateBounds.contains(x, y);
                if (mDelegateTargeted) {
                    mDelegateView = child;
                    mDelegateSlopBounds.set(mTempDelegateBounds);
                    mDelegateSlopBounds.inset(-mTouchSlop, -mTouchSlop);
                    break;
                }
            }
        }

        boolean sendToDelegate = mDelegateTargeted;
        boolean inBounds = true;
        switch (event.getAction()) {
            case MotionEvent.ACTION_MOVE:
                inBounds = mDelegateSlopBounds.contains(x, y);
                break;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                mDelegateTargeted = false;
                break;
        }

        boolean handled = false;
        if (sendToDelegate) {
            if (inBounds) {
                // Offset event coordinates to be inside the target view
                event.setLocation(mDelegateView.getWidth() / 2f, mDelegateView.getHeight() / 2f);
            } else {
                // Offset event coordinates to be outside the target view (in case it does
                // something like tracking pressed state)
                event.setLocation(-mTouchSlop * 2, -mTouchSlop * 2);
            }
            handled = mDelegateView.dispatchTouchEvent(event);
            // Cleanup if this was the last event to send to the delegate.
            if (!mDelegateTargeted) {
                mDelegateView = null;
            }
        }
        return handled;
    }

    private View inflate(@LayoutRes int layoutResId) {
        return LayoutInflater.from(getContext()).inflate(layoutResId, this, false);
    }