/* * Copyright 2018 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package androidx.car.widget; import android.graphics.Rect; import android.view.TouchDelegate; import android.view.View; import androidx.annotation.NonNull; /** * Helper class that will ensure that a given view meets a minimum touch target size that is * specified. */ class MinTouchTargetHelper { private MinTouchTargetHelper() {} /** * Sets up the view that will be checked and ensured to meet a minimum touch target size. * * @param view The view that to be checked; cannot be {@code null}. * @return A {@link TouchTargetSubject} that can be customized with touch target information. */ static TouchTargetSubject ensureThat(View view) { if (view == null) { throw new IllegalArgumentException("View cannot be null."); } return new TouchTargetSubject(view); } /** * A class to encapsulate information concerning a view whose target target needs to be * expanded to meet a minimum touch size. * *

This class should not be created directly. Instead use * {@link MinTouchTargetHelper#ensureThat(View)} to get an instance of this class. */ static class TouchTargetSubject { private View mSubjectView; private TouchTargetSubject(@NonNull View subject) { mSubjectView = subject; } /** * Sets the minimum touch target size in pixels for the subject view. Calling this method * will also set up the framework to do this assurance. * *

Under the hood, this method uses a {@link TouchDelegate} to handle the delegation, * meaning that only one child view can be set per parent. * * @param size The minimum touch target size in pixels. */ void hasMinTouchSize(int size) { if (size <= 0) { throw new IllegalStateException( "Minimum touch target size must be greater than 0."); } if (!(mSubjectView.getParent() instanceof View)) { throw new IllegalStateException("Subject view does not have a valid parent of type" + " View. Parent is: " + mSubjectView.getParent()); } View parentView = (View) mSubjectView.getParent(); // The TouchDelegate needs to be set after the subject view has been laid out in // order to get the hit Rect. Use a post() to ensure this. parentView.post(() -> { Rect rect = new Rect(); mSubjectView.getHitRect(rect); // Ensure that the touch target for the icon is the minimum touch size. int hitWidth = Math.abs(rect.right - rect.left); if (hitWidth < size) { int amountToIncrease = (size - hitWidth) / 2; rect.left -= amountToIncrease; rect.right += amountToIncrease; } int hitHeight = Math.abs(rect.top - rect.bottom); if (hitHeight < size) { int amountToIncrease = (size - hitHeight) / 2; rect.top -= amountToIncrease; rect.bottom += amountToIncrease; } parentView.setTouchDelegate(new TouchDelegate(rect, mSubjectView)); }); } } }