You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

ConstraintLayout 2.0.0-alpha4中MotionLayout+RecyclerView侧滑Item异常问题

Fixing MotionLayout Glitches in RecyclerView Items

Hey there, let's tackle those annoying issues you're facing with MotionLayout inside RecyclerView items—both the laggy swipe and the state duplication across items. Here's what's happening and how to fix it:

What's Causing the Problems?

  1. RecyclerView ViewHolder Reuse: RecyclerView recycles off-screen items to save memory. When you swipe an item, its MotionLayout state gets saved in the ViewHolder. When a new item (like position 13, 25, etc.) reuses that ViewHolder, it inherits the already-swiped state.
  2. Outdated MotionLayout Version: You're using 2.0.0-alpha4, which is an early preview build with known performance bugs and RecyclerView compatibility issues.

Step-by-Step Solutions

1. Fix State Duplication from ViewHolder Reuse

We need to reset the MotionLayout state every time a ViewHolder is bound or recycled.

Update Your Adapter Code

Add these methods to your RecyclerView Adapter:

override fun onBindViewHolder(holder: YourViewHolder, position: Int) {
    // Reset MotionLayout to initial state before binding new data
    holder.motionLayout.transitionToStart()
    
    // ... your existing data binding logic ...
    
    // Optional: If you need to preserve per-item swipe state, track it in a list
    // if (yourItemStateList[position]) holder.motionLayout.transitionToEnd()
}

override fun onViewRecycled(holder: YourViewHolder) {
    super.onViewRecycled(holder)
    // Reset state when ViewHolder is recycled
    holder.motionLayout.transitionToStart()
    holder.motionLayout.clearAnimation()
}

Disable Automatic State Saving (Optional)

Add this attribute to your MotionLayout to prevent it from saving state across reuse:

<androidx.constraintlayout.motion.widget.MotionLayout
    ...
    android:saveEnabled="false">

2. Fix Swipe Lag & Performance Issues

  • Upgrade ConstraintLayout: Ditch the alpha version for a stable release. Update your dependency to:
    implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
    
    (Or the latest stable version available—this fixes dozens of performance bugs from early alphas.)
  • Remove Debug Flags: You have app:showPaths="true" in your MotionLayout—this is for debugging only and adds unnecessary overhead. Remove it in production builds.
  • Simplify Layout Hierarchy: Check the included text layout to ensure it doesn't have unnecessary nested views, which can slow down swipe animations.

3. Preserve Per-Item Swipe State (If Needed)

If you want each item to keep its swipe state after scrolling, track states in your Adapter:

// Add a list to track each item's swipe state
private val itemSwipeStates = mutableListOf<Boolean>().apply {
    repeat(yourDataset.size) { add(false) }
}

override fun onBindViewHolder(holder: YourViewHolder, position: Int) {
    // Set initial state based on tracking list
    if (itemSwipeStates[position]) {
        holder.motionLayout.transitionToEnd()
    } else {
        holder.motionLayout.transitionToStart()
    }

    // Update state when animation completes
    holder.motionLayout.setTransitionListener(object : MotionLayout.TransitionListener {
        override fun onTransitionStarted(motionLayout: MotionLayout, startId: Int, endId: Int) {}
        override fun onTransitionChange(motionLayout: MotionLayout, startId: Int, endId: Int, progress: Float) {}
        override fun onTransitionCompleted(motionLayout: MotionLayout, currentId: Int) {
            itemSwipeStates[holder.adapterPosition] = currentId == R.id.end
        }
        override fun onTransitionTrigger(motionLayout: MotionLayout, triggerId: Int, positive: Boolean, progress: Float) {}
    })

    // ... rest of your binding logic ...
}

Your Original Code Reference

