1 /* 2 * Copyright (C) 2022 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 18 package com.android.wallpaper.picker.customization.ui.section 19 20 import android.content.Context 21 import android.util.AttributeSet 22 import android.view.MotionEvent 23 import android.view.ViewConfiguration 24 import android.widget.FrameLayout 25 import com.android.wallpaper.R 26 import com.android.wallpaper.util.RtlUtils 27 import kotlin.math.pow 28 import kotlin.math.sqrt 29 30 class ScreenPreviewClickView( 31 context: Context, 32 attrs: AttributeSet?, 33 ) : 34 FrameLayout( 35 context, 36 attrs, 37 ) { 38 39 private var downX = 0f 40 private var downY = 0f 41 private var onPreviewClicked: (() -> Unit)? = null 42 // isStart true means the start side; otherwise the end side 43 private var onSideClicked: ((isStart: Boolean) -> Unit)? = null 44 onInterceptTouchEventnull45 override fun onInterceptTouchEvent(event: MotionEvent): Boolean { 46 if (event.actionMasked == MotionEvent.ACTION_DOWN) { 47 downX = event.x 48 downY = event.y 49 } 50 51 // We want to intercept clicks so the Carousel MotionLayout child doesn't prevent users from 52 // clicking on the screen preview. 53 if (isClick(event, downX, downY)) { 54 val viewCenterX = width / 2F 55 val halfPreviewWidth = 56 context.resources.getDimensionPixelSize(R.dimen.screen_preview_width) / 2F 57 val leftXBound = viewCenterX - halfPreviewWidth 58 val rightXBound = viewCenterX + halfPreviewWidth 59 val isRtl = RtlUtils.isRtl(context) 60 when { 61 downX in (leftXBound..rightXBound) -> onPreviewClicked?.invoke() 62 downX < leftXBound -> onSideClicked?.invoke(!isRtl) 63 downX > rightXBound -> onSideClicked?.invoke(isRtl) 64 } 65 return true 66 } 67 return super.onInterceptTouchEvent(event) 68 } 69 setOnPreviewClickedListenernull70 fun setOnPreviewClickedListener(onPreviewClicked: (() -> Unit)) { 71 this.onPreviewClicked = onPreviewClicked 72 } 73 setOnSideClickedListenernull74 fun setOnSideClickedListener(onSideClicked: ((isStart: Boolean) -> Unit)) { 75 this.onSideClicked = onSideClicked 76 } 77 78 companion object { isClicknull79 private fun isClick(event: MotionEvent, downX: Float, downY: Float): Boolean { 80 return when { 81 // It's not a click if the event is not an UP action (though it may become one 82 // later, when/if an UP is received). 83 event.actionMasked != MotionEvent.ACTION_UP -> false 84 // It's not a click if too much time has passed between the down and the current 85 // event. 86 gestureElapsedTime(event) > ViewConfiguration.getTapTimeout() -> false 87 // It's not a click if the touch traveled too far. 88 distanceMoved(event, downX, downY) > ViewConfiguration.getTouchSlop() -> false 89 // Otherwise, this is a click! 90 else -> true 91 } 92 } 93 94 /** 95 * Returns the distance that the pointer traveled in the touch gesture the given event is 96 * part of. 97 */ distanceMovednull98 private fun distanceMoved(event: MotionEvent, downX: Float, downY: Float): Float { 99 val deltaX = event.x - downX 100 val deltaY = event.y - downY 101 return sqrt(deltaX.pow(2) + deltaY.pow(2)) 102 } 103 104 /** 105 * Returns the elapsed time since the touch gesture the given event is part of has begun. 106 */ gestureElapsedTimenull107 private fun gestureElapsedTime(event: MotionEvent): Long { 108 return event.eventTime - event.downTime 109 } 110 } 111 } 112