增强版的ListView——RecyclerView的使用

    Android 5.0以后提供了一个更强大滚动控件——RecyclerView,可以说是一个增强版的ListView,不仅可以轻松实现ListView 同样的效果,还优化了ListView 中存在的不足之处。RecyclerView 架构提供了一种插拔式的体验,它具有高度的解藕、异常的灵活性和更高的效率,通过设置他提供的不同的LayoutManager、ItemDecoration、ItemAnimator 可实现更加丰富多样的效果。但是不得不说的是RecyclerView 也有缺点和让人头疼的地方:设置列表的分割线需要自定义,另外列表的点击事件也需要自己去实现。OK,下面开始讲解RecyclerView 的基本用法。

    1、想要使用RecyclerView这个控件,首先要在项目的build.gradle 中添加相应的依赖库才行。打开app/build.gradle 文件,在dependencies 闭包中添加如下内容:  implementation 'com.android.support:recyclerview-v7:26.1.0'   

    添加成功后修改activity_main.xml 文件,代码如下

<?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.RecyclerView
        android:id="@+id/recyclerView"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

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

</LinearLayout>

    还要创建一个适配器,新建MyAdapter类,让这个适配器继承RecyclerView.Adapter,并将泛型指定为MyAdapter.ViewHolder。

public class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyViewHolder> {

    private List<String> mList;
    private Context mContext;

    /**
     * 自定义适配器
     *
     * @param context
     * @param list
     */
    public MyAdapter(Context context, List<String> list) {
        this.mContext = context;
        this.mList = list;
    }

    /**
     * 创建自定义的ViewHolder
     *
     * @param parent
     * @param viewType
     * @return
     */
    @Override
    public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        MyViewHolder holder = new MyViewHolder(LayoutInflater.from(mContext).
                inflate(R.layout.item_recycler, parent, false));
        return holder;
    }

    /**
     * 绑定ViewHolder
     *
     * @param holder
     * @param position
     */
    @Override
    public void onBindViewHolder(final MyViewHolder holder, final int position) {
        holder.tvItem.setText(mList.get(position));
    }

    @Override
    public int getItemCount() {
        return mList.size();
    }

    /**
     * 自定义ViewHolder
     */
    class MyViewHolder extends RecyclerView.ViewHolder {

        TextView tvItem;

        public MyViewHolder(View itemView) {
            super(itemView);
            tvItem = (TextView) itemView.findViewById(R.id.tv_item);
        }
    }

}

 其中的item_recycler.xml 是一个只包含TextView的item,代码如下

<?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="wrap_content"
    android:orientation="vertical">

    <TextView
        android:id="@+id/tv_item"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:gravity="center"
        android:text="111"
        android:textSize="18sp" />
</LinearLayout>

    在修改下MainActivity 中的代码,就可以初步实现一个简单的RecyclerView 了。 

        for (int i = 0; i < 20; i++) {
            lists.add(i + "");
        }
        mRecyclerView = (RecyclerView) findViewById(R.id.recyclerView);
        // 设置布局管理器  默认是垂直布局
        mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
        // 设置item 增加和删除时候的动画
        mRecyclerView.setItemAnimator(new DefaultItemAnimator());

        myAdapter = new MyAdapter(this, lists);
        mRecyclerView.setAdapter(myAdapter);

    而想要设置水平布局,代码如下设置

        // 设置水平布局
        LinearLayoutManager manager=new LinearLayoutManager(this);
        manager.setOrientation(LinearLayoutManager.HORIZONTAL);
        mRecyclerView.setLayoutManager(manager);

    还有网格布局,这个布局管理器的第一个参数是一行显示多少个,第二个参数是以什么形式显示

        // 设置网格布局                                                                                  
        StaggeredGridLayoutManager layoutManager = new StaggeredGridLayoutManager(4,
                LinearLayoutManager.VERTICAL);
        mRecyclerView.setLayoutManager(layoutManager);

    代码跑起来后,很明显的发现,并没有像ListView那样,有默认的分割线,这就需要我们自己继承RecyclerView.ItemDecoration 来自定义分割。我自己写了一个MyDividerItemDecoration类,代码如下:

/**
 * author: smile .
 * date: On 2018/5/19
 */
public class MyDividerItemDecoration extends RecyclerView.ItemDecoration {

    private static final int[] ATTRS = new int[]{
            android.R.attr.listDivider
    };
    // 分割线的方向参数
    public static final int HORIZONTAL_LIST = LinearLayoutManager.HORIZONTAL;
    public static final int VERTICAL_LIST = LinearLayoutManager.VERTICAL;

