牛骨文教育服务平台(让学习变的简单)

(一).前言:

作为Android L开始,Google更新了新控件RecyclerView和CardView,这两个控件在之前的文章中已经做了详细介绍和使用,同时在前面还对下拉刷新组件SwipeRefreshLayout进行相关讲解。本来该专题不在更新了,正好昨天有一个群友问到了怎么样结合SwipeRefreshLayout,RecyclerView,CardView这三种控件实现表格布局界面并且加入下拉刷新和上拉加载更多的效果,那么今天我们来实现并且一步步的完善Demo。

同时关于RecyclerView,CardView,SwipeRefreshLayout控件的使用讲解如下:

具体代码已经上传到下面的项目中,欢迎各位去star和fork一下。

FastDev4Android框架项目地址:https://github.com/jiangqqlmj/FastDev4Android

(二).需求介绍

以上三种控件结合需要实现的效果如下:

分析需要实现的界面的效果,首先是表格布局(GirdView)的列表,并且加入下拉刷新和上拉加载更多效果。这边对于表格中每一项我们可以使用CardView,然后列表这块使用RecyclerView,刷新这块我们采用Android给我们提供的SwipeRefreshLayout控件,下面我们来具体实现以下这个效果:

(三).实例实现(基础):

实现的第一种方法,我们可以把界面中的每一项Item布局都采用CardView来实现,那么每一项最终形成一个表格布局(GirdView),主列表采用RecyclerView,那么这边的布局管理器需要采用GridLayoutManger并且每行两列分布即可,通过前面的文章我们知道SwipeRefreshLayout的使用方法在RecyclerView外部套用即可。

3.1.Item View的布局文件如下:

<?xmlversion="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView
       xmlns:android="http://schemas.android.com/apk/res/android"
       xmlns:cardview="http://schemas.android.com/apk/res-auto"
       android:id="@+id/instance_cardview"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       cardview:cardBackgroundColor="@color/black"
        >
        <LinearLayout
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:orientation="vertical"
           android:background="@color/black"
            android:gravity="center"
           android:layout_gravity="center_horizontal"
            >
            <ImageView
               android:layout_margin="3dp"
               android:id="@+id/item_img"
               android:layout_width="150dp"
               android:layout_height="150dp"
               android:scaleType="fitXY"
                />
            <TextView
               android:textSize="16sp"
               android:layout_margin="3dp"
               android:textColor="@color/white"
               android:id="@+id/item_tv"
               android:layout_width="wrap_content"
               android:layout_height="wrap_content"
                android:maxLines="2"
               android:lines="2"/>
        </LinearLayout>
    </android.support.v7.widget.CardView>

该布局主体为一个CardView,在CardView内部为一个图片和标题TextView组成。

3.2.接着创建继承自RecyclerView.Adapter的ComInstanceAdapter适配器即可。查看下面的代码就可以知道,我在里边自定义两个ViewHolder,一个专门承载Item View,另一个是承载列表底部Foot View(用来显示上拉加载更多提示和进度的)。也就是我们会在布局添加刷新的时候会在RecyclerView的底部多添加一个布局View(Foot View)(该具体使用方法我们在前面的文章已经讲过了,如不了解可以查看前文)。并且我们这边已经也为RecyclerView扩展的Item点击事件了。唯一和以前实现代码不一样的为:

 @Override
    public int getItemCount() {
        if(mInstanceBeans.size()%2==0){
            //偶数
            return mInstanceBeans.size()+1;
        }else{
            return mInstanceBeans.size()+2;
        }
    }
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        if(holder instanceof ItemViewHolder){
           if(position<mInstanceBeans.size()){
                ((ItemViewHolder)holder).item_img.setImageResource(mInstanceBeans.get(position).getImg());
               ((ItemViewHolder)holder).item_tv.setText(mInstanceBeans.get(position).getTitle());
               holder.itemView.setTag(position);
               holder.itemView.setClickable(true);
            }else {
                ((ItemViewHolder)holder).item_img.setImageResource(R.drawable.moren);
               ((ItemViewHolder)holder).item_tv.setText("");
               holder.itemView.setClickable(false);
            }
        }else if(holder instanceof FootViewHolder){
            //上拉加载更多布局数据绑定
        }
    }

