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