MotionLayout Item Layout

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.motion.widget.MotionLayout 
    xmlns:android="http://schemas.android.com/apk/res/android" 
    xmlns:app="http://schemas.android.com/apk/res-auto" 
    xmlns:tools="http://schemas.android.com/tools" 
    android:layout_width="match_parent" 
    android:layout_height="wrap_content" 
    app:layoutDescription="@xml/scene_btn"
    android:saveEnabled="false"> <!-- Add this line -->

    <androidx.cardview.widget.CardView 
        android:id="@+id/iv_recorded_program" 
        android:layout_width="wrap_content" 
        android:layout_height="wrap_content" 
        android:layout_gravity="center_horizontal" 
        app:cardElevation="1dp" 
        app:cardCornerRadius="0dp" 
        app:layout_constraintEnd_toStartOf="@+id/rl_recorded_program_info" 
        app:layout_constraintHorizontal_bias="0.5" 
        app:layout_constraintStart_toStartOf="parent" 
        app:layout_constraintTop_toTopOf="parent">

        <ImageView 
            style="@style/styleRecordedProgramImage" 
            android:layout_width="@dimen/guide_list_width" 
            android:layout_height="@dimen/guide_list_height" 
            android:visibility="visible" />
    </androidx.cardview.widget.CardView>

    <include 
        android:id="@+id/rl_recorded_program_info" 
        layout="@layout/text" 
        android:layout_width="0dp" 
        android:layout_height="@dimen/guide_list_height" 
        android:background="@color/colorPrimary" 
        android:visibility="visible" 
        app:layout_constraintEnd_toStartOf="@+id/button" 
        app:layout_constraintHorizontal_bias="0.5" 
        app:layout_constraintStart_toEndOf="@+id/iv_recorded_program" 
        app:layout_constraintTop_toTopOf="parent"/>

    <ImageView 
        android:id="@+id/button" 
        android:layout_width="0dp" 
        android:layout_height="@dimen/guide_list_height" 
        android:background="@color/colorAccent" 
        android:visibility="visible" 
        app:layout_constraintEnd_toEndOf="parent" 
        app:layout_constraintTop_toTopOf="parent"/>
</androidx.constraintlayout.motion.widget.MotionLayout>

MotionScene File (scene_btn.xml)

<?xml version="1.0" encoding="utf-8"?>
<MotionScene xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" >
    <Transition 
        app:constraintSetEnd="@+id/end" 
        app:constraintSetStart="@+id/start" 
        app:duration="1000">
        <OnSwipe 
            app:dragDirection="dragRight" 
            app:touchAnchorId="@id/rl_recorded_program_info" 
            app:touchAnchorSide="right"/>
    </Transition>

    <ConstraintSet android:id="@+id/start">
        <Constraint 
            android:id="@id/iv_recorded_program" 
            android:layout_width="@dimen/guide_list_width" 
            android:layout_height="@dimen/guide_list_height" 
            android:elevation="10dp" 
            android:visibility="visible" 
            app:layout_constraintEnd_toStartOf="@+id/rl_recorded_program_info" 
            app:layout_constraintHorizontal_bias="0.5" 
            app:layout_constraintStart_toStartOf="parent" 
            app:layout_constraintTop_toTopOf="parent"/>
        <Constraint 
            android:id="@id/rl_recorded_program_info" 
            android:layout_width="0dp" 
            android:layout_height="@dimen/guide_list_height" 
            android:visibility="visible" 
            app:layout_constraintEnd_toStartOf="@+id/button" 
            app:layout_constraintHorizontal_bias="0.5" 
            app:layout_constraintStart_toEndOf="@+id/iv_recorded_program" 
            app:layout_constraintTop_toTopOf="parent"/>
        <Constraint 
            android:id="@id/button" 
            android:layout_width="0dp" 
            android:layout_height="@dimen/guide_list_height" 
            android:visibility="visible" 
            app:layout_constraintEnd_toEndOf="parent" 
            app:layout_constraintTop_toTopOf="parent"/>
    </ConstraintSet>

    <ConstraintSet android:id="@+id/end">
        <Constraint 
            android:id="@id/iv_recorded_program" 
            android:layout_width="@dimen/guide_list_width" 
            android:layout_height="@dimen/guide_list_height" 
            android:elevation="10dp" 
            android:visibility="visible" 
            app:layout_constraintEnd_toStartOf="parent" 
            app:layout_constraintStart_toStartOf="parent" 
            app:layout_constraintTop_toTopOf="parent"/>
        <Constraint 
            android:id="@id/rl_recorded_program_info" 
            android:layout_width="0dp" 
            android:layout_height="@dimen/guide_list_height" 
            app:layout_constraintEnd_toStartOf="@+id/button" 
            app:layout_constraintStart_toStartOf="@+id/iv_recorded_program" 
            app:layout_constraintTop_toTopOf="parent"/>
        <Constraint 
            android:id="@id/button" 
            android:layout_width="0dp" 
            android:layout_height="@dimen/guide_list_height" 
            app:layout_constraintEnd_toEndOf="parent" 
            app:layout_constraintStart_toEndOf="@+id/iv_recorded_program" 
            app:layout_constraintTop_toTopOf="parent"/>
    </ConstraintSet>
</MotionScene>

内容的提问来源于stack exchange,提问作者Subhankar

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.12 05:23:25