上面数量我进行判断需要绑定的数据是偶数还是奇数,因为我们这边的每一行是两个数据,如果我们的数据是奇数的话,那么这边最后的FootView布局会加在右边了如下显示:

这样的效果是比较丑的,所以要把FootView移动到底部,奇数情况数据进行加1即可,最后数据绑定的时候控制显示一下。

ComInstanceAdapter完成代码如下:

public class ComInstanceAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
    private Context mContext;
    private List<InstanceBean> mInstanceBeans;
    private LayoutInflater mInflater;
    //布局新增一项类别
    //普通ITEM
    private static final int ITEM_VIEW=1;
    //FOOT ITEM
    private static final int FOOT_VIEW=2;
 
    public ComInstanceAdapter(Context context,List<InstanceBean> pInstanceBeans){
        this.mContext=context;
        this.mInstanceBeans=pInstanceBeans;
       mInflater=LayoutInflater.from(this.mContext);
    }
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(final ViewGroup parent, int viewType) {
        if (viewType == ITEM_VIEW) {
            final View view =mInflater.inflate(R.layout.com_instance_item_layout, parent, false);
            view.setOnClickListener(newView.OnClickListener() {
                @Override
                public void onClick(View v) {
                    if (onItemClickListener !=null) {
                       onItemClickListener.onItemClick(view, (int) view.getTag());
                    }
                }
            });
            return new ItemViewHolder(view);
        } else if (viewType == FOOT_VIEW) {
            View view =mInflater.inflate(R.layout.instance_load_more_layout, parent, false);
            return new FootViewHolder(view);
        }
        return null;
    }
    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        if(holder instanceof ItemViewHolder){
           if(position<mInstanceBeans.size()){
                ((ItemViewHolder)holder).item_img.setImageResource(mInstanceBeans.get(position).getImg());
               ((ItemViewHolder)holder).item_tv.setText(mInstanceBeans.get(position).getTitle());
               holder.itemView.setTag(position);
               holder.itemView.setClickable(true);
            }else {
                ((ItemViewHolder)holder).item_img.setImageResource(R.drawable.moren);
               ((ItemViewHolder)holder).item_tv.setText("");
               holder.itemView.setClickable(false);
            }
        }else if(holder instanceofFootViewHolder){
            //上拉加载更多布局数据绑定
        }
    }
 
    @Override
    public int getItemViewType(int position) {
        if (position + 1 == getItemCount()) {
            return FOOT_VIEW;
        } else {
            return ITEM_VIEW;
        }
    }
    @Override
    public int getItemCount() {
        if(mInstanceBeans.size()%2==0){
            //偶数
            return mInstanceBeans.size()+1;
        }else{
            return mInstanceBeans.size()+2;
        }
    }
 
    public static class ItemViewHolder extends RecyclerView.ViewHolder{
        private ImageView item_img;
        private TextView item_tv;
        public ItemViewHolder(View itemView) {
            super(itemView);
           item_img=(ImageView)itemView.findViewById(R.id.item_img);
           item_tv=(TextView)itemView.findViewById(R.id.item_tv);
        }
    }
    /**
     * 底部FootView布局
     */
    public static class FootViewHolder extends  RecyclerView.ViewHolder{
        private TextView foot_view_item_tv;
        public FootViewHolder(View view) {
            super(view);
           foot_view_item_tv=(TextView)view.findViewById(R.id.foot_view_item_tv);
        }
    }
    /**
     * Item 点击监听回调接口
     */
    public interface OnItemClickListener {
        void onItemClick(View view,intposition);
    }
    private OnItemClickListener onItemClickListener;
 
    public OnItemClickListener getOnItemClickListener() {
        return onItemClickListener;
    }
    public void setOnItemClickListener(OnItemClickListener onItemClickListener) {
        this.onItemClickListener =onItemClickListener;
    }
 
    /**
     * 进行下拉刷新数据添加 并且刷新UI
     * @param pInstanceBeans
     */
    public void addRefreshBeans(List<InstanceBean> pInstanceBeans){
         List<InstanceBean> temp=new ArrayList<InstanceBean>();
         temp.addAll(pInstanceBeans);
         temp.addAll(mInstanceBeans);
        mInstanceBeans.removeAll(mInstanceBeans);
         mInstanceBeans.addAll(temp);
         notifyDataSetChanged();
    }
    /**
     * 进行上拉加载更多 并且刷新UI
     * @param pInstanceBeans
     */
    public void addMoreBeans(List<InstanceBean> pInstanceBeans){
        mInstanceBeans.addAll(pInstanceBeans);
        notifyDataSetChanged();
    }
}

