SwipeRefreshLayout与RecyclerView的巧夺天工

SwipeRefreshLayout与RecyclerView的巧夺天工平常开发我们需要使用ListView下拉刷新或者其下拉加载的时候,不是自己写就是用别人写好了,但是编程中有一点是不变的,就是一般封装好的东西,其扩展性极低,比如你使用xutils,imageloader等开源框架的时候,它允许你扩展吗?答案当然是否,那我想要实现自己非常酷酷的ListView时候,只有自己动手实现。不过,谷歌在2015在v4开发包加入豪华套餐SwipeRefreshLayout供

大家好,又见面了,我是你们的朋友全栈君。

平常开发我们需要使用ListView下拉刷新或者其上滑加载的时候,不是自己写就是用别人写好了,但是编程中有一点是不变的,就是一般封装好的东西,其扩展性极低,比如你使用xutils,imageloader等开源框架的时候,它允许你扩展吗?答案当然是否,那我想要实现自己非常酷酷的ListView时候,只有自己动手实现。不过,谷歌在2015在v4开发包加入 豪华套餐SwipeRefreshLayout供你享用。



1.SwipeRefreshLayout使用注意说明



SwipeRefreshLayout默认只能包含一个滑动控件,比如本文使用的RecyclerView。


㈡一般使用ListView组件都有一个需求,那么就是没有网络的情况下,将显示其他控件提示用户加载失败或者需要联网。那么,SwipeRefreshLayout可以包含有且仅有一个布局,布局里面可以添加你需要的控件。


㈢如果你按㈡这样做,那么SwipeRefreshLayout默认只会监听一个滑动控件,当你有多个控件的时候会使其找不到监听的滑动控件。这样SwipeRefreshLayout功能就不复存在了。


㈣那么今天我们将实现的下拉刷新和上滑加载该怎么办呢?答案就是重写SwipeRefreshLayout。



2.重写SwipeRefreshLayout



当我们重写SwipeRefreshLayout,需要使用到如下一个方法:


㈠canChildScrollUp


我们来看看其文档说明:


public boolean canChildScrollUp ()
Returns
Whether it is possible for the child view of this layout to scroll up. Override this if the child view is a custom view.


如果子视图为自定义视图那么必须重写该方法。同理,当你的子视图用布局包裹的时候,其就是你自定义的,除非你的子视图只有ListView,当有多个控件时候,其默认找不到ListView监听其滑动事件,必须重写该方法。

㈡重写SwipeRefreshLayout


既然找不到该子视图,那么就必须传入子视图的控件,以监听其滑动状态,也就是自定义一个属性:


<declare-styleable name="LYJSwipeLayoutAttrs">
    <attr name="scrollableChildId" format="reference" />
</declare-styleable>

下面源码是怎么写的canChildScrollUp:


public boolean canChildScrollUp() {
    if (android.os.Build.VERSION.SDK_INT < 14) {
        if (mTarget instanceof AbsListView) {
            final AbsListView absListView = (AbsListView) mTarget;
            return absListView.getChildCount() > 0
                    && (absListView.getFirstVisiblePosition() > 0 || absListView.getChildAt(0)
                            .getTop() < absListView.getPaddingTop());
        } else {
            return ViewCompat.canScrollVertically(mTarget, -1) || mTarget.getScrollY() > 0;
        }
    } else {
        return ViewCompat.canScrollVertically(mTarget, -1);
    }
}

下面我们来依葫芦画瓢重写SwipeRefreshLayout:


public class LYJSwipeRefreshLayout extends SwipeRefreshLayout {
    private static final String TAG = LYJSwipeRefreshLayout.class.getCanonicalName();
    private int mScrollableChildId;//控件ID
    private View mScrollableChild;//子控件
    public LYJSwipeRefreshLayout(Context context) {
        this(context, null);
    }
    public LYJSwipeRefreshLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        //获取监听子控件的ID
        TypedArray a = context.obtainStyledAttributes(
                attrs, R.styleable.LYJSwipeLayoutAttrs);
        mScrollableChildId = a.getResourceId(R.styleable.LYJSwipeLayoutAttrs_scrollableChildId, 0);
        mScrollableChild = findViewById(mScrollableChildId);
        a.recycle();
    }
    @Override
    public boolean canChildScrollUp() {
        //判断有没有传入子控件
        ensureScrollableChild();
        if (android.os.Build.VERSION.SDK_INT < 14) {
            if (mScrollableChild instanceof AbsListView) {
                final AbsListView absListView = (AbsListView) mScrollableChild;
                return absListView.getChildCount() > 0
                        && (absListView.getFirstVisiblePosition() > 0 || absListView.getChildAt(0)
                        .getTop() < absListView.getPaddingTop());
            } else {
                return mScrollableChild.getScrollY() > 0;
            }
        } else {
            return ViewCompat.canScrollVertically(mScrollableChild, -1);
        }
    }
    private void ensureScrollableChild() {
        if (mScrollableChild == null) {
            mScrollableChild = findViewById(mScrollableChildId);
        }
    }
}

