这里我们来说说RecyclerView以及它的自定义分割线(下)

nice,终于有空,这波我们接着上次的继续说。
上次说的是RecyclerView的Adppter以及它的ViewHolder,这次我们来说说RecyclerView的点击事件以及它的自定义分隔线Decoration。
首先,我们说说点击事件,在RecyclerView的Adapter中的onBindViewHolder中设置它的点击事件,让我们来看看具体怎么写。
我们先创建一个接口 MyRecyclerViewClickListener,它内部有两个抽象方法,分别是:

interface MyRecyclerViewClickListener {

    void click(int position);
    boolean longClick(int position);
}

别急,这两个方法的作用继续往后看,我们就能清楚。
接着我们来看看Adapter中代码应该怎么写,代码如下:

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

    private List<String> strList;
    //这里定义了刚才我们定义的接口
    private MyRecyclerViewClickListener listener;

    //添加了一个用于设置MyRecyclerViewClickListener的方法
    public void setOnRecyclerViewClickListener(MyRecyclerViewClickListener listener){

        this.listener = listener;

    }

    @Override
    public void onBindViewHolder(final MyAdapter.MyViewHodler holder, int position) {

        //根据position和初始化MyAdapter传入的strList 来设置子项中TextView要显示的数据
        holder.view.setText(list.get(position));

        //这里我们对RecyclerView子项中的TextView设置点击事件
        holder.tv.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                //调用MyRecyclerViewClickListener使具体实现由Activity实现
                if (listener != null){

                    int pos = holder.getLayoutPosition();
                    listener.onClick(pos);

                }else throw new RuntimeException("params exception");

            }
        });


        //同样我们对长按的事件也响应一下
        holder.tv.setOnLongClickListener(new View.OnLongClickListener() {
            @Override           
            public boolean onLongClick(View v) {
                if (listener != null){
                    int pos = holder.getLayoutPosition();
                    listener.onLongClick(pos);

                }else throw new RuntimeException("params exception");

                return true;
            }
        });

    }

看上面的代码,我们可以知道,我们定义的接口的作用就是能使点击事件在我们想要写的地方再去实现,就像这个示例里,我打算在Activity中再去实现我的点击事件,所以我调用了这两个接口中的方法。当然你也可以直接在Adapter中就实现点击事件(刘望舒老师的书中是自己写了接口的,我个人觉得定义一个接口更好,这样看起来,或者使用起来更方便),你也可以对RecyclerView中子项的其他控件来设置点击事件。这些都看你的实际需求(当然,也可以对子项中的父布局来设置点击事件,这样就能包裹整个子项)。

下面,我们看看MainActivity中的代码,如下:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {

        //这里省略之前的代码

        adapter.setRecyclerViewClickListener(new MyRecyclerViewClickListener() {
            @Override
            public void click(int position) {

                Toast.makeText(MainActivity.this,"you click "
                +position,Toast.LENGTH_SHORT).show();

            }

            @Override
            public boolean longClick(final int position) {

                Toast.makeText(MainActivity.this,"you long click"
                +position,Toast.LENGTH_SHORT).show();

                return true;

            }
        });
    }
}

ok,搞定,这里我们只是简单的弹了一个Toast,当然,也可以实现更复杂的点击事件,如长按删除等,这些就等待朋友们自己实现了。

RecyclerView设置点击事件,其实就是对它的子项设置点击事件(我是这么理解的)。

说完点击事件,我们来说说分隔线,分隔线还是比较麻烦,需要自己计算,下面,我们就来看看自定义分隔线。
首先,创建一个类继承RecyclerView.ItemDecoration,如下:

public class MyItemDecoration extends RecyclerView.ItemDecoration {
}

可以看到,继承以后并没有必须要重写的方法。其实ItemDecoration中只有3个方法,如下:
1.void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state):此方法是绘制分隔线的方法。
2.void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state):顾名思义,这个方法是绘制完成调用的。
3.void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state):
这个方法用于设置item的padding属性的。