3.3.接下来是布局RecyclerView和SwipeRefreshLayout布局文件了,这个和之前的文章上面的一样,我们在RecyclerView外部嵌套SwipeRefreshLayout组件即可,最后在Activity中获取使用。

com_instance_layout.xml布局完整代码如下:

<?xmlversion="1.0" encoding="utf-8"?>
<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"android:layout_width="match_parent"
   android:layout_height="match_parent"
   android:background="@color/black">
    <includelayout="@layout/common_top_bar_layout"/>
   <android.support.v4.widget.SwipeRefreshLayout
       android:id="@+id/instance_swiperefreshlayout"
       android:layout_width="fill_parent"
       android:layout_height="fill_parent"
        android:scrollbars="none">
    <android.support.v7.widget.RecyclerView
       android:id="@+id/instance_recycler"
       android:layout_width="fill_parent"
       android:layout_height="fill_parent"
       android:scrollbars="none"/>
   </android.support.v4.widget.SwipeRefreshLayout>
</LinearLayout>

3.4.最后Activity中初始化SwipeRefreshLayout控件并且设置背景和刷新进度条的颜色。RecyclerView控件初始化以及设置布局管理器(GirdLayoutManger)和适配器即可。

ComInstanceActivity完整代码如下:

public class ComInstanceActivity extends BaseActivity {
    private LinearLayout top_bar_linear_back;
    private TextView top_bar_title;
    private RecyclerView instance_recycler;
    private ComInstanceAdapter adapter;
    private SwipeRefreshLayout instance_swiperefreshlayout;
    private int lastVisibleItem;
    //是否正在加载更多的标志
    private boolean isMoreLoading=false;
    @Override
    protected void onCreate(BundlesavedInstanceState) {
        super.onCreate(savedInstanceState);
       setContentView(R.layout.com_instance_layout);
       top_bar_linear_back=(LinearLayout)this.findViewById(R.id.top_bar_linear_back);
       instance_swiperefreshlayout=(SwipeRefreshLayout)this.findViewById(R.id.instance_swiperefreshlayout);
        //设置刷新时动画的颜色,可以设置4个
       instance_swiperefreshlayout.setProgressBackgroundColorSchemeResource(android.R.color.white);
       instance_swiperefreshlayout.setColorSchemeResources(android.R.color.holo_blue_light,
                android.R.color.holo_red_light,android.R.color.holo_orange_light,
               android.R.color.holo_green_light);
       top_bar_linear_back.setOnClickListener(new CustomOnClickListener());
       top_bar_title=(TextView)this.findViewById(R.id.top_bar_title);
       top_bar_title.setText("综合实例");
       instance_recycler=(RecyclerView)this.findViewById(R.id.instance_recycler);
        final GridLayoutManager gridLayoutManager=new GridLayoutManager(this,2);
       instance_recycler.setLayoutManager(gridLayoutManager);
        instance_recycler.setAdapter(adapter =new ComInstanceAdapter(this, InstanceDataUtils.getInstanceBeans()));
        //添加Item点击监听事件
        adapter.setOnItemClickListener(new ComInstanceAdapter.OnItemClickListener() {
            @Override
            public void onItemClick(View view,int position) {
               Toast.makeText(ComInstanceActivity.this,"点击了第"+position+"项",Toast.LENGTH_SHORT).show();
            }
        });
        //下拉刷新
       instance_swiperefreshlayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
            @Override
            public void onRefresh() {
                  new Handler().postDelayed(newRunnable() {
                      @Override
                      public void run() {
                         List<InstanceBean> temp=new ArrayList<InstanceBean>();
                          for(inti=0;i<5;i++){
                             InstanceBean bean=new InstanceBean("我是杨颖Item"+i,R.drawable.baby);
                              temp.add(bean);
                          }
                         adapter.addRefreshBeans(temp);
                         instance_swiperefreshlayout.setRefreshing(false);
                         Toast.makeText(ComInstanceActivity.this, "更新了五条数据...", Toast.LENGTH_SHORT).show();
                      }
                  },3500);
            }
        });
        //上拉加载更多
       instance_recycler.setOnScrollListener(new RecyclerView.OnScrollListener() {
            @Override
            public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
               super.onScrollStateChanged(recyclerView, newState);
                if (newState ==RecyclerView.SCROLL_STATE_IDLE && lastVisibleItem + 1 ==adapter.getItemCount()) {
                    if(!isMoreLoading){
                      isMoreLoading=true;
                      newHandler().postDelayed(new Runnable() {
                        @Override
                        public void run() {
                           List<InstanceBean> temp=new ArrayList<InstanceBean>();
                            for (int i = 0; i< 5; i++) {
                                   InstanceBean bean=new InstanceBean("我是MoreItem"+i,R.drawable.meinv);
                                   temp.add(bean);
                            }
                           adapter.addMoreBeans(temp);
                           Toast.makeText(ComInstanceActivity.this, "上拉加载了五条数据...", Toast.LENGTH_SHORT).show();
                           isMoreLoading=false;
                        }
                    },2000);
                    }
                }
            }
 
            @Override
            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                super.onScrolled(recyclerView,dx, dy);
                lastVisibleItem =gridLayoutManager.findLastVisibleItemPosition();
            }
        });
 
    }
    class CustomOnClickListener implementsView.OnClickListener{
        @Override
        public void onClick(View v) {
                ComInstanceActivity.this.finish();
        }
    }
}