布局文件如下:


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <android.support.v7.widget.Toolbar
        android:id="@+id/activity_main_toolbar"
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        android:background="@color/activity_main_tablayout_bg">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerInParent="true"
            android:layout_gravity="center"
            android:text="@string/app_name"
            android:textColor="@android:color/black"
            android:textSize="20sp"
            android:textStyle="bold" />

    </android.support.v7.widget.Toolbar>


    <com.example.liyuanjing.welltestdemo.LYJSwipeRefreshLayout
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:id="@+id/activity_main_swipe"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:scrollableChildId="@+id/activity_main_recyclerview"
        android:background="@android:color/transparent">

        <FrameLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content">

            <android.support.v7.widget.RecyclerView
                android:id="@+id/activity_main_recyclerview"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:cacheColorHint="@null"
                android:scrollbars="vertical" />

            <LinearLayout
                android:id="@+id/activity_main_linearlayout"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:orientation="vertical"/>

        </FrameLayout>

    </com.example.liyuanjing.welltestdemo.LYJSwipeRefreshLayout>

</LinearLayout>

红色标记的为传入子控件ID的属性。这样SwipeRefreshLayout就可以监听recyclerview了。



3.实现下拉刷新,上滑加载


为了代码的重用效率高,我写了一个基类BaseActivity:


public abstract class BaseActivity extends AppCompatActivity implements SwipeRefreshLayout.OnRefreshListener {
    /***  * 处理下拉和刷新滴  */  protected SwipeRefreshLayout swipeRefreshLayout;
    /***  * 进化的ListView  */  protected RecyclerView recyclerView;
    /***  * 该布局在没有网络的时候,显示的布局  */  protected LinearLayout linearLayout;
    /***  * RecyclerView的样式(网格,瀑布,线性)  */  protected LinearLayoutManager mLayoutManager;
    /***  * 记录最后一项的位置  */  protected int lastVisibleItem=0;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        this.swipeRefreshLayout=(SwipeRefreshLayout)findViewById(R.id.activity_main_swipe);
        this.recyclerView=(RecyclerView)findViewById(R.id.activity_main_recyclerview);
        this.linearLayout=(LinearLayout)findViewById(R.id.activity_main_linearlayout);
        initView();
        recyclerView.setOnScrollListener(new RecyclerView.OnScrollListener() {
            @Override
            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                super.onScrolled(recyclerView, dx, dy);
                lastVisibleItem = mLayoutManager.findLastVisibleItemPosition();
            }

            @Override
            public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
                super.onScrollStateChanged(recyclerView, newState);
                onRecyclerViewStateChanged(newState);
            }
        });
    }

    @Override
    public void onRefresh() {
        onRecyclerViewRefresh();
    }

    /***  * 初始化界面  */  public abstract void initView();

    /***  * 监听RecyclerView滑动事件  * @param newState 滑动状态  */  public abstract void onRecyclerViewStateChanged(int newState);

    /***  * 下拉刷新处理  */  public abstract void onRecyclerViewRefresh();
}

注释非常明确,布局中有一个linearlayout其中无任何控件,是为了扩展任何你需要的无网络时显示的界面的。你只需要继承该类实现这几个抽象方法。


㈠自定义适配器


代码如下:


public class LYJRecyclerViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
    /***  * 每项的数据集合  */  private List<String> messageItems;
    /***  * 监听item点击事件。  */  private LYJItemClickListener mItemClickListener;
    /***  * 一共显示多少条数据  */  private int totalSize;

    public LYJRecyclerViewAdapter(List<String> messageItems,int size){
        this.messageItems=messageItems;
        this.totalSize=size;
    }

    /***  * 监听点击事件接口  */  public interface LYJItemClickListener {
        public void onItemClick(View view, int postion);
    }

    /***  * 设置item点击事件  * @param listener  */  public void setOnItemClickListener(LYJItemClickListener listener) {
        this.mItemClickListener = listener;
    }
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
        if (i == Constants.TYPE_ITEM) {
            View view = LayoutInflater.from(viewGroup.getContext()).inflate(
                    R.layout.activity_main_adapter_item, null);
            view.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
                    ViewGroup.LayoutParams.WRAP_CONTENT));
            return new ItemViewHolder(view,this.mItemClickListener);
        }
        //滑动到底部返回footview
        else if (i == Constants.TYPE_FOOTER) {
            View view = LayoutInflater.from(viewGroup.getContext()).inflate(
                    R.layout.activity_main_adapter_footview, null);
            view.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
                    ViewGroup.LayoutParams.WRAP_CONTENT));
            return new FooterViewHolder(view);
        }
        return null;
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int i) {
        if(viewHolder instanceof ItemViewHolder){
            ((ItemViewHolder) viewHolder).name.setText(messageItems.get(i));
        }else{
            if(this.totalSize==(getItemCount()-1)){
                ((FooterViewHolder)viewHolder).flagTxt.setText("已经加载完全部内容");
            }else{
                ((FooterViewHolder)viewHolder).flagTxt.setText("正在加载中........");
            }
        }
    }

    @Override
    public int getItemCount() {
        return messageItems.size()+1;//加1是多的footview那一项,也就是滑动到footview就加载,而不是最后数据项。
    }

    @Override
    public int getItemViewType(int position) {
        if (position + 1 == getItemCount()) {
            return Constants.TYPE_FOOTER;
        } else {
            return Constants.TYPE_ITEM;
        }
    }

    /***  * 底部布局  */  public class FooterViewHolder extends RecyclerView.ViewHolder {
        private TextView flagTxt;
        public FooterViewHolder(View itemView) {
            super(itemView);
            this.flagTxt=(TextView)itemView.findViewById(R.id.activity_main_adapter_footview_txt);
        }
    }

    /***  * 数据项布局  */  class ItemViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
        private TextView name;
        private LYJItemClickListener mListener;//设置点击事件

        public ItemViewHolder(View itemView, LYJItemClickListener listener) {
            super(itemView);
            this.name = (TextView) itemView.findViewById(R.id.activity_main_adapter_item_name);
            this.mListener = listener;
            itemView.setOnClickListener(this);//设置点击事件
        }

        @Override
        public void onClick(View v) {
            if (mListener != null) {
                mListener.onItemClick(v, getPosition());
            }

        }
    }
}

㈡MainActivity的实现


其继承自BaseActivity,并且实现item点击事件接口LYJRecyclerViewAdapter.LYJItemClickListener:


public class MainActivity extends BaseActivity implements LYJRecyclerViewAdapter.LYJItemClickListener{
    /***  * 数据项  */  private List<String> messageItems=new ArrayList<>();
    /***  * 自定义adapter  */  private LYJRecyclerViewAdapter adapter;
    /***  * 获取资源文件字符串中间转换集合  */  private List<String> strFlag;
    @Override
    public void initView() {
        Toolbar toolbar=(Toolbar)findViewById(R.id.activity_main_toolbar);
        toolbar.setTitle("");
        setSupportActionBar(toolbar);
        this.swipeRefreshLayout.setColorSchemeColors(Color.RED);//设置加载内圈颜色
        this.swipeRefreshLayout.setOnRefreshListener(this);//设置下拉刷新事件
        this.swipeRefreshLayout.setProgressBackgroundColorSchemeColor(getResources().getColor(R.color.activity_main_tablayout_bg));//设置加载外圈颜色
        // 这句话是为了,第一次进入页面的时候显示加载进度条
        swipeRefreshLayout.setProgressViewOffset(false, 0, (int) TypedValue
                .applyDimension(TypedValue.COMPLEX_UNIT_DIP, 24, getResources()
                        .getDisplayMetrics()));
        mLayoutManager=new LinearLayoutManager(this,LinearLayoutManager.VERTICAL,false);//设置布局样式
        recyclerView.setHasFixedSize(true);
        recyclerView.setLayoutManager(mLayoutManager);
        loadingRecyclerView(Constants.LISTVIEW_INIT);//初始化RecyclerView
    }

    @Override
    public void onRecyclerViewStateChanged(int newState) {
        if (messageItems == null || messageItems.size() <= 0) {
            Snackbar.make(swipeRefreshLayout, "没有数据得先下拉刷新", Snackbar.LENGTH_SHORT).show();
            return;
        }
        //滚动事件结束并且到达最底端
        if (newState == RecyclerView.SCROLL_STATE_IDLE  && lastVisibleItem + 1 == adapter.getItemCount()) {
            loadingRecyclerView(Constants.LISTVIEW_DOWNLOAD);//下滑RecyclerView
        }
    }

    @Override
    public void onRecyclerViewRefresh() {
        loadingRecyclerView(Constants.LISTVIEW_REFRESH);//下拉刷新RecyclerView
    }
    public void loadingRecyclerView(int recyclerViewState){
        swipeRefreshLayout.setRefreshing(true);//打开加载动画
        if (!LYJNetwork.isNetworkAvailable(MainActivity.this)) {
            Snackbar.make(swipeRefreshLayout, "没有网络你逗我玩啊?", Snackbar.LENGTH_SHORT).show();
            swipeRefreshLayout.setRefreshing(false);//没有网络时候直接关闭加载动画
            return;
        }
        //当为初始化的时候
        if(recyclerViewState==Constants.LISTVIEW_INIT){
            addStringToList();
            adapter=new LYJRecyclerViewAdapter(messageItems,100);
            recyclerView.setAdapter(adapter);
        }else if(recyclerViewState==Constants.LISTVIEW_REFRESH){
            //当为下拉刷新的时候
            messageItems.clear();
            addStringToList();
            recyclerView.setAdapter(null);
            adapter = new LYJRecyclerViewAdapter(messageItems,100);
            adapter.setOnItemClickListener(MainActivity.this);
            recyclerView.setAdapter(adapter);
        }else{
            //当为下滑加载的时候
            if(messageItems.size()!=100){
                addStringToList();
                adapter.notifyDataSetChanged();
            }
        }
        swipeRefreshLayout.setRefreshing(false);//执行完成也要关闭加载动画
    }

