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

如何让SwipeRefreshLayout加载圈相对RecyclerView静态定位?

解决SwipeRefreshLayout加载圈随RecyclerView滚动的问题

这个需求确实挺实用的——默认的SwipeRefreshLayout加载圈是固定在父容器顶部的,完全不跟着列表滚动,要实现它和RecyclerView联动,咱们可以从两个方向入手,给你详细说说:

方案一:把加载圈改成RecyclerView的Header(最推荐)

思路很直接:把原来SwipeRefreshLayout自带的加载圈藏起来,换成RecyclerView的HeaderView,这样Header会天然跟着列表一起滚动,完美符合你的需求。

具体步骤:

  1. 隐藏原生加载圈
    先让SwipeRefreshLayout自带的加载圈彻底消失,避免和自定义Header冲突:

    mSwipeRefreshLayout.setProgressViewOffset(true, 0, 0);
    // 或者直接通过样式设置透明:
    // <style name="SwipeRefresh" parent="Widget.SwipeRefreshLayout">
    //     <item name="colorSchemeColors">@android:color/transparent</item>
    // </style>
    
  2. 创建自定义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>
    
  3. 修改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);
            }
        }
    }
    
  4. 关联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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.21 07:34:20