1 /* <lambda>null2 * Copyright (C) 2024 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.wallpaper.picker.preview.ui.view 18 19 import android.content.Context 20 import android.graphics.Rect 21 import android.util.AttributeSet 22 import android.view.GestureDetector 23 import android.view.GestureDetector.SimpleOnGestureListener 24 import android.view.MotionEvent 25 import android.view.ViewGroup 26 import android.view.ViewParent 27 import androidx.constraintlayout.motion.widget.MotionLayout 28 import androidx.core.view.ancestors 29 import androidx.core.view.children 30 31 /** A [MotionLayout] that performs click on one of its child if it is the recipient. */ 32 class ClickableMotionLayout(context: Context, attrs: AttributeSet?) : MotionLayout(context, attrs) { 33 34 /** True for this view to intercept all motion events. */ 35 var shouldInterceptTouch = true 36 37 private val clickableViewIds = mutableListOf<Int>() 38 private val singleTapDetector = 39 GestureDetector( 40 context, 41 object : SimpleOnGestureListener() { 42 override fun onSingleTapUp(event: MotionEvent): Boolean { 43 // Check if any immediate child view is clicked 44 children 45 .find { 46 isEventPointerInRect(event, Rect(it.left, it.top, it.right, it.bottom)) 47 } 48 ?.let { child -> 49 // Find all the clickable ids in the hierarchy of the clicked view and 50 // perform click on the exact view that should be clicked. 51 clickableViewIds 52 .mapNotNull { child.findViewById(it) } 53 .find { clickableView -> 54 if (clickableView == child) { 55 true 56 } else { 57 // Find ancestors of this clickable view up until this 58 // layout and transform coordinates to align with motion 59 // event. 60 val ancestors = clickableView.ancestors 61 var ancestorsLeft = 0 62 var ancestorsTop = 0 63 ancestors 64 .filter { 65 ancestors.indexOf(it) <= 66 ancestors.indexOf(child as ViewParent) 67 } 68 .forEach { 69 it as ViewGroup 70 ancestorsLeft += it.left 71 ancestorsTop += it.top 72 } 73 isEventPointerInRect( 74 event, 75 Rect( 76 /* left= */ ancestorsLeft + clickableView.left, 77 /* top= */ ancestorsTop + clickableView.top, 78 /* right= */ ancestorsLeft + clickableView.right, 79 /* bottom= */ ancestorsTop + clickableView.bottom, 80 ), 81 ) 82 } 83 } 84 ?.performClick() 85 } 86 87 return true 88 } 89 }, 90 ) 91 92 override fun onInterceptTouchEvent(event: MotionEvent): Boolean { 93 // MotionEvent.ACTION_DOWN is the first MotionEvent received and is necessary to detect 94 // various gesture, returns true to intercept all event so they are forwarded into 95 // onTouchEvent. 96 return shouldInterceptTouch 97 } 98 99 override fun onTouchEvent(event: MotionEvent): Boolean { 100 super.onTouchEvent(event) 101 102 // Handle single tap 103 singleTapDetector.onTouchEvent(event) 104 105 return true 106 } 107 108 fun setClickableViewIds(ids: List<Int>) { 109 clickableViewIds.apply { 110 clear() 111 addAll(ids) 112 } 113 } 114 115 private fun isEventPointerInRect(e: MotionEvent, rect: Rect): Boolean { 116 return e.x >= rect.left && e.x <= rect.right && e.y >= rect.top && e.y <= rect.bottom 117 } 118 } 119