0

i try to implement programmatically version of MotionLayout by extending it. And i have a base activity ayout using RecyclerView. However, when i add my motion layout as an item of the RecyclerView, the view is not recycled when i try to scrolling up and down. And it works well when i use as a normal view (act as single view).

Here is the preview:

enter image description here

class SimpleMotionLayout @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
) : MotionLayout(context, attrs, defStyleAttr) {

    private val motionScene = MotionScene(this)
    private var _simpleTransition: MotionScene.Transition? = null

    private lateinit var squareView: View

    init {
        layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)
        initDefaultConstraint(this)
        setMotion()
    }

    fun setMotion() {
        _simpleTransition = createPlaceholderTransition(motionScene)
        setDebugMode(DEBUG_SHOW_PATH)

        /**
         * The order matters here.
         * [MotionScene.addTransition] adds the transition to the scene while
         * [MotionScene.setTransition] sets the transition to be the current transition.
         */
        motionScene.addTransition(_simpleTransition)
        motionScene.setTransition(_simpleTransition)
        
        scene = motionScene
        setTransition(_simpleTransition!!.id)

        animateView()
    }

    fun setSquareColor(color: Int) {
        squareView.setBackgroundColor(color)
    }

    fun initDefaultConstraint(motionLayout: ConstraintLayout) {
        // View
        squareView = View(context).apply {
            id = R.id.default_button
            setBackgroundColor(Color.BLACK)
        }
        motionLayout.addView(
            squareView,
            LayoutParams(
                fromDp(context, 52),
                fromDp(context, 52)
            )
        )

        val set = ConstraintSet()
        set.clone(motionLayout)

        // Setup constraint set to TOP, LEFT to the Parent
        set.connect(
            squareView.id,
            TOP,
            PARENT_ID,
            TOP
        )
        set.connect(
            squareView.id,
            START,
            PARENT_ID,
            START
        )

        set.applyTo(motionLayout)
    }

    private fun setToEnd() {
        val endSet = getConstraintSet(_simpleTransition?.endConstraintSetId ?: return)
        endSet.clear(R.id.default_button, START)
        endSet.connect(
            R.id.default_button,
            END,
            PARENT_ID,
            END
        )
    }

    fun animateView() {
        setToEnd()
        _simpleTransition?.setOnSwipe(
            OnSwipe().apply {
                dragDirection = DRAG_END
                touchAnchorId = R.id.default_button
                touchAnchorSide = SIDE_START
                onTouchUp = ON_UP_AUTOCOMPLETE_TO_START
                setMaxAcceleration(500)
            }
        )
        setTransition(_simpleTransition!!.id)
    }

    // Placeholder transition??
    fun createPlaceholderTransition(motionScene: MotionScene): MotionScene.Transition? {
        val startSetId = View.generateViewId()
        val startSet = ConstraintSet()
        startSet.clone(this)

        val endSetId = View.generateViewId()
        val endSet = ConstraintSet()
        endSet.clone(this)

        val transitionId = View.generateViewId()

        return TransitionBuilder.buildTransition(
            motionScene,
            transitionId,
            startSetId, startSet,
            endSetId, endSet
        )
    }

    /**
     * Get px from dp
     */
    private fun fromDp(context: Context, inDp: Int): Int {
        val scale = context.resources.displayMetrics.density
        return (inDp * scale).toInt()
    }
}

Below is my adapter:

class SimpleMotionLayoutAdapter : RecyclerView.Adapter<SimpleMotionLayoutAdapter.ViewHolder>() {

    val items = mutableListOf<Int>() // colors

    class ViewHolder(val view: View) : RecyclerView.ViewHolder(view) {
        fun setColor(color: Int) {
            (view as SimpleMotionLayout).setSquareColor(color)
        }
    }

    override fun getItemId(position: Int): Long {
        return position.toLong()
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        val view = SimpleMotionLayout(parent.context)
        return ViewHolder(view)
    }

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        holder.setColor(items[position])
    }

    override fun getItemCount(): Int = items.size

    companion object {
        const val TYPE_NORMAL = 0
        const val TYPE_EXCEPTIONAL = 1
    }
}

Am i missing implementation? Thank you

fanjavaid
  • 1,676
  • 8
  • 34
  • 65

1 Answers1

0

In general you need to cache and restore the state of the MotionLayout when it gets Recycled. Right now in onBindViewHolder you only set the Color. Remember RecyclerView keeps a only a screens worth (+ about 3) of ViewHolders and reuses them using onBindViewHolder At minimum you need to set the Progress of the MotionLayout. Due to differences in timing you may need to set the progress in an onPost

hoford
  • 4,918
  • 2
  • 19
  • 19