diff --git a/api/current.txt b/api/current.txt index 4c9c96a9..e585b628 100644 --- a/api/current.txt +++ b/api/current.txt @@ -641,9 +641,17 @@ package androidx.core.view { public final class ViewKt { ctor public ViewKt(); method @RequiresApi(16) public static void announceForAccessibility(android.view.View, @StringRes int resource); + method public static void doOnActionCancel(android.view.View, kotlin.jvm.functions.Function1 action); + method public static void doOnActionDown(android.view.View, kotlin.jvm.functions.Function1 action); + method public static void doOnActionMove(android.view.View, kotlin.jvm.functions.Function1 action); + method public static void doOnActionOutside(android.view.View, kotlin.jvm.functions.Function1 action); + method public static void doOnActionPointerDown(android.view.View, kotlin.jvm.functions.Function1 action); + method public static void doOnActionPointerUp(android.view.View, kotlin.jvm.functions.Function1 action); + method public static void doOnActionUp(android.view.View, kotlin.jvm.functions.Function1 action); method public static void doOnLayout(android.view.View, kotlin.jvm.functions.Function1 action); method public static void doOnNextLayout(android.view.View, kotlin.jvm.functions.Function1 action); method public static void doOnPreDraw(android.view.View, kotlin.jvm.functions.Function1 action); + method public static void doOnTouch(android.view.View, kotlin.jvm.functions.Function1 onActionDown = "{}", kotlin.jvm.functions.Function1 onActionUp = "{}", kotlin.jvm.functions.Function1 onActionMove = "{}", kotlin.jvm.functions.Function1 onActionCancel = "{}", kotlin.jvm.functions.Function1 onActionOutside = "{}", kotlin.jvm.functions.Function1 onActionPointerDown = "{}", kotlin.jvm.functions.Function1 onActionPointerUp = "{}"); method public static boolean isGone(android.view.View); method public static boolean isInvisible(android.view.View); method public static boolean isVisible(android.view.View); diff --git a/src/androidTest/java/androidx/core/view/ViewTest.kt b/src/androidTest/java/androidx/core/view/ViewTest.kt index 1216f4cd..e48a1e63 100644 --- a/src/androidTest/java/androidx/core/view/ViewTest.kt +++ b/src/androidTest/java/androidx/core/view/ViewTest.kt @@ -18,8 +18,10 @@ package androidx.core.view import android.graphics.Bitmap import android.graphics.Color +import android.os.SystemClock import android.support.test.InstrumentationRegistry import android.view.LayoutInflater +import android.view.MotionEvent import android.view.View import android.view.ViewGroup import android.widget.LinearLayout @@ -37,6 +39,15 @@ class ViewTest { private val context = InstrumentationRegistry.getContext() private val view = View(context) + private fun obtainMotionEvent(eventType: Int) = MotionEvent.obtain( + SystemClock.uptimeMillis(), + SystemClock.uptimeMillis() + 100, + eventType, + 0f, + 0f, + 0 + ) + @Test fun doOnNextLayout() { var calls = 0 @@ -270,4 +281,74 @@ class ViewTest { val resolvedText = context.getText(R.string.text) assertEquals(testView.announcement, resolvedText) } + + @Test fun doOnActionDown() { + var called = false + view.doOnActionDown { + called = true + } + + view.dispatchTouchEvent(obtainMotionEvent(MotionEvent.ACTION_DOWN)) + assertTrue(called) + } + + @Test fun doOnActionUp() { + var called = false + view.doOnActionUp { + called = true + } + + view.dispatchTouchEvent(obtainMotionEvent(MotionEvent.ACTION_UP)) + assertTrue(called) + } + + @Test fun doOnActionMove() { + var called = false + view.doOnActionMove { + called = true + } + + view.dispatchTouchEvent(obtainMotionEvent(MotionEvent.ACTION_MOVE)) + assertTrue(called) + } + + @Test fun doOnActionCancel() { + var called = false + view.doOnActionCancel { + called = true + } + + view.dispatchTouchEvent(obtainMotionEvent(MotionEvent.ACTION_CANCEL)) + assertTrue(called) + } + + @Test fun doOnActionOutside() { + var called = false + view.doOnActionOutside { + called = true + } + + view.dispatchTouchEvent(obtainMotionEvent(MotionEvent.ACTION_OUTSIDE)) + assertTrue(called) + } + + @Test fun doOnActionPointerDown() { + var called = false + view.doOnActionPointerDown { + called = true + } + + view.dispatchTouchEvent(obtainMotionEvent(MotionEvent.ACTION_POINTER_DOWN)) + assertTrue(called) + } + + @Test fun doOnActionPointerUp() { + var called = false + view.doOnActionPointerUp { + called = true + } + + view.dispatchTouchEvent(obtainMotionEvent(MotionEvent.ACTION_POINTER_UP)) + assertTrue(called) + } } diff --git a/src/main/java/androidx/core/view/View.kt b/src/main/java/androidx/core/view/View.kt index f0e29030..6af613af 100644 --- a/src/main/java/androidx/core/view/View.kt +++ b/src/main/java/androidx/core/view/View.kt @@ -23,6 +23,14 @@ import android.support.annotation.Px import android.support.annotation.RequiresApi import android.support.annotation.StringRes import android.support.v4.view.ViewCompat +import android.view.MotionEvent +import android.view.MotionEvent.ACTION_CANCEL +import android.view.MotionEvent.ACTION_DOWN +import android.view.MotionEvent.ACTION_MOVE +import android.view.MotionEvent.ACTION_OUTSIDE +import android.view.MotionEvent.ACTION_POINTER_DOWN +import android.view.MotionEvent.ACTION_POINTER_UP +import android.view.MotionEvent.ACTION_UP import android.view.View import android.view.ViewGroup import android.view.ViewTreeObserver @@ -289,3 +297,89 @@ inline fun View.updateLayoutParams(block: T block(params) layoutParams = params } + +/** + * Add an [action] which will be invoked when a pressed gesture has started + * + * @see View.setOnTouchListener + */ +inline fun View.doOnActionDown(crossinline action: (event: MotionEvent) -> Unit) = + doOnTouch(onActionDown = action) + +/** + * Add an [action] which will be invoked when a pressed gesture has finished + * + * @see View.setOnTouchListener + */ +inline fun View.doOnActionUp(crossinline action: (event: MotionEvent) -> Unit) = + doOnTouch(onActionUp = action) + +/** + * Add an [action] which will be invoked when a change has happened during a press gesture + * + * @see View.setOnTouchListener + */ +inline fun View.doOnActionMove(crossinline action: (event: MotionEvent) -> Unit) = + doOnTouch(onActionMove = action) + +/** + * Add an [action] which will be invoked when the current gesture has been aborted + * + * @see View.setOnTouchListener + */ +inline fun View.doOnActionCancel(crossinline action: (event: MotionEvent) -> Unit) = + doOnTouch(onActionCancel = action) + +/** + * Add an [action] which will be invoked when a movement has happened outside of the + * normal bounds of the UI element. This does not provide a full gesture, + * but only the initial location of the movement/touch. + * + * @see View.setOnTouchListener + */ +inline fun View.doOnActionOutside(crossinline action: (event: MotionEvent) -> Unit) = + doOnTouch(onActionOutside = action) + +/** + * Add an [action] which will be invoked when a non-primary pointer has gone down. + * Use [event.actionIndex] to retrieve the index of the pointer that changed. + * + * @see View.setOnTouchListener + */ +inline fun View.doOnActionPointerDown(crossinline action: (event: MotionEvent) -> Unit) = + doOnTouch(onActionPointerDown = action) + +/** + * Add an [action] which will be invoked when a non-primary pointer has gone up. + * Use [event.actionIndex] to retrieve the index of the pointer that changed. + * + * @see View.setOnTouchListener + */ +inline fun View.doOnActionPointerUp(crossinline action: (event: MotionEvent) -> Unit) = + doOnTouch(onActionPointerUp = action) + +/** + * Add a touch listener to this View using the provided actions. + */ +inline fun View.doOnTouch( + crossinline onActionDown: (event: MotionEvent) -> Unit = {}, + crossinline onActionUp: (event: MotionEvent) -> Unit = {}, + crossinline onActionMove: (event: MotionEvent) -> Unit = {}, + crossinline onActionCancel: (event: MotionEvent) -> Unit = {}, + crossinline onActionOutside: (event: MotionEvent) -> Unit = {}, + crossinline onActionPointerDown: (event: MotionEvent) -> Unit = {}, + crossinline onActionPointerUp: (event: MotionEvent) -> Unit = {} +) { + setOnTouchListener { _, event -> + when (event.action) { + ACTION_DOWN -> onActionDown(event) + ACTION_UP -> onActionUp(event) + ACTION_MOVE -> onActionMove(event) + ACTION_CANCEL -> onActionCancel(event) + ACTION_OUTSIDE -> onActionOutside(event) + ACTION_POINTER_DOWN -> onActionPointerDown(event) + ACTION_POINTER_UP -> onActionPointerUp(event) + } + return@setOnTouchListener true + } +}