Android学习随笔(8)------RecyclerView(2)

学习流程来自《第一行代码》(第二版)
在之前的RecyclerView(1)开头给出了对RecyclerView设置的几个模块。在书中只介绍了部分,这边来扩展一下。

GridLayoutManager

RecyclerView能实现的布局有很多,布局都由LayoutManager来控制,可拓展性十分高。

GridLayoutManager layoutManager = new GridLayoutManager(this, 2);

把RecyclerView的LayoutManager设置成GridLayout并传入上下文,和列数这两个参数即可。
Exler
我们仍就靠随机生成TextView的值,但是每个item的排列是网格类型的。

增加分隔线

可以明显的看出之前我们的RecyclerView是没有分隔线的。
addItemDecoration(ItemDecoration decoration) :方法就是用来设置分隔线的。
而ItemDecoration是一个抽象类,所以需要实现
1. void getItemOffsets ()
2. void onDraw ()
3. void onDrawOver ()
这三个方法。可以看出分隔线是要自己画上去的。添加Divider,主要是找到添加Divider的位置, 而Divider是在drawable文件中写好了的。 利用onDraw和onDrawOver都差不多,所以创建自己的Decoration类继承RecyclerView.ItemDecoration的时候,只要重写getItemOffsets(),还有onDraw()和onDrawOver两者其中之一就可以了.

getItemOffsets()方法,从字面意思就是Item要偏移, 由于我们在Item和Item之间加入了分隔线,线其实本质就是一个长方形,也是用户自定义的,既然线也有长宽高,就画横线来说,上面的Item加入了分隔线,那下面的Item就要往下平移,平移的量就是分隔线的高度。

先来比较两张图
Exler
Exler
可以看到第二张图片中的每个item分隔明显(ChildView,也就是平时用到的ListView,RecyclerView中的getChildAt(int position)这个返回的,这一部分指的是图二中红色的部分白色的部分属于ChildView的布局。)

一个View对应一个布局


Exler

可以明显的看出我们要画的就是黑色的这一条线。这条线是加在两个布局之间的。

所以我们可以知道我们画分隔线的位置,是在每一个Item的布局之间。


确定画线的具体的坐标位置

分隔线的left, top, right, Bottom.
在Adapter中,我们很容易通过parent(这个parent它其实就是我们能看到的部分)获取每一个childView:
1. left:parent.getPaddingLeft()
2. right: parent. getWidth()-parent.getPaddingRight();
3. top : 就是红线的上面:我们通过ChildView.getBottom()来得到这个Item的底部的高度,也就是蓝线位置,蓝线和红线之间间距:就是这个Item布局文件的:layout_marginBottom, 然后top的位置就是两者之和。
4. bttom: 就是top加上分隔线的高度:top+线高

Exler
可以看到,在这张图片中两个item之间有了一条系统默认(感觉是布局空出来的背景色)的分割线。
用自定义的样式来填充分隔线。在drawable文件夹下添加一个divider.xml文件。

<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">
    <solid android:color="#00ff00" />
    <size android:height="1dp" />
</shape>

定义一条绿线。
在values文件下的styles.xml文件中style标签之间添加

<item name="android:listDivider">@drawable/divider</item>

把自定义的样式引用进来。

MainActivity.java :

List<String> strings = null;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        initStr();

        RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recyclerview);
        LinearLayoutManager manager = new LinearLayoutManager(this);
        manager.setOrientation(LinearLayoutManager.VERTICAL);

        ItemAdapter adapter = new ItemAdapter(this,strings);

        recyclerView.setLayoutManager(manager);
        recyclerView.setAdapter(adapter);


        recyclerView.addItemDecoration(new MyDecoration(this, MyDecoration.VERTICAL_LIST));
            }

    public void initStr() {
        strings = new ArrayList<String>();
        for (int i = 0; i < 50; i++)
            strings.add("Item" + i);
    }

大部分代码与(1)类似 :
1. 初始化填入的数据
2. 定义一个LayoutManager,把垂直排列传进去
3. 定义item的适配器
4. 定义分隔线(告知是垂直的布局)

ItemAdapter.java

public class ItemAdapter extends RecyclerView.Adapter<ItemAdapter.ViewHolder> {

    private List<String> mList;
    private Context mContext;

