使用Canvas裁剪技术实现RecyclerView与粘性Header重叠部分不显示的效果,Header无需设置固定背景色。
实现思路
- 自定义RecyclerView,重写
dispatchDraw()
方法进行画布裁剪 - 获取粘性Header的高度作为裁剪区域
- 使用
clipRect()
方法排除粘性Header占据的区域 - 确保粘性Header始终位于RecyclerView之上
完整代码实现
xml
<!-- activity_main.xml -->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/dynamic_background">
<!-- 自定义RecyclerView -->
<com.example.StickyHeaderRecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<!-- 粘性Header视图 -->
<TextView
android:id="@+id/stickyHeader"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/transparent"
android:gravity="center_vertical"
android:padding="16dp"
android:text="A seguir"
android:textColor="@android:color/white"
android:textSize="18sp"
android:textStyle="bold" />
</FrameLayout>
java
// StickyHeaderRecyclerView.java
public class StickyHeaderRecyclerView extends RecyclerView {
private View mStickyHeader;
private int mStickyHeaderHeight = -1;
public StickyHeaderRecyclerView(Context context) {
super(context);
init();
}
public StickyHeaderRecyclerView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public StickyHeaderRecyclerView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
private void init() {
// 启用裁剪功能
setClipToPadding(false);
}
public void setStickyHeader(View stickyHeader) {
this.mStickyHeader = stickyHeader;
if (mStickyHeader != null) {
// 添加布局监听器获取Header高度
mStickyHeader.addOnLayoutChangeListener(new OnLayoutChangeListener() {
@Override
public void onLayoutChange(View v, int left, int top, int right,
int bottom, int oldLeft, int oldTop,
int oldRight, int oldBottom) {
if (mStickyHeaderHeight != mStickyHeader.getHeight()) {
mStickyHeaderHeight = mStickyHeader.getHeight();
requestLayout();
}
}
});
}
}
@Override
protected void dispatchDraw(Canvas canvas) {
// 保存当前画布状态
canvas.save();
// 裁剪画布,排除粘性Header占据的区域
if (mStickyHeader != null && mStickyHeaderHeight > 0) {
// 创建裁剪区域 - 从顶部到Header高度的区域不绘制
canvas.clipRect(0, mStickyHeaderHeight, getWidth(), getHeight());
}
// 绘制子视图
super.dispatchDraw(canvas);
// 恢复画布状态
canvas.restore();
}
}
java
// MainActivity.java
public class MainActivity extends AppCompatActivity {
private StickyHeaderRecyclerView recyclerView;
private TextView stickyHeader;
private MusicAdapter adapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
recyclerView = findViewById(R.id.recyclerView);
stickyHeader = findViewById(R.id.stickyHeader);
// 设置布局管理器
LinearLayoutManager layoutManager = new LinearLayoutManager(this);
recyclerView.setLayoutManager(layoutManager);
// 创建并设置适配器
List<MusicItem> musicList = createMusicList();
adapter = new MusicAdapter(musicList);
recyclerView.setAdapter(adapter);
// 设置粘性Header
recyclerView.setStickyHeader(stickyHeader);
// 添加滚动监听更新粘性Header内容
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
updateStickyHeader();
}
});
}
private List<MusicItem> createMusicList() {
List<MusicItem> list = new ArrayList<>();
// 添加section headers和items
list.add(new MusicItem("A seguir", true)); // Section header
list.add(new MusicItem("3", false));
list.add(new MusicItem("Próximas de:", true)); // Section header
list.add(new MusicItem("5", false));
list.add(new MusicItem("4", false));
list.add(new MusicItem("5", false));
list.add(new MusicItem("7", false));
list.add(new MusicItem("8", false));
list.add(new MusicItem("9", false));
list.add(new MusicItem("10", false));
list.add(new MusicItem("乱舞春秋", true)); // Section header
list.add(new MusicItem("Jay Chou", false));
list.add(new MusicItem("Apologize", true)); // Section header
list.add(new MusicItem("OneRepublic", false));
// 添加更多音乐项...
return list;
}
private void updateStickyHeader() {
LinearLayoutManager layoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();
int firstVisiblePosition = layoutManager.findFirstVisibleItemPosition();
if (firstVisiblePosition != -1) {
// 找到当前section的header
int currentSectionStart = firstVisiblePosition;
while (currentSectionStart >= 0 && !adapter.getItem(currentSectionStart).isHeader) {
currentSectionStart--;
}
if (currentSectionStart >= 0) {
MusicItem sectionHeader = adapter.getItem(currentSectionStart);
stickyHeader.setText(sectionHeader.title);
}
}
}
// 音乐项数据类
class MusicItem {
String title;
boolean isHeader;
MusicItem(String title, boolean isHeader) {
this.title = title;
this.isHeader = isHeader;
}
}
// 音乐适配器
class MusicAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private static final int TYPE_HEADER = 0;
private static final int TYPE_ITEM = 1;
private List<MusicItem> items;
public MusicAdapter(List<MusicItem> items) {
this.items = items;
}
@Override
public int getItemViewType(int position) {
return items.get(position).isHeader ? TYPE_HEADER : TYPE_ITEM;
}
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
if (viewType == TYPE_HEADER) {
View view = inflater.inflate(R.layout.item_header, parent, false);
return new HeaderViewHolder(view);
} else {
View view = inflater.inflate(R.layout.item_music, parent, false);
return new ItemViewHolder(view);
}
}
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
MusicItem item = items.get(position);
if (holder instanceof HeaderViewHolder) {
((HeaderViewHolder) holder).title.setText(item.title);
} else if (holder instanceof ItemViewHolder) {
((ItemViewHolder) holder).title.setText(item.title);
}
}
@Override
public int getItemCount() {
return items.size();
}
public MusicItem getItem(int position) {
return items.get(position);
}
class HeaderViewHolder extends RecyclerView.ViewHolder {
TextView title;
HeaderViewHolder(View itemView) {
super(itemView);
title = itemView.findViewById(R.id.headerTitle);
}
}
class ItemViewHolder extends RecyclerView.ViewHolder {
TextView title;
ItemViewHolder(View itemView) {
super(itemView);
title = itemView.findViewById(R.id.musicTitle);
}
}
}
}
布局文件
xml
<!-- item_header.xml -->
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#80000000"
android:gravity="center_vertical"
android:padding="16dp"
android:textColor="@android:color/white"
android:textSize="18sp"
android:textStyle="bold"
android:id="@+id/headerTitle" />
xml
<!-- item_music.xml -->
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:padding="16dp"
android:textColor="@android:color/white"
android:textSize="16sp"
android:id="@+id/musicTitle" />
实现说明
- 自定义RecyclerView :通过重写
dispatchDraw()
方法,使用clipRect()
裁剪画布,排除粘性Header占据的区域 - 动态获取Header高度 :通过
OnLayoutChangeListener
监听粘性Header的高度变化 - 多布局适配器:使用两种视图类型区分Section Header和普通项目
- 滚动监听:根据滚动位置更新粘性Header的内容
- 透明背景:粘性Header使用透明背景,不会遮挡动态变化的布局背景
这种方法确保了无论布局背景如何变化,RecyclerView的内容都不会显示在粘性Header的区域,实现了真正的裁剪效果而非简单的视觉遮挡。
扩展建议
- 可以添加动画效果,使粘性Header的切换更加平滑
- 可以考虑使用ItemDecoration来实现Section Header,减少适配器的复杂性
- 可以添加点击事件处理,使粘性Header具有交互功能
这个解决方案应该能够完美解决您遇到的动态背景下的粘性Header显示问题。