1349e2285f933743829cdef1810106d62d0f1c4fcAnthony Chen/*
2349e2285f933743829cdef1810106d62d0f1c4fcAnthony Chen * Copyright 2018 The Android Open Source Project
3349e2285f933743829cdef1810106d62d0f1c4fcAnthony Chen *
4349e2285f933743829cdef1810106d62d0f1c4fcAnthony Chen * Licensed under the Apache License, Version 2.0 (the "License");
5349e2285f933743829cdef1810106d62d0f1c4fcAnthony Chen * you may not use this file except in compliance with the License.
6349e2285f933743829cdef1810106d62d0f1c4fcAnthony Chen * You may obtain a copy of the License at
7349e2285f933743829cdef1810106d62d0f1c4fcAnthony Chen *
8349e2285f933743829cdef1810106d62d0f1c4fcAnthony Chen *      http://www.apache.org/licenses/LICENSE-2.0
9349e2285f933743829cdef1810106d62d0f1c4fcAnthony Chen *
10349e2285f933743829cdef1810106d62d0f1c4fcAnthony Chen * Unless required by applicable law or agreed to in writing, software
11349e2285f933743829cdef1810106d62d0f1c4fcAnthony Chen * distributed under the License is distributed on an "AS IS" BASIS,
12349e2285f933743829cdef1810106d62d0f1c4fcAnthony Chen * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13349e2285f933743829cdef1810106d62d0f1c4fcAnthony Chen * See the License for the specific language governing permissions and
14349e2285f933743829cdef1810106d62d0f1c4fcAnthony Chen * limitations under the License.
15349e2285f933743829cdef1810106d62d0f1c4fcAnthony Chen */
16349e2285f933743829cdef1810106d62d0f1c4fcAnthony Chen
17349e2285f933743829cdef1810106d62d0f1c4fcAnthony Chenpackage androidx.car.widget;
18349e2285f933743829cdef1810106d62d0f1c4fcAnthony Chen
19349e2285f933743829cdef1810106d62d0f1c4fcAnthony Chenimport android.graphics.Rect;
20349e2285f933743829cdef1810106d62d0f1c4fcAnthony Chenimport android.view.TouchDelegate;
21349e2285f933743829cdef1810106d62d0f1c4fcAnthony Chenimport android.view.View;
22349e2285f933743829cdef1810106d62d0f1c4fcAnthony Chen
23349e2285f933743829cdef1810106d62d0f1c4fcAnthony Chenimport androidx.annotation.NonNull;
24349e2285f933743829cdef1810106d62d0f1c4fcAnthony Chen
25349e2285f933743829cdef1810106d62d0f1c4fcAnthony Chen/**
26349e2285f933743829cdef1810106d62d0f1c4fcAnthony Chen * Helper class that will ensure that a given view meets a minimum touch target size that is
27349e2285f933743829cdef1810106d62d0f1c4fcAnthony Chen * specified.
28349e2285f933743829cdef1810106d62d0f1c4fcAnthony Chen */
29349e2285f933743829cdef1810106d62d0f1c4fcAnthony Chenclass MinTouchTargetHelper {
30349e2285f933743829cdef1810106d62d0f1c4fcAnthony Chen    private MinTouchTargetHelper() {}
31349e2285f933743829cdef1810106d62d0f1c4fcAnthony Chen
32349e2285f933743829cdef1810106d62d0f1c4fcAnthony Chen    /**
33349e2285f933743829cdef1810106d62d0f1c4fcAnthony Chen     * Sets up the view that will be checked and ensured to meet a minimum touch target size.
34349e2285f933743829cdef1810106d62d0f1c4fcAnthony Chen     *
35349e2285f933743829cdef1810106d62d0f1c4fcAnthony Chen     * @param view The view that to be checked; cannot be {@code null}.
36349e2285f933743829cdef1810106d62d0f1c4fcAnthony Chen     * @return A {@link TouchTargetSubject} that can be customized with touch target information.
37349e2285f933743829cdef1810106d62d0f1c4fcAnthony Chen     */
38349e2285f933743829cdef1810106d62d0f1c4fcAnthony Chen    static TouchTargetSubject ensureThat(View view) {
39349e2285f933743829cdef1810106d62d0f1c4fcAnthony Chen        if (view == null) {
40349e2285f933743829cdef1810106d62d0f1c4fcAnthony Chen            throw new IllegalArgumentException("View cannot be null.");
41349e2285f933743829cdef1810106d62d0f1c4fcAnthony Chen        }
42349e2285f933743829cdef1810106d62d0f1c4fcAnthony Chen        return new TouchTargetSubject(view);
43349e2285f933743829cdef1810106d62d0f1c4fcAnthony Chen    }
44349e2285f933743829cdef1810106d62d0f1c4fcAnthony Chen
45349e2285f933743829cdef1810106d62d0f1c4fcAnthony Chen    /**
46349e2285f933743829cdef1810106d62d0f1c4fcAnthony Chen     * A class to encapsulate information concerning a view whose target target needs to be
47349e2285f933743829cdef1810106d62d0f1c4fcAnthony Chen     * expanded to meet a minimum touch size.
48349e2285f933743829cdef1810106d62d0f1c4fcAnthony Chen     *
49349e2285f933743829cdef1810106d62d0f1c4fcAnthony Chen     * <p>This class should not be created directly. Instead use
50349e2285f933743829cdef1810106d62d0f1c4fcAnthony Chen     * {@link MinTouchTargetHelper#ensureThat(View)} to get an instance of this class.
51349e2285f933743829cdef1810106d62d0f1c4fcAnthony Chen     */
52349e2285f933743829cdef1810106d62d0f1c4fcAnthony Chen    static class TouchTargetSubject {
53349e2285f933743829cdef1810106d62d0f1c4fcAnthony Chen        private View mSubjectView;
54349e2285f933743829cdef1810106d62d0f1c4fcAnthony Chen
55349e2285f933743829cdef1810106d62d0f1c4fcAnthony Chen        private TouchTargetSubject(@NonNull View subject) {
56349e2285f933743829cdef1810106d62d0f1c4fcAnthony Chen            mSubjectView = subject;
57349e2285f933743829cdef1810106d62d0f1c4fcAnthony Chen        }
58349e2285f933743829cdef1810106d62d0f1c4fcAnthony Chen
59349e2285f933743829cdef1810106d62d0f1c4fcAnthony Chen        /**
60349e2285f933743829cdef1810106d62d0f1c4fcAnthony Chen         * Sets the minimum touch target size in pixels for the subject view. Calling this method
61349e2285f933743829cdef1810106d62d0f1c4fcAnthony Chen         * will also set up the framework to do this assurance.
62349e2285f933743829cdef1810106d62d0f1c4fcAnthony Chen         *
63349e2285f933743829cdef1810106d62d0f1c4fcAnthony Chen         * <p>Under the hood, this method uses a {@link TouchDelegate} to handle the delegation,
64349e2285f933743829cdef1810106d62d0f1c4fcAnthony Chen         * meaning that only one child view can be set per parent.
65349e2285f933743829cdef1810106d62d0f1c4fcAnthony Chen         *
66349e2285f933743829cdef1810106d62d0f1c4fcAnthony Chen         * @param size The minimum touch target size in pixels.
67349e2285f933743829cdef1810106d62d0f1c4fcAnthony Chen         */
68349e2285f933743829cdef1810106d62d0f1c4fcAnthony Chen        void hasMinTouchSize(int size) {
69349e2285f933743829cdef1810106d62d0f1c4fcAnthony Chen            if (size <= 0) {
70349e2285f933743829cdef1810106d62d0f1c4fcAnthony Chen                throw new IllegalStateException(
71349e2285f933743829cdef1810106d62d0f1c4fcAnthony Chen                        "Minimum touch target size must be greater than 0.");
72349e2285f933743829cdef1810106d62d0f1c4fcAnthony Chen            }
73349e2285f933743829cdef1810106d62d0f1c4fcAnthony Chen
74349e2285f933743829cdef1810106d62d0f1c4fcAnthony Chen            if (!(mSubjectView.getParent() instanceof View)) {
75349e2285f933743829cdef1810106d62d0f1c4fcAnthony Chen                throw new IllegalStateException("Subject view does not have a valid parent of type"
76349e2285f933743829cdef1810106d62d0f1c4fcAnthony Chen                        + " View. Parent is: " + mSubjectView.getParent());
77349e2285f933743829cdef1810106d62d0f1c4fcAnthony Chen            }
78349e2285f933743829cdef1810106d62d0f1c4fcAnthony Chen
79349e2285f933743829cdef1810106d62d0f1c4fcAnthony Chen            View parentView = (View) mSubjectView.getParent();
80349e2285f933743829cdef1810106d62d0f1c4fcAnthony Chen
81349e2285f933743829cdef1810106d62d0f1c4fcAnthony Chen            // The TouchDelegate needs to be set after the subject view has been laid out in
82349e2285f933743829cdef1810106d62d0f1c4fcAnthony Chen            // order to get the hit Rect. Use a post() to ensure this.
83349e2285f933743829cdef1810106d62d0f1c4fcAnthony Chen            parentView.post(() -> {
84349e2285f933743829cdef1810106d62d0f1c4fcAnthony Chen                Rect rect = new Rect();
85349e2285f933743829cdef1810106d62d0f1c4fcAnthony Chen                mSubjectView.getHitRect(rect);
86349e2285f933743829cdef1810106d62d0f1c4fcAnthony Chen
87349e2285f933743829cdef1810106d62d0f1c4fcAnthony Chen                // Ensure that the touch target for the icon is the minimum touch size.
88349e2285f933743829cdef1810106d62d0f1c4fcAnthony Chen                int hitWidth = Math.abs(rect.right - rect.left);
89349e2285f933743829cdef1810106d62d0f1c4fcAnthony Chen                if (hitWidth < size) {
90349e2285f933743829cdef1810106d62d0f1c4fcAnthony Chen                    int amountToIncrease = (size - hitWidth) / 2;
91349e2285f933743829cdef1810106d62d0f1c4fcAnthony Chen                    rect.left -= amountToIncrease;
92349e2285f933743829cdef1810106d62d0f1c4fcAnthony Chen                    rect.right += amountToIncrease;
93349e2285f933743829cdef1810106d62d0f1c4fcAnthony Chen                }
94349e2285f933743829cdef1810106d62d0f1c4fcAnthony Chen
95349e2285f933743829cdef1810106d62d0f1c4fcAnthony Chen                int hitHeight = Math.abs(rect.top - rect.bottom);
96349e2285f933743829cdef1810106d62d0f1c4fcAnthony Chen                if (hitHeight < size) {
97349e2285f933743829cdef1810106d62d0f1c4fcAnthony Chen                    int amountToIncrease = (size - hitHeight) / 2;
98349e2285f933743829cdef1810106d62d0f1c4fcAnthony Chen                    rect.top -= amountToIncrease;
99349e2285f933743829cdef1810106d62d0f1c4fcAnthony Chen                    rect.bottom += amountToIncrease;
100349e2285f933743829cdef1810106d62d0f1c4fcAnthony Chen                }
101349e2285f933743829cdef1810106d62d0f1c4fcAnthony Chen
102349e2285f933743829cdef1810106d62d0f1c4fcAnthony Chen                parentView.setTouchDelegate(new TouchDelegate(rect, mSubjectView));
103349e2285f933743829cdef1810106d62d0f1c4fcAnthony Chen            });
104349e2285f933743829cdef1810106d62d0f1c4fcAnthony Chen        }
105349e2285f933743829cdef1810106d62d0f1c4fcAnthony Chen    }
106349e2285f933743829cdef1810106d62d0f1c4fcAnthony Chen}
107