如何让SwipeRefreshLayout加载圈相对RecyclerView静态定位?
解决SwipeRefreshLayout加载圈随RecyclerView滚动的问题
这个需求确实挺实用的——默认的SwipeRefreshLayout加载圈是固定在父容器顶部的,完全不跟着列表滚动,要实现它和RecyclerView联动,咱们可以从两个方向入手,给你详细说说:
方案一:把加载圈改成RecyclerView的Header(最推荐)
思路很直接:把原来SwipeRefreshLayout自带的加载圈藏起来,换成RecyclerView的HeaderView,这样Header会天然跟着列表一起滚动,完美符合你的需求。
具体步骤:
隐藏原生加载圈
先让SwipeRefreshLayout自带的加载圈彻底消失,避免和自定义Header冲突:mSwipeRefreshLayout.setProgressViewOffset(true, 0, 0); // 或者直接通过样式设置透明: // <style name="SwipeRefresh" parent="Widget.SwipeRefreshLayout"> // <item name="colorSchemeColors">@android:color/transparent</item> // </style>创建自定义Header布局
写一个和原生加载圈样式一致的布局(比如refresh_header.xml):<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center" android:padding="16dp"> <ProgressBar android:id="@+id/progress_bar" android:layout_width="32dp" android:layout_height="32dp" style="@style/Widget.Material.ProgressIndicator.Circular"/> </LinearLayout>修改RecyclerView的Adapter
给Adapter添加Header类型的item,控制它的显示/隐藏:public class MyListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> { private static final int TYPE_HEADER = 0; private static final int TYPE_ITEM = 1; private boolean isRefreshing = false; private List<String> mDataList; // 构造方法、数据设置等省略... @Override public int getItemViewType(int position) { // 刷新状态下,第一个位置是Header return isRefreshing && position == 0 ? TYPE_HEADER : TYPE_ITEM; } @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { LayoutInflater inflater = LayoutInflater.from(parent.getContext()); if (viewType == TYPE_HEADER) { View headerView = inflater.inflate(R.layout.refresh_header, parent, false); return new HeaderViewHolder(headerView); } else { // 创建普通item的ViewHolder View itemView = inflater.inflate(R.layout.item_list, parent, false); return new ItemViewHolder(itemView); } } @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { if (holder instanceof HeaderViewHolder) { // 控制加载圈显示 ((HeaderViewHolder) holder).progressBar.setVisibility( isRefreshing ? View.VISIBLE : View.GONE ); } else { // 绑定普通item数据,注意position要减去Header的占位 int realPos = isRefreshing ? position - 1 : position; ((ItemViewHolder) holder).tvContent.setText(mDataList.get(realPos)); } } @Override public int getItemCount() { // 刷新状态下多算一个Header的位置 return isRefreshing ? mDataList.size() + 1 : mDataList.size(); } // 对外提供刷新状态切换方法 public void setRefreshing(boolean refreshing) { isRefreshing = refreshing; notifyDataSetChanged(); } // ViewHolder定义 static class HeaderViewHolder extends RecyclerView.ViewHolder { ProgressBar progressBar; public HeaderViewHolder(View itemView) { super(itemView); progressBar = itemView.findViewById(R.id.progress_bar); } } static class ItemViewHolder extends RecyclerView.ViewHolder { TextView tvContent; public ItemViewHolder(View itemView) { super(itemView); tvContent = itemView.findViewById(R.id.tv_content); } } }关联SwipeRefreshLayout的刷新状态
在Activity/Fragment里,把SwipeRefreshLayout的刷新状态同步给Adapter:mSwipeRefreshLayout.setOnRefreshListener(() -> { // 触发刷新逻辑 mAdapter.setRefreshing(true); // 模拟网络请求 new Handler().postDelayed(() -> { mAdapter.setRefreshing(false); mSwipeRefreshLayout.setRefreshing(false); }, 2000); });
方案二:自定义SwipeRefreshLayout监听滚动动态调整位置
如果不想修改Adapter,可以通过监听RecyclerView的滚动事件,动态修改原生加载圈的位置,让它跟着列表走。
自定义SwipeRefreshLayout示例:
public class ScrollFollowSwipeRefreshLayout extends SwipeRefreshLayout { private RecyclerView mRecyclerView; private int mOriginalTopOffset; public ScrollFollowSwipeRefreshLayout(Context context) { super(context); init(); } public ScrollFollowSwipeRefreshLayout(Context context, AttributeSet attrs) { super(context, attrs); init(); } private void init() { // 获取原生加载圈的初始位置(注意androidx和support库的id不同) View refreshView = findViewById(androidx.swiperefreshlayout.widget.R.id.swipe_refresh_progress); if (refreshView != null) { mOriginalTopOffset = refreshView.getTop(); } } @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); // 找到子View中的RecyclerView for (int i = 0; i < getChildCount(); i++) { View child = getChildAt(i); if (child instanceof RecyclerView) { mRecyclerView = (RecyclerView) child; setupScrollListener(); break; } } } private void setupScrollListener() { mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { @Override public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) { super.onScrolled(recyclerView, dx, dy); // 只有刷新状态下才调整位置 if (isRefreshing()) { View refreshView = findViewById(androidx.swiperefreshlayout.widget.R.id.swipe_refresh_progress); if (refreshView != null) { // 计算新的top位置:初始位置减去列表滚动的偏移量 int newTop = mOriginalTopOffset - recyclerView.computeVerticalScrollOffset(); refreshView.setTop(newTop); // 当加载圈被滚出屏幕时隐藏 refreshView.setVisibility(newTop >= 0 ? View.VISIBLE : View.INVISIBLE); } } } }); } }
两种方案对比
- 方案一:逻辑简单,兼容性强,完全符合列表滚动的直觉,但需要修改Adapter的结构,适合大多数场景。
- 方案二:不需要改动Adapter,保留原生SwipeRefreshLayout的手势逻辑,但要注意不同Android版本的内部控件id差异,以及滚动时的性能损耗。
内容的提问来源于stack exchange,提问作者AlanSTACK
相关产品推荐
相关产品推荐