    private Drawable mDivider;
    // 分割线的高度  默认是mDivider.getIntrinsicHeight()=1
    private int mDividerHeight = 2;
    // 分割线的方向
    private int mOrientation;
    // 自定义画笔工具
    private Paint mPaint;

    /**
     * 构造函数  自定义分割线
     *
     * @param context     环境
     * @param orientation 布局方向
     */
    public MyDividerItemDecoration(Context context, int orientation) {
        final TypedArray array = context.obtainStyledAttributes(ATTRS);
        mDivider = array.getDrawable(0);
        array.recycle();
        setOrientation(orientation);
    }

    /**
     * 构造函数  自定义分割线
     *
     * @param context     环境
     * @param orientation 布局方向
     * @param drawableId  分割线图片
     */
    public MyDividerItemDecoration(Context context, int orientation, int drawableId) {
        mDivider = ContextCompat.getDrawable(context, drawableId);
        mDividerHeight = mDivider.getIntrinsicHeight();
        setOrientation(orientation);
    }

    /**
     * 构造函数  自定义分割线
     *
     * @param context      环境
     * @param orientation  布局方向
     * @param dividerHight 分割线高度
     * @param dividerHight 分割线颜色
     */
    public MyDividerItemDecoration(Context context, int orientation, int dividerHight, int dividerColor) {
        mDividerHeight = dividerHight;
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaint.setColor(dividerColor);
        mPaint.setStyle(Paint.Style.FILL);
        setOrientation(orientation);
    }

    /**
     * 判断绘制横向的分割线还是纵向的分割线
     *
     * @param orientation 布局方向
     */
    public void setOrientation(int orientation) {
        if (orientation != HORIZONTAL_LIST && orientation != VERTICAL_LIST) {
            // 跑出非法参数异常
            throw new IllegalArgumentException("invalid orientation");
        }
        mOrientation = orientation;
    }

    /**
     * 进行绘制
     *
     * @param c      画布
     * @param parent 父控件
     * @param state
     */
    @Override
    public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
        super.onDraw(c, parent, state);
        // 判断选择的方向是什么 来确定绘制什么方向的分割线
        if (mOrientation == VERTICAL_LIST) {
            drawVertical(c, parent);
        } else {
            drawHorizontal(c, parent);
        }
    }

    /**
     * 绘制垂直方向的分割线
     *
     * @param c
     * @param parent
     */
    private void drawVertical(Canvas c, RecyclerView parent) {
        // 获取左边的X值
        final int left = parent.getPaddingLeft();
        // 获取右边的X值
        final int right = parent.getWidth() - parent.getPaddingRight();
        // 获取子控件的数量
        final int childCount = parent.getChildCount();
        for (int i = 0; i < childCount; i++) {
            // 获取子控件
            final View child = parent.getChildAt(i);
            RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
            // 获取子空间的底部  即分割线的顶部 Y值
            final int top = child.getBottom() + params.bottomMargin;
            // 获取分割线的底部     顶部+分割线的高度
            final int bottom = top + mDividerHeight;
            Log.e("MyDivider", "drawVertical: " + left + "-------" + top + "-------"
                    + right + "------" + bottom);
            if (mDivider != null) {
                // 设置分割线的范围
                mDivider.setBounds(left, top, right, bottom);
                // 绘制分割线
                mDivider.draw(c);
            }

            if (mPaint != null) {
                c.drawRect(left, top, right, bottom, mPaint);
            }
        }
    }

    /**
     * 绘制水平方向的分割线
     *
     * @param c
     * @param parent
     */
    private void drawHorizontal(Canvas c, RecyclerView parent) {
        // 获取分割线的顶部Y 值
        final int top = parent.getPaddingTop();
        // 获取分割线的底部Y 值
        final int bottom = parent.getHeight() - parent.getPaddingBottom();
        // 获取子控件的数量
        final int childCount = parent.getChildCount();
        for (int i = 0; i < childCount; i++) {
            // 获取子控件
            final View child = parent.getChildAt(i);
            RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
            // 获取子空间的右边  即分割线的左边X值
            final int left = child.getRight() + params.rightMargin;
            // 获取分割线的右边  左边+分割线的高度
            final int right = left + mDividerHeight;
            if (mDivider != null) {
                // 设置分割线的范围
                mDivider.setBounds(left, top, right, bottom);
                // 绘制分割线
                mDivider.draw(c);
            }

            if (mPaint != null) {
                c.drawRect(left, top, right, bottom, mPaint);
            }
        }
    }

    /**
     * 设置item 四个方向的值
     *
     * @param outRect
     * @param view
     * @param parent
     * @param state
     */
    @Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
        super.getItemOffsets(outRect, view, parent, state);
        // 为outRect 设置的四个方向的值   最终也会计入RecyclerView
        if (mOrientation == VERTICAL_LIST) {
            outRect.set(0, 0, 0, mDividerHeight);
        } else {
            outRect.set(0, 0, mDividerHeight, 0);
        }
    }
}

    这个MyDividerItem类中,提供了三个构造函数:第一个构造函数式只设置布局方向,分割线的样式和高度跟随系统默认。第二个构造函数是设置布局方向以及分割线的图片,分割线的样式由图片决定。第三个构造函数是设置布局方向、分割线的高度和颜色,采用画笔的方式将分割线实现。 设置完自定义的分割线后,我们只要在setAdapter 之前加入代码便可以加入分割线。

        // 设置分割线
        mRecyclerView.addItemDecoration(new MyDividerItemDecoration(this,
                MyDividerItemDecoration.VERTICAL_LIST, 3, Color.BLACK));

    接下来我们来自定义点击事件,自定义点击事件其实并不是很难,先自定义一个 OnItemClickListener 接口并提供回调,用来监听点击和长按事件,代码如下:

/**
 * 自定义回调接口
 */
public interface OnItemClickListener {
    void onItemClick(View view, int position);

    void onItemLongClick(View view, int position);
}

    在MyAdpater适配器中设置接口方法回调给对象

    /**
     * 设置接口回调对象
     *
     * @param onItemClickListener
     */
    public void setOnItemClickListener(OnItemClickListener onItemClickListener) {
        this.mOnItemClickListener = onItemClickListener;
    }
     接下来对item中的控件进行点击事件的监听并回调给我们的自定义的监听,代码如下:
    /**
     * 绑定ViewHolder
     *
     * @param holder
     * @param position
     */
    @Override
    public void onBindViewHolder(final MyViewHolder holder, final int position) {
        holder.tvItem.setText(mList.get(position));
        if (mOnItemClickListener != null) {
            holder.tvItem.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    int pos = holder.getLayoutPosition();
                    mOnItemClickListener.onItemClick(v, pos);
                }
            });
            holder.tvItem.setOnLongClickListener(new View.OnLongClickListener() {
                @Override
                public boolean onLongClick(View v) {
                    int pos = holder.getLayoutPosition();
                    mOnItemClickListener.onItemLongClick(v, pos);
                    return false;
                }
            });
        }
    }

    最后在Activity中对适配器设置监听:

        // 设置点击事件的回调
        myAdapter.setOnItemClickListener(new OnItemClickListener() {
            @Override
            public void onItemClick(View view, int position) {
                Toast.makeText(MainActivity.this, "这是点击的第" + position + "条",
                        Toast.LENGTH_SHORT).show();
            }

            @Override
            public void onItemLongClick(View view, final int position) {
                Toast.makeText(MainActivity.this, "这是长按的第" + position + "条",
                        Toast.LENGTH_SHORT).show();
            }
        });
    最后我还想添加一个长按当前Item 删除的功能。首先在MyAdapter 中添加一个removeData的方法
    /**
     * 删除数据并更新界面
     *
     * @param position
     */
    public void removeData(int position) {
        mList.remove(position);
        notifyItemRemoved(position);
    }
    然后更改长按监听的事件逻辑处理就可以了。
        // 设置点击事件的回调
        myAdapter.setOnItemClickListener(new OnItemClickListener() {
            @Override
            public void onItemClick(View view, int position) {
                Toast.makeText(MainActivity.this, "这是点击的第" + position + "条",
                        Toast.LENGTH_SHORT).show();
            }

            @Override
            public void onItemLongClick(View view, final int position) {
                new AlertDialog.Builder(MainActivity.this)
                        .setTitle("确定要删除吗?")
                        .setNegativeButton("取消", null)
                        .setPositiveButton("确定", new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialog, int which) {
                                myAdapter.removeData(position);
                            }
                        }).show();
            }
        });
    这时候长按会弹出对话框,删除时会有消失的动画效果。

    

    今天520,前几天重新回到了伟大的单身狗阶级,所以今天有时间写博客。我想对她说,我以后可能再也不能陪你走下去了,不要太要强,好好照顾自己,一定要幸福!!! 



猜你喜欢

转载自blog.csdn.net/javasxl/article/details/80376113