在这里,主要的是对onDraw方法的重写,下面我们来看看代码:

public class MyItemDecoration extends RecyclerView.ItemDecoration {

    //这里定义的是分隔线的样式
    private static final int[] ATTR = new int[]{android.R.attr.listDivider};
    //这里定义的int值表示RecyclerView子项的排列方式
    public static final int VERTICAL = LinearLayoutManager.VERTICAL;
    public static final int HORIZONTAL = LinearLayoutManager.HORIZONTAL;
    private int mOrientation;
    private Context mContext;
    private Drawable mDivider;

    public MyItemDecoration(Context context,int orientation) {

        //获取样式的属性集合,里面记录了该分隔线的各种属性
        TypedArray a = context.obtainStyledAttributes(ATTR);
        mDivider = a.getDrawable(0);
        a.recycle();
        mContext =context;

        //检查用户传进来的orientation的值是否正确
        setOrientation(orientation);

    }

    private void setOrientation(int orientation) {

        if (orientation != VERTICAL && orientation != HORIZONTAL){

            throw new RuntimeException("请检查参数是否正确");

        }
        mOrientation = orientation;

    }

    @Override
    public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {

        //根据用户传进来的orientation来确定怎么绘制分隔线
        if (mOrientation == VERTICAL){

            drawVertical(c,parent);

        }else{

            drawHorizontal(c,parent);

        }

    }

    //当RecyclerView是水平线排列时调用
    private void drawHorizontal(Canvas c, RecyclerView parent) {

        int top = parent.getTop();
        int bottom = parent.getBottom();
        int count = parent.getChildCount();
        for (int i = 0;i < count;i++){

            View child = parent.getChildAt(i);

            RecyclerView.LayoutParams params= 
            (RecyclerView.LayoutParams)child.getLayoutParams();

            int left = child.getRight()+ params.leftMargin;
            int right = left + mDivider.getIntrinsicWidth();

            mDivider.setBounds(left,top,right,bottom);
            mDivider.draw(c);
        }

    }
    //当RecyclerView是竖直排列时调用
    private void drawVertical(Canvas c, RecyclerView parent) {

        int left = parent.getPaddingLeft();
        int right = parent.getWidth() - parent.getPaddingRight();
        int count = parent.getChildCount();

        Log.v("left",left+"");

        for(int i = 0 ; i< count;i++){

            View child = parent.getChildAt(i);

            RecyclerView.LayoutParams params= 
            (RecyclerView.LayoutParams) child.getLayoutParams();

            int top = child.getBottom() + params.bottomMargin;
            int bottom = top + mDivider.getIntrinsicHeight();

            mDivider.setBounds(left,top,right,bottom);
            mDivider.draw(c);

        }

    }
}

从代码中,我们可以知道,样式是我们定义好的,获取该样式的属性集合,判断用户传进来的参数等等。
最主要的是onDraw方法,根据用户传的参数,来绘制分隔线。我们来看看分隔线位置是怎么计算的。

首先看drawHorizontal,也就是当RecyclerView的子项是水平线排列时,我们来看看代码:

private void drawHorizontal(Canvas c, RecyclerView parent) {

        int top = parent.getPaddingTop()
        int bottom = parent.getHeight() - parent.getPaddingBottom();
        int count = parent.getChildCount();
        for (int i = 0;i < count;i++){

            View child = child.getChildAt(i);

            RecyclerView.LayoutParams params= 
            (RecyclerView.LayoutParams)child.getLayoutParams();

            int left = parent.getRight()+ params.leftMargin;
            int right = left + mDivider.getIntrinsicWidth();

            mDivider.setBounds(left,top,right,bottom);
            mDivider.draw(c);
        }

    }

