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?
- 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.
- 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:
(Or the latest stable version available—this fixes dozens of performance bugs from early alphas.)implementation 'androidx.constraintlayout:constraintlayout:2.1.4' - 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
textlayout 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
相关产品推荐
相关产品推荐