上面的代码中要使用SwipeRefreshLayout实现下拉刷新只要设置OnRefreshListener监听器即可,要实现上拉加载更多给RecyclerView添加OnScrollListener判断是否已经下拉滑动的底部,然后开始加载更多数据。

3.5.运行效果如下:

(四).实例实现(改进版):

看到上面的效果实现,我们会发现一个问题:FootView虽然在底部了,但是表格一行是两列的,所以FootView就会在底部的最左边了,只会占据一个CardView的空间。但是我们平时的效果应该是FootView是整一行实现的,这样比较美观。OK 下面我们来进行改进一下:

之前RecyclerView我们采用的是GirdLayoutManger布局,这边我们采用LinearLayoutManger垂直方向实现。这样的话每一行为单独的Item了,并且该行Item中我直接放上一个CardView布局,然后在内部添加两个水平布局的LinearLayout。但是这样修改之后Item的点击事件就要进行修改了,我们这边把点击事件添加在每一个LinearLayout布局。

4.1.advance_com_instance_item_layout完成布局代码如下:

<?xmlversion="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView
       xmlns:android="http://schemas.android.com/apk/res/android"
       xmlns:cardview="http://schemas.android.com/apk/res-auto"
       android:id="@+id/instance_cardview"
       android:layout_width="fill_parent"
       android:layout_height="wrap_content"
       cardview:cardBackgroundColor="@color/black"
        >
        <LinearLayout
           android:layout_width="fill_parent"
           android:layout_height="wrap_content"
           android:orientation="horizontal"
           android:background="@color/black"
            android:gravity="center"
           android:layout_gravity="center_horizontal"
            >
            <LinearLayout
               android:id="@+id/leftL"
               android:layout_width="fill_parent"
               android:layout_height="wrap_content"
               android:orientation="vertical"
               android:layout_weight="1"
               android:gravity="center">
                <ImageView
                   android:src="@drawable/meinv"
                   android:layout_margin="3dp"
                   android:id="@+id/item_img_one"
                   android:layout_width="150dp"
                   android:layout_height="150dp"
                   android:scaleType="fitXY"
                    />
                <TextView
                   android:text="古代美女"
                   android:textSize="16sp"
                   android:layout_margin="3dp"
                   android:textColor="@color/white"
                   android:id="@+id/item_tv_one"
                   android:layout_width="wrap_content"
                   android:layout_height="wrap_content"
                   android:maxLines="2"
                   android:lines="2"/>
            </LinearLayout>
            <LinearLayout
               android:id="@+id/rightL"
               android:layout_width="fill_parent"
               android:layout_height="wrap_content"
               android:orientation="vertical"
               android:layout_weight="1"
               android:gravity="center">
                <ImageView
                   android:src="@drawable/liuyan"
                   android:layout_margin="3dp"
                   android:id="@+id/item_img_two"
                   android:layout_width="150dp"
                   android:layout_height="150dp"
                   android:scaleType="fitXY"
                    />
                <TextView
                   android:text="柳岩"
                   android:textSize="16sp"
                   android:layout_margin="3dp"
                   android:textColor="@color/white"
                   android:id="@+id/item_tv_two"
                   android:layout_width="wrap_content"
                   android:layout_height="wrap_content"
                   android:maxLines="2"
                   android:lines="2"/>
            </LinearLayout>
        </LinearLayout>
    </android.support.v7.widget.CardView>

