How to swipe one row of RecyclerView programatically (at run time)?

半城伤御伤魂 提交于 2020-12-29 13:22:52

问题


I have a RecyclerView with items inside like this:

I use ItemTouchHelper.SimpleCallback to listen for swipe and onChildDraw() to draw a canvas when items are swiped:

A bit more swipe:

My problem:

I want to simulate a swipe (at run time) just on first item inside the list of items; I need first item to go (more or less ) -100 px on its X axis and then go back to original position. How to achieve this?


回答1:


I created some utility method in case someone needs it

import android.animation.ValueAnimator
import android.os.SystemClock
import android.view.MotionEvent
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView

/**
 * @author Oleh Haidaienko
 * @since 29.07.2020
 */
object SwipeUtils {
    /**
     * Programmatically swipe RecyclerView item
     * @param recyclerView RecyclerView which item will be swiped
     * @param index Position of item
     * @param distance Swipe distance
     * @param direction Swipe direction, can be [ItemTouchHelper.START] or [ItemTouchHelper.END]
     * @param time Animation time in milliseconds
     */
    fun swipeRecyclerViewItem(
        recyclerView: RecyclerView,
        index: Int,
        distance: Int,
        direction: Int,
        time: Long
    ) {
        val childView = recyclerView.getChildAt(index) ?: return
        val x = childView.width / 2F
        val viewLocation = IntArray(2)
        childView.getLocationInWindow(viewLocation)
        val y = (viewLocation[1] + childView.height) / 2F
        val downTime = SystemClock.uptimeMillis()
        recyclerView.dispatchTouchEvent(
            MotionEvent.obtain(
                downTime,
                downTime,
                MotionEvent.ACTION_DOWN,
                x,
                y,
                0
            )
        )
        ValueAnimator.ofInt(0, distance).apply {
            duration = time
            addUpdateListener {
                val dX = it.animatedValue as Int
                val mX = when (direction) {
                    ItemTouchHelper.END -> x + dX
                    ItemTouchHelper.START -> x - dX
                    else -> 0F
                }
                recyclerView.dispatchTouchEvent(
                    MotionEvent.obtain(
                        downTime,
                        SystemClock.uptimeMillis(),
                        MotionEvent.ACTION_MOVE,
                        mX,
                        y,
                        0
                    )
                )
            }
        }.start()
    }
}



回答2:


i guess you could achieve that with RecyclerView by simply registering DataObserver for your Adapter which will notify you when an item has been removed, inserted or changed. lets consider this scenario

    adapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() {
        @Override public void onChanged() {
            super.onChanged();
             adapter.unregisterAdapterDataObserver(this);//unregister the adapter since no longer needed.
        }
    });

in above code will notify you when an item has been inserted you could then use recyclerView.getChildAt(0).translateX(-100); to simulate the swipe and simply recyclerView.getChildAt(0).translateX(0); to reposition the X of the view back within the method onChanged().




回答3:


This extension functions could help you:

fun ViewGroup.performSwipeToLeft(target: View, distance: Float) {
    this.performSwipe(target, distanceX = -distance, distanceY = 0f)
}
fun ViewGroup.performSwipeToRight(target: View, distance: Float) {
    this.performSwipe(target, distanceX = +distance, distanceY = 0f)
}

fun ViewGroup.performSwipe(target: View, distanceX: Float, distanceY: Float) {
    val parentCoords = intArrayOf(0, 0)
    this.getLocationInWindow(parentCoords)

    val childCoords = intArrayOf(0, 0)
    target.getLocationInWindow(childCoords)

    val initGlobalX = childCoords[0].toFloat() + 1f
    val initGlobalY = childCoords[1].toFloat() + 1f

    val initLocalX = (childCoords[0] - parentCoords[0]).toFloat() + 1f
    val initLocalY = (childCoords[1] - parentCoords[1]).toFloat() + 1f

    val downTime = SystemClock.uptimeMillis()
    var eventTime = SystemClock.uptimeMillis()

    this.dispatchTouchEvent(
        MotionEvent.obtain(
            downTime,
            eventTime,
            MotionEvent.ACTION_DOWN,
            initGlobalX,
            initGlobalY,
            0
        ).apply {
            setLocation(initLocalX, initLocalY)
            source = InputDevice.SOURCE_TOUCHSCREEN
        }
    )

    val steps = 20
    var i = 0
    while (i in 0..steps) {
        val globalX = initGlobalX + i * distanceX / steps
        val globalY = initGlobalY + i * distanceY / steps
        val localX = initLocalX + i * distanceX / steps
        val localY = initLocalY + i * distanceY / steps
        if (globalX <= 10f || globalY <= 10f) {
            break
        }
        this.dispatchTouchEvent(
            MotionEvent.obtain(
                downTime,
                ++eventTime,
                MotionEvent.ACTION_MOVE,
                globalX,
                globalY,
                0
            ).apply {
                setLocation(localX, localY)
                source = InputDevice.SOURCE_TOUCHSCREEN
            }
        )
        i++
    }

    this.dispatchTouchEvent(
        MotionEvent.obtain(
            downTime,
            ++eventTime,
            MotionEvent.ACTION_UP,
            initGlobalX + i * distanceX,
            initGlobalY + i * distanceY,
            0
        ).apply {
            setLocation(initLocalX + i * distanceX, initLocalY + i * distanceY)
            source = InputDevice.SOURCE_TOUCHSCREEN
        }
    )
}

In your case you could use this as follows:

recylerView.performSwipeToLeft(recyclerView.getChildAt(0), 100f)


来源:https://stackoverflow.com/questions/34401166/how-to-swipe-one-row-of-recyclerview-programatically-at-run-time

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!