    @Override
    public void onItemClick(View view, int postion) {
        //每项的点击事件
    }

    //模拟增加数据
    public void addStringToList(){
        strFlag= Arrays.asList(getResources().getStringArray(R.array.welltest_array_string));
        for(int i=0;i<strFlag.size();i++){
            messageItems.add(strFlag.get(i));
        }
    }
}

这样谷歌官方控件的下拉刷新,上滑动加载就完成了。


从这里可以看到,虽然说ListView有点击事件,有许多扩展,但你想扩展ListView就必须重构很多地方。而RecyclerView,虽然什么都没有,但你扩展起来要方便的多。这就是从0开始的优势。当一个框架继承了很多很多东西,那么你要修改其中的东西,那么就是牵一发而动全身。没有最适合的框架,只有最优解。


本文源码:(MainActivity为本文源码,自行修改)


http://download.csdn.net/detail/liyuanjinglyj/9418932


看看最后实现的效果:


SwipeRefreshLayout与RecyclerView的巧夺天工

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/152921.html原文链接:https://javaforall.net

(0)
上一篇 2022年6月25日 下午1:46
下一篇 2022年6月25日 下午1:46


相关推荐

  • mixpanel实验教程(1)

    mixpanel实验教程(1)

    2022年2月6日
    42
  • C语言贪吃蛇代码_c语言贪吃蛇游戏

    C语言贪吃蛇代码_c语言贪吃蛇游戏一、C语言贪吃蛇代码实现前言设计贪吃蛇游戏的主要目的是让大家夯实C语言基础,训练编程思维,培养解决问题的思路,领略多姿多彩的C语言。贪吃蛇是非常经典的一款游戏,本次我们模拟在控制台实现贪吃蛇游戏,也就是实现贪吃蛇的基本功能,比如在地图中,用“↑↓←→”控制移动蛇的方向,吃掉食物之后,蛇身体会变长等等。。。。首先我们得分析,游戏中我们会碰见的一些情况。①蛇的部分,蛇的身子是一节一节的,此时最容易联想到的数据结构就是顺序表,链表,如果把蛇比做顺序表或者链表,在之后吃到食物的时候,身子肯定会变长,

    2025年9月6日
    6
  • Android 3D画廊采用Gallery实现无限循环、自动轮播

    Android 3D画廊采用Gallery实现无限循环、自动轮播公司最近有一个需求,是打算做一个轮播图的展示界面,不过和传统意义上不同,并非是在手机app的顶部展示几张定时切换的固定大小宽高的图片,而是中间长方形,两边向里倾斜,形成对称感的特殊界面,如下图:需要实现功能:无限循环,自动跳转,倒影效果。(原本的企划是动画轮播的时候,下面会呈现一条Listview,里面会因为展示的不同界面而呈现不同的内容,但是后面放弃了。)下面开始上代码:

    2022年6月13日
    46
  • SSM的简介

    SSM的简介SSM的简介

    2022年4月22日
    50
  • AvalonDock 2.0+Caliburn.Micro+MahApps.Metro实现Metro风格插件式系统(一)

    AvalonDock 2.0+Caliburn.Micro+MahApps.Metro实现Metro风格插件式系统(一)随着IOS7由之前UI的拟物化设计变为如今的扁平化设计,也许扁平化的时代要来了,当然我们是不是该吐槽一下,苹果什么时候也开始跟风了,自GOOGLE和微软界面扁平化过后,苹果也加入了这一队伍。AvalonDock  AvalonDock是一个.NET库,用于在停靠模式布局(docking)中排列一系列WPF/WinForm控件。最新发布的版本原生支持MVVM框架、AeroSnap特效…

    2022年7月20日
    16
  • ps快捷键大全表格汇总_ps调色快捷键大全表格图

    ps快捷键大全表格汇总_ps调色快捷键大全表格图【photoshop快捷键大全】 F1 帮助 F2 剪切 F3 拷贝 F4 粘贴 F5 隐藏/显示画笔面板 F6 隐藏/显示颜色面板 F7 隐藏/显示图层面板 F8 隐藏/显示信息面板.

    2026年4月18日
    4

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号