我们看到上面的布局中两个LinearLayout分别加上了两个id,用来后面获取控件并且添加点击监听事件。

4.2.然后自定义适配器中的数据绑定方法和之前的有所不同:

 public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        if(holder instanceof ItemViewHolder){
            AdvanceInstanceBean advanceInstanceBean=mAdvanceInstanceBeans.get(position);
            if(advanceInstanceBean!=null){
                final List<InstanceBean> instanceBeans=advanceInstanceBean.getInstanceBeans();
                if(instanceBeans.size()==2){
                    ((ItemViewHolder)holder).item_img_one.setImageResource(instanceBeans.get(0).getImg());
                   ((ItemViewHolder)holder).item_tv_one.setText(instanceBeans.get(0).getTitle());
                    ((ItemViewHolder)holder).item_img_two.setImageResource(instanceBeans.get(1).getImg());
                   ((ItemViewHolder)holder).item_tv_two.setText(instanceBeans.get(1).getTitle());
                            ((ItemViewHolder)holder).leftL.setOnClickListener(new View.OnClickListener() {
                        @Override
                        public voidonClick(View v) {
                            if(onItemClickListener != null) {
                               onItemClickListener.onItemClick(instanceBeans.get(0));
                            }
                        }
                    });
                    ((ItemViewHolder)holder).rightL.setOnClickListener(new View.OnClickListener() {
                        @Override
                        public voidonClick(View v) {
                           if(onItemClickListener!=null){
                               onItemClickListener.onItemClick(instanceBeans.get(1));
                            }
                        }
                    });
                }else {
                    ((ItemViewHolder)holder).item_img_one.setImageResource(instanceBeans.get(0).getImg());
                   ((ItemViewHolder)holder).item_tv_one.setText(instanceBeans.get(0).getTitle());
                    ((ItemViewHolder)holder).item_img_two.setImageResource(R.drawable.moren);
                   ((ItemViewHolder)holder).item_tv_two.setText("");
                }
            }
        }else if(holder instanceof FootViewHolder){
            //上拉加载更多布局数据绑定
        }
    }

上面的数据绑定代码中,给左右两个布局分别加入了onClick事件,来进行回调点击数据传递。具体回调接口定义如下:

/**
     * Item 点击监听回调接口
     */
    public interface OnItemClickListener {
 
        /**
         * item回调的数据
         * @param instanceBean
         */
        void onItemClick(InstanceBean instanceBean);
    }

具体完成代码比较多就不贴了,到时候大家clone一下项目代码:

FastDev4Android框架项目地址:https://github.com/jiangqqlmj/FastDev4Android

4.3.Acitivty中处理初始化设置的代码就不贴了

4.4.运行效果如下:

(五).最后总结

今天我们通过SwipeRefreshLayout+RecyclerView+CardView实现的表格布局以及下拉刷新,上拉加载更多的效果。

本次实例代码因为比较多,代码全贴比较浪费篇幅,重点在于讲解思路了。不过实例注释过的全部代码已经上传到Github项目中了。同时欢迎大家去Github站点进行clone或者fork浏览整个开源快速开发框架项目~

https://github.com/jiangqqlmj/FastDev4Android