    public ItemAdapter(Context context, List<String> list) {
        this.mContext = context;
        this.mList = list;
    }

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(mContext).inflate(R.layout.item_layout, parent, false);
        return new ViewHolder(view);
    }

    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {
        String str = mList.get(position);
        holder.textView.setText(str);
    }

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

    class ViewHolder extends RecyclerView.ViewHolder {

        TextView textView;

        public ViewHolder(View view) {
            super(view);
            textView = (TextView) view.findViewById(R.id.list_item);
        }
    }
}

子项适配器的代码。

Exler
可以看到item之间有一条淡淡的绿线。
效果不是特别好,可以选择重新绘制一下divider.xml文件

<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle" >

    <gradient
        android:centerColor="#ff00ff00"
        android:endColor="#ff0000ff"
        android:startColor="#ffff0000"
        android:type="linear" />
    <size android:height="4dp"/>
</shape>

Exler
这个效果就是比较好的了。

 manager.setOrientation(LinearLayoutManager.HORIZONTAL);


 recyclerView.addItemDecoration(new MyDecoration(this, MyDecoration.HORIZONTAL_LIST));

更改一下这两行代码
并且可以把item_layout.xml的layout_width属性改成固定值,如 :200dp
Exler
item的排列为水平时的竖直分隔线效果图。

public class MyDecoration extends RecyclerView.ItemDecoration {

    private Context mContext;
    private Drawable mDivider;
    private int mOrientation;
    public static final int HORIZONTAL_LIST = LinearLayoutManager.HORIZONTAL;
    public static final int VERTICAL_LIST = LinearLayoutManager.VERTICAL;

    public static final int[] ATRRS  = new int[]{ android.R.attr.listDivider};     //通过获取系统属性中的listDivider来添加,在AndroidManifest.xml中的AppTheme中设置

    public MyDecoration(Context context, int orientation) {
        this.mContext = context;
        final TypedArray ta = context.obtainStyledAttributes(ATRRS);    // TypedArray 用于存放 android 自定义控件 把属性集 和我们自己定义的属性集合建立映射关系
        this.mDivider = ta.getDrawable(0);
        ta.recycle();
        setOrientation(orientation);
    }

    public void setOrientation(int orientation) {    // 设置屏幕方向
        if (orientation != HORIZONTAL_LIST && orientation != VERTICAL_LIST) {
            throw new IllegalArgumentException("invalid orientation");    // 此异常表明向方法传递了一个不合法或不正确的参数
        }
        mOrientation = orientation;
    }

    @Override
    public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
        if (mOrientation == HORIZONTAL_LIST) {    // 水平 画竖线
            drawVerricalLine(c, parent, state);
        } else {    // 子项布局垂直排列时花在两个布局之间(上布局的底部)
            drawHorizontalLine(c, parent, state);
        }
    }

    public void drawHorizontalLine(Canvas c, RecyclerView parent, RecyclerView.State state) {    // 画横线 Android.graphics

        int left = parent.getPaddingLeft();    // 得到布局的左边距
        int right = parent.getWidth() - parent.getPaddingRight();    // 布局右边的终点 parent的宽度-(减去)布局的右边距(此程序得到的是一条占满屏幕宽度的线)
        int childCount = parent.getChildCount();
        for (int i = 0; i < childCount; i++) {
            View child = parent.getChildAt(i);

            RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();    // 获取child的布局信息 params 参数
            int top = child.getBottom() + params.bottomMargin;    // item View(Textview)的底部+布局中的(页下空白)
            int bottom = top +mDivider.getIntrinsicHeight();    // 高度 + Drawable的固有高度 dp为单位
            mDivider.setBounds(left, top, right, bottom);
            mDivider.draw(c);
        }
    }

    public void drawVerricalLine(Canvas c, RecyclerView parent, RecyclerView.State state) {    //画竖线
        int top = parent.getPaddingTop();    // 获取parent的上边距
        int bottom = parent.getHeight() - parent.getPaddingBottom();    //parent的高度-parent的下边距
        int childCount = parent.getChildCount();
        for (int i = 0; i < childCount; i++) {
            View child = parent.getChildAt(i);

            RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)child.getLayoutParams();    // 获得child的布局信息
            int left = child.getRight() + params.rightMargin ;    // 从第一个item(item0)的尾部开始加入分隔线  View的最右边 + 布局的页右空白
            int right = left + mDivider.getIntrinsicHeight() ;    // 两个布局之间的空隙 (此处应该与getItemOffsets()方法中的偏移数值相呼应)
            mDivider.setBounds(left, top, right, bottom);
            mDivider.draw(c);
        }
    }

    @Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
        if (mOrientation == HORIZONTAL_LIST) {    // 画竖线 往右偏移
            Log.d("admin",""+mDivider.getIntrinsicWidth()+" "+mDivider.getIntrinsicHeight());    // 为何输出Width为负数
            outRect.set(0, 0,mDivider.getIntrinsicHeight(), 0);    // mDivider.getIntrinsicHeight()
        } else {     画横线,往下偏移一个分割线的高度
            outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());
        }
    }
}

