{"id":13641451,"url":"https://github.com/VeerHan/RecyclerRefresh","last_synced_at":"2025-04-20T07:33:41.260Z","repository":{"id":188162537,"uuid":"54767436","full_name":"VeerHan/RecyclerRefresh","owner":"VeerHan","description":"A refresh demo realized by SwipeRefreshLayout and RecyclerView with 'Jianshu' style.","archived":true,"fork":false,"pushed_at":"2016-05-26T02:27:28.000Z","size":110,"stargazers_count":242,"open_issues_count":3,"forks_count":67,"subscribers_count":9,"default_branch":"master","last_synced_at":"2024-08-04T00:04:54.032Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"http://blog.csdn.net/leoleohan/article/details/50989549","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/VeerHan.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null}},"created_at":"2016-03-26T07:06:28.000Z","updated_at":"2024-08-04T00:04:54.033Z","dependencies_parsed_at":"2023-08-14T04:22:58.873Z","dependency_job_id":"70164daa-1268-447a-be84-d614eb67c39f","html_url":"https://github.com/VeerHan/RecyclerRefresh","commit_stats":null,"previous_names":["veerhan/recyclerrefresh","leoleohan/recyclerrefresh"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/VeerHan%2FRecyclerRefresh","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/VeerHan%2FRecyclerRefresh/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/VeerHan%2FRecyclerRefresh/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/VeerHan%2FRecyclerRefresh/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/VeerHan","download_url":"https://codeload.github.com/VeerHan/RecyclerRefresh/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":249864369,"owners_count":21336729,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-08-02T01:01:20.809Z","updated_at":"2025-04-20T07:33:40.863Z","avatar_url":"https://github.com/VeerHan.png","language":"Java","readme":"一、概述\n----\n我们公司目前开发的所有Android APP都是遵循iOS风格设计的，这并不是一个好现象。我决定将Android 5.x控件引入最近开发的项目中，使用RecyclerView取代以往使用的ListView、GridView，使用SwipeRefreshLayout取代pull-to-refresh第三方库，打造更符合Material Design风格的APP。\n\n本篇博客介绍的就是如何使用SwipeRefreshLayout和RecyclerView实现高仿简书Android端的下拉刷新和上拉加载更多的效果。\n\n![这里写图片描述](http://img.blog.csdn.net/20160326214149913)\n\n根据效果图可以发现，本案例实现了如下效果：\n\n - 第一次进入页面显示SwipeRefreshLayout的下拉刷新效果\n - 当内容铺满屏幕时，向下滑动显示“加载中...”效果并加载更多数据\n - 当SwipeRefreshLayout正在下拉刷新时，将屏蔽加载更多操作\n - 当加载更多数据时，屏蔽有可能的重复的上拉操作\n - 当向上滑动RecyclerView时，隐藏Toolbar以获得更好的用户体验\n\n二、代码实现\n------\n\n - MainActivity\n \n\n```\npackage com.leohan.refresh;\n\nimport android.os.Bundle;\nimport android.os.Handler;\nimport android.support.v4.widget.SwipeRefreshLayout;\nimport android.support.v7.app.AppCompatActivity;\nimport android.support.v7.widget.LinearLayoutManager;\nimport android.support.v7.widget.RecyclerView;\nimport android.support.v7.widget.Toolbar;\nimport android.util.Log;\nimport android.view.View;\n\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\nimport butterknife.ButterKnife;\nimport butterknife.InjectView;\n\n/**\n * @author Leo\n */\npublic class MainActivity extends AppCompatActivity {\n\n\n    @InjectView(R.id.toolbar)\n    Toolbar toolbar;\n    @InjectView(R.id.recyclerView)\n    RecyclerView recyclerView;\n    @InjectView(R.id.SwipeRefreshLayout)\n    SwipeRefreshLayout swipeRefreshLayout;\n\n    boolean isLoading;\n    private List\u003cMap\u003cString, Object\u003e\u003e data = new ArrayList\u003c\u003e();\n    private MyAdapter adapter = new MyAdapter(this, data);\n    private Handler handler = new Handler();\n\n    @Override\n    public void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_notice);\n        ButterKnife.inject(this);\n        initView();\n        initData();\n    }\n\n    public void initView() {\n        setSupportActionBar(toolbar);\n        toolbar.setTitle(R.string.notice);\n        toolbar.setNavigationOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                finish();\n            }\n        });\n\n        swipeRefreshLayout.setColorSchemeResources(R.color.blueStatus);\n        swipeRefreshLayout.post(new Runnable() {\n            @Override\n            public void run() {\n                swipeRefreshLayout.setRefreshing(true);\n            }\n        });\n\n        swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {\n            @Override\n            public void onRefresh() {\n                handler.postDelayed(new Runnable() {\n                    @Override\n                    public void run() {\n                        data.clear();\n                        getData();\n                    }\n                }, 2000);\n            }\n        });\n        final LinearLayoutManager layoutManager = new LinearLayoutManager(this);\n        recyclerView.setLayoutManager(layoutManager);\n        recyclerView.setAdapter(adapter);\n        recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {\n            @Override\n            public void onScrollStateChanged(RecyclerView recyclerView, int newState) {\n                super.onScrollStateChanged(recyclerView, newState);\n                Log.d(\"test\", \"StateChanged = \" + newState);\n\n\n            }\n\n            @Override\n            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {\n                super.onScrolled(recyclerView, dx, dy);\n                Log.d(\"test\", \"onScrolled\");\n\n                int lastVisibleItemPosition = layoutManager.findLastVisibleItemPosition();\n                if (lastVisibleItemPosition + 1 == adapter.getItemCount()) {\n                    Log.d(\"test\", \"loading executed\");\n\n                    boolean isRefreshing = swipeRefreshLayout.isRefreshing();\n                    if (isRefreshing) {\n                        adapter.notifyItemRemoved(adapter.getItemCount());\n                        return;\n                    }\n                    if (!isLoading) {\n                        isLoading = true;\n                        handler.postDelayed(new Runnable() {\n                            @Override\n                            public void run() {\n                                getData();\n                                Log.d(\"test\", \"load more completed\");\n                                isLoading = false;\n                            }\n                        }, 1000);\n                    }\n                }\n            }\n        });\n\n        //添加点击事件\n        adapter.setOnItemClickListener(new MyAdapter.OnItemClickListener() {\n            @Override\n            public void onItemClick(View view, int position) {\n                Log.d(\"test\", \"item position = \" + position);\n            }\n\n            @Override\n            public void onItemLongClick(View view, int position) {\n\n            }\n        });\n    }\n\n\n    public void initData() {\n        handler.postDelayed(new Runnable() {\n            @Override\n            public void run() {\n                getData();\n            }\n        }, 1500);\n\n    }\n\n    /**\n     * 获取测试数据\n     */\n    private void getData() {\n        for (int i = 0; i \u003c 6; i++) {\n            Map\u003cString, Object\u003e map = new HashMap\u003c\u003e();\n            data.add(map);\n        }\n        adapter.notifyDataSetChanged();\n        swipeRefreshLayout.setRefreshing(false);\n        adapter.notifyItemRemoved(adapter.getItemCount());\n    }\n\n\n}\n\n```\n\n - RecyclerViewAdapter\n \n\n```\npackage com.leohan.refresh;\n\nimport android.content.Context;\nimport android.support.v7.widget.RecyclerView.Adapter;\nimport android.support.v7.widget.RecyclerView.ViewHolder;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.TextView;\n\nimport java.util.List;\n\npublic class RecyclerViewAdapter extends Adapter\u003cViewHolder\u003e {\n\n    private static final int TYPE_ITEM = 0;\n    private static final int TYPE_FOOTER = 1;\n    private Context context;\n    private List data;\n\n    public interface OnItemClickListener {\n        void onItemClick(View view, int position);\n\n        void onItemLongClick(View view, int position);\n    }\n\n    private OnItemClickListener onItemClickListener;\n\n    public void setOnItemClickListener(OnItemClickListener onItemClickListener) {\n        this.onItemClickListener = onItemClickListener;\n    }\n\n    @Override\n    public int getItemCount() {\n        return data.size() == 0 ? 0 : data.size() + 1;\n    }\n\n    @Override\n    public int getItemViewType(int position) {\n        if (position + 1 == getItemCount()) {\n            return TYPE_FOOTER;\n        } else {\n            return TYPE_ITEM;\n        }\n    }\n\n    public RecyclerViewAdapter(Context context, List data) {\n        this.context = context;\n        this.data = data;\n    }\n\n    @Override\n    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {\n        if (viewType == TYPE_ITEM) {\n            View view = LayoutInflater.from(context).inflate(R.layout.item_notice, parent,\n                    false);\n            return new ItemViewHolder(view);\n        } else if (viewType == TYPE_FOOTER) {\n            View view = LayoutInflater.from(context).inflate(R.layout.item_foot, parent,\n                    false);\n            return new FootViewHolder(view);\n        }\n        return null;\n    }\n\n\n    @Override\n    public void onBindViewHolder(final ViewHolder holder, int position) {\n        if (holder instanceof ItemViewHolder) {\n            //holder.tv.setText(data.get(position));\n            if (onItemClickListener != null) {\n                holder.itemView.setOnClickListener(new View.OnClickListener() {\n                    @Override\n                    public void onClick(View v) {\n                        int position = holder.getLayoutPosition();\n                        onItemClickListener.onItemClick(holder.itemView, position);\n                    }\n                });\n\n                holder.itemView.setOnLongClickListener(new View.OnLongClickListener() {\n                    @Override\n                    public boolean onLongClick(View v) {\n                        int position = holder.getLayoutPosition();\n                        onItemClickListener.onItemLongClick(holder.itemView, position);\n                        return false;\n                    }\n                });\n            }\n        }\n    }\n\n\n    static class ItemViewHolder extends ViewHolder {\n\n        TextView tv;\n\n        public ItemViewHolder(View view) {\n            super(view);\n            tv = (TextView) view.findViewById(R.id.tv_date);\n        }\n    }\n\n    static class FootViewHolder extends ViewHolder {\n\n        public FootViewHolder(View view) {\n            super(view);\n        }\n    }\n}\n```\n\n - item_base.xml\n \n```\n\u003c?xml version=\"1.0\" encoding=\"utf-8\"?\u003e\n\n\u003candroid.support.v7.widget.CardView xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginLeft=\"@dimen/margin_10\"\n    android:layout_marginRight=\"@dimen/margin_10\"\n    android:foreground=\"?android:attr/selectableItemBackgroundBorderless\"\n    android:layout_marginTop=\"6dp\"\n    android:orientation=\"vertical\"\n    app:cardBackgroundColor=\"@color/line\"\n    app:cardPreventCornerOverlap=\"true\"\n    app:cardUseCompatPadding=\"true\"\n    app:contentPadding=\"6dp\"\u003e\n\n    \u003cLinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:orientation=\"vertical\"\u003e\n\n        \u003cTextView\n            android:id=\"@+id/tv_date\"\n            style=\"@style/NormalTextView\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:text=\"2015-12-11 12:00\" /\u003e\n\n        \u003candroid.support.v7.widget.CardView\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\"\n            android:orientation=\"vertical\"\n            app:cardBackgroundColor=\"@color/white\"\n            app:cardPreventCornerOverlap=\"true\"\n            app:cardUseCompatPadding=\"true\"\n            app:contentPadding=\"10dp\"\u003e\n\n            \u003cTextView\n                android:id=\"@+id/tv_title\"\n                style=\"@style/SmallGreyTextView\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:ellipsize=\"end\"\n                android:maxLines=\"2\"\n                android:text=\"视线好转,0729出口开通,0621进口开通。视线好转,0729出口开通,0621进口开通。\" /\u003e\n\n        \u003c/android.support.v7.widget.CardView\u003e\n    \u003c/LinearLayout\u003e\n\n\u003c/android.support.v7.widget.CardView\u003e\n```\n\n - item_foot.xml\n \n\n```\n\u003c?xml version=\"1.0\" encoding=\"utf-8\"?\u003e\n\u003cLinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"40dp\"\n    android:gravity=\"center\"\n    android:orientation=\"horizontal\"\n \u003e\n\n\n    \u003cProgressBar\n        android:layout_marginRight=\"6dp\"\n        android:id=\"@+id/progressBar\"\n        style=\"?android:attr/progressBarStyleSmall\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_gravity=\"center\" /\u003e\n\n    \u003cTextView\n        style=\"@style/SmallGreyTextView\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_gravity=\"center\"\n        android:text=\"@string/loading\" /\u003e\n\n\n\u003c/LinearLayout\u003e\n\n```\n\n三、代码分析\n------\n\n - 上拉加载更多数据通过监听RecyclerView的滚动事件**RecyclerView.OnScrollListener()**实现的，它提供了两个方法：\n\n```\n\t\t/**\n         * 当RecyclerView的滑动状态改变时触发\n         */\n        public void onScrollStateChanged(RecyclerView recyclerView, int newState){}\n\n        /**\n         * 当RecyclerView滑动时触发\n         * 类似点击事件的MotionEvent.ACTION_MOVE\n         */\n        public void onScrolled(RecyclerView recyclerView, int dx, int dy){}\n```\n - RecyclerView的滑动状态有如下三种：\n \n\n```\n    /**\n     * The RecyclerView is not currently scrolling.\n     * 手指离开屏幕\n     */\n    public static final int SCROLL_STATE_IDLE = 0;\n\n    /**\n     * The RecyclerView is currently being dragged by outside input such as user touch input.\n     * 手指触摸屏幕\n     */\n    public static final int SCROLL_STATE_DRAGGING = 1;\n\n    /**\n     * The RecyclerView is currently animating to a final position while not under\n     * outside control.\n     * 手指加速滑动并放开，此时滑动状态伴随SCROLL_STATE_IDLE\n     */\n    public static final int SCROLL_STATE_SETTLING = 2;\n```\n - 由于简书APP的上拉加载更多的是在滑动到最后一个item时自动触发的，与手指是否在屏幕上无关，即与滑动状态无关。因此，实现这种效果只需要在**public void onScrolled(RecyclerView recyclerView, int dx, int dy)** 方法中操作，无需关注当时的滑动状态：\n\n```\n  @Override\n            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {\n                super.onScrolled(recyclerView, dx, dy);\n                Log.d(\"test\", \"onScrolled\");\n\n                int lastVisibleItemPosition = layoutManager.findLastVisibleItemPosition();\n                if (lastVisibleItemPosition + 1 == adapter.getItemCount()) {\n                    Log.d(\"test\", \"loading executed\");\n\n                    boolean isRefreshing = swipeRefreshLayout.isRefreshing();\n                    if (isRefreshing) {\n                        adapter.notifyItemRemoved(adapter.getItemCount());\n                        return;\n                    }\n                    if (!isLoading) {\n                        isLoading = true;\n                        handler.postDelayed(new Runnable() {\n                            @Override\n                            public void run() {\n                                getData();\n                                Log.d(\"test\", \"load more completed\");\n                                isLoading = false;\n                            }\n                        }, 1000);\n                    }\n                }\n            }\n```\n\n - 如果要实现当且仅当滑动到最后一项并且手指上拉抛出时才执行上拉加载更多效果的话，需要配合**onScrollStateChanged(RecyclerView recyclerView, int newState**的使用，可以将代码改为：\n\n```\n @Override\n            public void onScrollStateChanged(RecyclerView recyclerView, int newState) {\n                super.onScrollStateChanged(recyclerView, newState);\n                Log.d(\"test\", \"StateChanged = \" + newState);\n                if (newState == RecyclerView.SCROLL_STATE_IDLE \u0026\u0026 lastVisibleItemPosition + 1 == adapter.getItemCount()) {\n                    Log.d(\"test\", \"loading executed\");\n\n                    boolean isRefreshing = swipeRefreshLayout.isRefreshing();\n                    if (isRefreshing) {\n                        adapter.notifyItemRemoved(adapter.getItemCount());\n                        return;\n                    }\n                    if (!isLoading) {\n                        isLoading = true;\n                        handler.postDelayed(new Runnable() {\n                            @Override\n                            public void run() {\n                                getData();\n                                Log.d(\"test\", \"load more completed\");\n                                isLoading = false;\n                            }\n                        }, 1000);\n                    }\n                }\n            }\n\n            @Override\n            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {\n                super.onScrolled(recyclerView, dx, dy);\n                Log.d(\"test\", \"onScrolled\");\n\n                lastVisibleItemPosition = layoutManager.findLastVisibleItemPosition();\n\n            }\n```\n\n - 加载更多的效果可以通过item_foot.xml自定义，滑动到最后一项时显示该item并执行加载更多，当加载数据完毕时需要将该item移除掉\n \n\n```\nadapter.notifyItemRemoved(adapter.getItemCount());\n```\n\n下面的代码就是RecyclerView的多个item布局的实现方法：\n\n```\n @Override\n    public int getItemCount() {\n        return data.size() == 0 ? 0 : data.size() + 1;\n    }\n\n    @Override\n    public int getItemViewType(int position) {\n        if (position + 1 == getItemCount()) {\n            return TYPE_FOOTER;\n        } else {\n            return TYPE_ITEM;\n        }\n    }\n\n    @Override\n    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {\n        if (viewType == TYPE_ITEM) {\n            View view = LayoutInflater.from(context).inflate(R.layout.item_base, parent,\n                    false);\n            return new ItemViewHolder(view);\n        } else if (viewType == TYPE_FOOTER) {\n            View view = LayoutInflater.from(context).inflate(R.layout.item_foot, parent,\n                    false);\n            return new FootViewHolder(view);\n        }\n        return null;\n    }\n```\n","funding_links":[],"categories":["RecyclerView"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FVeerHan%2FRecyclerRefresh","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FVeerHan%2FRecyclerRefresh","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FVeerHan%2FRecyclerRefresh/lists"}