如何在RecyclerView滚动时流畅隐藏上方的ChipGroup视图?
解决RecyclerView滚动时ChipGroup卡顿隐藏的问题
我来帮你搞定这个滚动卡顿的问题!你当前的实现之所以卡顿,主要是因为每次滚动都频繁触发视图状态变更和TransitionManager,导致UI线程被反复占用,自然就拖慢了滚动速度。下面给你几个从简单到进阶的优化方案,保证滚动丝滑:
方案1:优化现有实现,减少不必要的性能开销
先从最小改动入手,修复当前代码里的低效问题:
核心优化点:
- 提前缓存视图引用,避免每次滚动都调用
findViewById - 提前获取ChipGroup的高度,避免重复计算
- 增加滚动阈值,避免微小滚动就触发操作
- 用状态标记避免重复执行相同的显示/隐藏逻辑
修改后的代码:
// 提前声明变量,缓存视图和状态 private ChipGroup chipGroup; private FrameLayout frameLayout; private int chipGroupHeight; private boolean isChipGroupVisible = true; private static final int SCROLL_THRESHOLD = 10; // 滚动阈值,避免微小滚动触发 // 初始化阶段完成视图绑定和高度获取 chipGroup = root.findViewById(R.id.chipGroup); frameLayout = root.findViewById(R.id.frameLayout); // 等待视图布局完成后获取高度(避免直接getHeight()返回0) chipGroup.post(() -> { chipGroupHeight = chipGroup.getHeight(); mRecyclerView.setPadding(0, chipGroupHeight, 0, 0); }); // 滚动监听优化 mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { @Override public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) { super.onScrolled(recyclerView, dx, dy); if (!HIDE_VIEWS_ON_SCOLL) return; // 向下滚动,且ChipGroup当前可见 if (dy > SCROLL_THRESHOLD && isChipGroupVisible) { isChipGroupVisible = false; TransitionManager.beginDelayedTransition(frameLayout, new ChangeBounds()); chipGroup.setVisibility(View.GONE); mRecyclerView.setPadding(0, 0, 0, 0); } // 向上滚动,且ChipGroup当前隐藏 else if (dy < -SCROLL_THRESHOLD && !isChipGroupVisible) { isChipGroupVisible = true; TransitionManager.beginDelayedTransition(frameLayout, new ChangeBounds()); chipGroup.setVisibility(View.VISIBLE); mRecyclerView.setPadding(0, chipGroupHeight, 0, 0); } } });
方案2:用属性动画替代直接视图状态变更(更流畅)
直接设置View.GONE/VISIBLE会触发视图的测量和重布局,而属性动画只是修改视图的视觉位置,性能更优:
实现代码:
private ChipGroup chipGroup; private int chipGroupHeight; private boolean isChipGroupVisible = true; private static final int SCROLL_THRESHOLD = 10; private static final int ANIM_DURATION = 200; // 动画时长 // 初始化 chipGroup = root.findViewById(R.id.chipGroup); chipGroup.post(() -> chipGroupHeight = chipGroup.getHeight()); mRecyclerView.setPadding(0, chipGroupHeight, 0, 0); // 滚动监听 mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { private ObjectAnimator hideAnimator; private ObjectAnimator showAnimator; @Override public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) { super.onScrolled(recyclerView, dx, dy); if (!HIDE_VIEWS_ON_SCOLL) return; // 向下滚动隐藏ChipGroup if (dy > SCROLL_THRESHOLD && isChipGroupVisible) { isChipGroupVisible = false; // 取消可能正在运行的显示动画 if (showAnimator != null && showAnimator.isRunning()) showAnimator.cancel(); // 创建隐藏动画:向上平移ChipGroup自身高度 if (hideAnimator == null) { hideAnimator = ObjectAnimator.ofFloat(chipGroup, "translationY", 0, -chipGroupHeight); hideAnimator.setDuration(ANIM_DURATION); hideAnimator.addListener(new Animator.AnimatorListener() { @Override public void onAnimationEnd(Animator animation) { chipGroup.setVisibility(View.INVISIBLE); // 动画结束后隐藏,避免误触 } @Override public void onAnimationStart(Animator animation) {} @Override public void onAnimationCancel(Animator animation) {} @Override public void onAnimationRepeat(Animator animation) {} }); } hideAnimator.start(); // 同步动画RecyclerView的paddingTop ObjectAnimator.ofInt(mRecyclerView, "paddingTop", chipGroupHeight, 0).setDuration(ANIM_DURATION).start(); } // 向上滚动显示ChipGroup else if (dy < -SCROLL_THRESHOLD && !isChipGroupVisible) { isChipGroupVisible = true; chipGroup.setVisibility(View.VISIBLE); // 先显示视图,否则动画看不到 // 取消可能正在运行的隐藏动画 if (hideAnimator != null && hideAnimator.isRunning()) hideAnimator.cancel(); // 创建显示动画:平移回原位置 if (showAnimator == null) { showAnimator = ObjectAnimator.ofFloat(chipGroup, "translationY", -chipGroupHeight, 0); showAnimator.setDuration(ANIM_DURATION); } showAnimator.start(); // 同步动画RecyclerView的paddingTop ObjectAnimator.ofInt(mRecyclerView, "paddingTop", 0, chipGroupHeight).setDuration(ANIM_DURATION).start(); } } });
方案3:用CoordinatorLayout+自定义Behavior(最优解)
如果可以调整布局结构,使用CoordinatorLayout的嵌套滚动机制是最优雅、性能最好的方案,和Material Design的AppBarLayout原理一致:
1. 修改布局为CoordinatorLayout
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent"> <!-- ChipGroup绑定自定义Behavior --> <com.google.android.material.chip.ChipGroup android:id="@+id/chipGroup" android:layout_width="match_parent" android:layout_height="wrap_content" android:padding="8dp" app:layout_behavior=".ChipGroupScrollBehavior"> <!-- 你的Chips --> <com.google.android.material.chip.Chip ... /> <com.google.android.material.chip.Chip ... /> </com.google.android.material.chip.ChipGroup> <!-- RecyclerView锚定到ChipGroup下方 --> <androidx.recyclerview.widget.RecyclerView android:id="@+id/recylcerViewItems" android:layout_width="match_parent" android:layout_height="match_parent" android:divider="@android:color/transparent" android:dividerHeight="8dp" android:scrollbars="vertical" app:layout_anchor="@id/chipGroup" app:layout_anchorGravity="bottom"/> </androidx.coordinatorlayout.widget.CoordinatorLayout>
2. 自定义Behavior类
public class ChipGroupScrollBehavior extends CoordinatorLayout.Behavior<ChipGroup> { private int chipGroupHeight; @Override public boolean onLayoutChild(@NonNull CoordinatorLayout parent, @NonNull ChipGroup child, int layoutDirection) { // 布局完成后获取ChipGroup高度 chipGroupHeight = child.getMeasuredHeight(); return super.onLayoutChild(parent, child, layoutDirection); } @Override public boolean onStartNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull ChipGroup child, @NonNull View directTargetChild, @NonNull View target, int axes, int type) { // 只监听垂直滚动 return axes == ViewCompat.SCROLL_AXIS_VERTICAL; } @Override public void onNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull ChipGroup child, @NonNull View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int type, @NonNull int[] consumed) { super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, type, consumed); // 根据滚动偏移动态调整ChipGroup的位置 float currentTranslation = child.getTranslationY(); float newTranslation = currentTranslation - dyConsumed; // 限制平移范围:不能超过自身高度(完全隐藏),也不能超过0(完全显示) newTranslation = Math.max(-chipGroupHeight, Math.min(0, newTranslation)); child.setTranslationY(newTranslation); // 同步调整RecyclerView的paddingTop,保证内容不被遮挡 RecyclerView recyclerView = (RecyclerView) target; recyclerView.setPadding( recyclerView.getPaddingLeft(), (int) (chipGroupHeight + newTranslation), recyclerView.getPaddingRight(), recyclerView.getPaddingBottom() ); } }
这种方案完全利用系统的嵌套滚动机制,实时响应滚动偏移,没有频繁的视图状态变更,性能和流畅度都是最优的。
内容的提问来源于stack exchange,提问作者BlazeCodeDev
相关产品推荐
相关产品推荐