这就是添加分隔线的代码,具体的代码解释含在注释里了,大致上是
1. 定义一个drawable,
2. 判断屏幕方向,调用相应的画线方法
3. 设定item之间的偏移

item的增加删除

有时候item不是单一不变的,有时候会有添加和删除的需求。
先在布局中添加两个button

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        >
    <Button
        android:id="@+id/add_btn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Add"
        android:layout_margin="15dp"/>
    <Button
        android:id="@+id/delete_btn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Delete"
        android:layout_margin="15dp"/>
    </LinearLayout>
    <android.support.v7.widget.RecyclerView
        android:id="@+id/recyclerview"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
    </android.support.v7.widget.RecyclerView>
</LinearLayout>

在适配器代码中添加增加和移除功能 :

    public void addData(int position) {    // 添加item
        String str = "新增item";
        mList.add(position, str);
        notifyItemInserted(position);
        notifyDataSetChanged();
        notifyItemRangeChanged(position, mList.size());
    }

    public void removeData(int position) {    // 删除item
        mList.remove(position);

        notifyItemRemoved(position);
        notifyItemRangeChanged(position, mList.size());
    }

在MainActivity.java中 调用 适配器实例的增加和移除的方法 :

Button addData = (Button) findViewById(R.id.add_btn);
Button deleteData = (Button) findViewById(R.id.delete_btn);
RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recyclerview);
LinearLayoutManager manager = new LinearLayoutManager(this);
final ItemAdapter adapter = new ItemAdapter(this,strings);

manager.setOrientation(LinearLayoutManager.VERTICAL);
recyclerView.setLayoutManager(manager);
recyclerView.setAdapter(adapter);

recyclerView.setItemAnimator(new DefaultItemAnimator());    // 设置默认动画

addData.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        adapter.addData(0);
    }
});

deleteData.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        adapter.removeData(0);
    }
});

这边没有显示我们增加的分隔线。
运行点击Add按钮,可以看到有新增item加入 :
Exler

点击移除 :
Exler
移除的时候还会出现我们在配置RecyclerView时调用的setItemAnimator(new DefaultItemAnimator()); 使用的默认动画。


参考

http://www.jianshu.com/p/4eff036360da
http://blog.csdn.net/lmj623565791/article/details/45059587
http://blog.csdn.net/qq_21071977/article/details/78001509


遗留问题

1.

在LinearLayoutManager为垂直的时候,画竖分隔线

public void drawVerricalLine(Canvas c, RecyclerView parent, RecyclerView.State state) {    //画竖线
        int top = parent.getPaddingTop();    // 获取parent的上边距
        int bottom = parent.getHeight() - parent.getPaddingBottom();    //parent的高度-parent的下边距
        int childCount = parent.getChildCount();
        for (int i = 0; i < childCount; i++) {
            View child = parent.getChildAt(i);

            RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)child.getLayoutParams();    // 获得child的布局信息
            int left = child.getRight() + params.rightMargin ;    // 从第一个item(item0)的尾部开始加入分隔线  View的最右边 + 布局的页右空白
            int right = left + mDivider.getIntrinsicHeight() ;    // 两个布局之间的空隙 (此处为何用mDivider.getIntrinsicHeight())
            mDivider.setBounds(left, top, right, bottom);
            mDivider.draw(c);
        }
    }

获取了一下mDivider.getIntrinsicWidth()方法的数值为负。
Exler

为何此数值为负数

2.

添加item时,position = 0,自定义的List可以列表更新,但是界面上却没有显示出这个新增item(只是整个RecyclerView闪动了一下),删除的时候也能发现需要多删一下。就是不能在position=0 的位置显示新增item。(position = 1之后一切正常)

增加notifyDataSetChanged();可以在position=0处显示出新增item,但是不出现新增动画。

删除功能一切正常。


此博文为个人学习笔记,仅供个人学习使用,希望对大家有帮助。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值