当RecyclerView子项水平线排列时,我们的分隔线应该是垂直的竖线。那么,分隔线的top和bottom是不变的,会变得是分隔线的left和right,首先我们看top和bottom怎么计算:
int top = parent.getPaddingTop(); 直接获取RecyclerView中父控件的PaddingTop的值
int bottom = parent.getHeight() - parent.getPaddingBottom(); RecyclerView的高度减去父控件中的PaddingBottom的值
那么,决定这两个值的就是activity_main.xml文件中 RecyclerView的宽高,以及父控件就是LinearLayout的padding属性了。
再看left和right怎么算:
int left = child.getRight()+ params.leftMargin; RecyclerView子项的right加上RecyclerView子项的父布局中的marginRight的值(也就是layout_recycler.xml中RelativeLayout中的Margin值)
int right = left + mDivider.getIntrinsicWidth(); 算出来的left的值加上分隔线的实际宽度
看到这就有点费解了,为什么left是获取RecyclerView的right而不是left,这就是分隔线在RecyclerView子项的左边还是右边的问题了,如果获取的是RecyclerView的left则分隔线在子项的左边,反之则是右边,算出位置以后后面的代码就是绘制分隔线了。

下面,我们看看RecyclerView子项竖直排列怎么算:

private void drawVertical(Canvas c, RecyclerView parent) {

        int left = parent.getPaddingLeft();
        int right = parent.getWidth() - parent.getPaddingRight();
        int count = parent.getChildCount();

        Log.v("left",left+"");

        for(int i = 0 ; i< count;i++){

            View child = parent.getChildAt(i);

            RecyclerView.LayoutParams params= 
            (RecyclerView.LayoutParams) child.getLayoutParams();

            int top = child.getBottom() + params.bottomMargin;
            int bottom = top + mDivider.getIntrinsicHeight();

            mDivider.setBounds(left,top,right,bottom);
            mDivider.draw(c);

        }

    }

当RecyclerView子项竖直排列时,我们的分隔线应该是水平线。那么,分隔线的left和right是不变的,会变得是分隔线的top和bottom,首先我们看top和bottom怎么计算:
int left = parent.getPaddingLeft(); 获取RecyclerView父控件的PaddingLeft
int right = parent.getWidth() - parent.getPaddingRight(); 获取RecyclerView的宽度减去父控件的PaddingRight
那么,决定这两个值的就是activity_main.xml文件中 RecyclerView的宽高,以及父控件就是LinearLayout的padding属性了。
再看top和bottom怎么算:
int top = child.getBottom() + params.bottomMargin; RecyclerView子项的Bottom值加上定义RecyclerView子项的父布局的MarginBottom的值(也就是layout_recycler中的RelativeLayout)
int bottom = top + mDivider.getIntrinsicHeight(); 算出来的top的值加上分隔线的实际宽度
同理,child的getBottom或者getTop方法,会改变分隔线的位置算出位置以后后面的代码就是绘制分隔线了。
总结一下,这样定义分隔线的能改变分隔线位置的只有activity_main.xml中LinearLayout的padding会改变分隔线的长度,RecyclerView子项的父布局(也就是layout_recycler中的RelativeLayout)中的margin属性会改变分隔线的位置。并且代码中,我们用for循环遍历每一个子项。并算出分隔线的位置。

在MainActivity中 我们只要加上一句代码就可以了:

recyclerView.addItemDecoration(new MyItemDecoration(this,MyItemDecoration.VERTICAL));

需要注意的是,这句代码要在RecyclerView关联Adapter之前,下面我们来看看效果
这里写图片描述
如果大家对计算位置有疑问,可以给RecyclerView设置个半透明背景,然后在其他控件里加上Padding或者Margin属性看看。

以上,就是刘望舒老师书中对RecyclerView以及其自定义分隔线的描述,以及我自己的见解。(以后我的博文中会尽量少提到刘望舒老师,以防有人说我给他打广告,看看我的第一篇博文就知道我为什么总说他了。)

本人菜鸟,不对之处,望各路大神指教。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值