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

偏执的太偏执、 2022-06-06 06:42 316阅读 0赞

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

GridLayoutManager

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

  1. 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文件。

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

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

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

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

MainActivity.java :

  1. List<String> strings = null;
  2. @Override
  3. protected void onCreate(Bundle savedInstanceState) {
  4. super.onCreate(savedInstanceState);
  5. setContentView(R.layout.activity_main);
  6. initStr();
  7. RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recyclerview);
  8. LinearLayoutManager manager = new LinearLayoutManager(this);
  9. manager.setOrientation(LinearLayoutManager.VERTICAL);
  10. ItemAdapter adapter = new ItemAdapter(this,strings);
  11. recyclerView.setLayoutManager(manager);
  12. recyclerView.setAdapter(adapter);
  13. recyclerView.addItemDecoration(new MyDecoration(this, MyDecoration.VERTICAL_LIST));
  14. }
  15. public void initStr() {
  16. strings = new ArrayList<String>();
  17. for (int i = 0; i < 50; i++)
  18. strings.add("Item" + i);
  19. }

大部分代码与(1)类似 :

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

ItemAdapter.java

  1. public class ItemAdapter extends RecyclerView.Adapter<ItemAdapter.ViewHolder> {
  2. private List<String> mList;
  3. private Context mContext;
  4. public ItemAdapter(Context context, List<String> list) {
  5. this.mContext = context;
  6. this.mList = list;
  7. }
  8. @Override
  9. public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
  10. View view = LayoutInflater.from(mContext).inflate(R.layout.item_layout, parent, false);
  11. return new ViewHolder(view);
  12. }
  13. @Override
  14. public void onBindViewHolder(ViewHolder holder, int position) {
  15. String str = mList.get(position);
  16. holder.textView.setText(str);
  17. }
  18. @Override
  19. public int getItemCount() {
  20. return mList.size();
  21. }
  22. class ViewHolder extends RecyclerView.ViewHolder {
  23. TextView textView;
  24. public ViewHolder(View view) {
  25. super(view);
  26. textView = (TextView) view.findViewById(R.id.list_item);
  27. }
  28. }
  29. }

子项适配器的代码。

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

  1. <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle" >
  2. <gradient android:centerColor="#ff00ff00" android:endColor="#ff0000ff" android:startColor="#ffff0000" android:type="linear" />
  3. <size android:height="4dp"/>
  4. </shape>

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

  1. manager.setOrientation(LinearLayoutManager.HORIZONTAL);
  2. recyclerView.addItemDecoration(new MyDecoration(this, MyDecoration.HORIZONTAL_LIST));

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

  1. public class MyDecoration extends RecyclerView.ItemDecoration {
  2. private Context mContext;
  3. private Drawable mDivider;
  4. private int mOrientation;
  5. public static final int HORIZONTAL_LIST = LinearLayoutManager.HORIZONTAL;
  6. public static final int VERTICAL_LIST = LinearLayoutManager.VERTICAL;
  7. public static final int[] ATRRS = new int[]{ android.R.attr.listDivider}; //通过获取系统属性中的listDivider来添加,在AndroidManifest.xml中的AppTheme中设置
  8. public MyDecoration(Context context, int orientation) {
  9. this.mContext = context;
  10. final TypedArray ta = context.obtainStyledAttributes(ATRRS); // TypedArray 用于存放 android 自定义控件 把属性集 和我们自己定义的属性集合建立映射关系
  11. this.mDivider = ta.getDrawable(0);
  12. ta.recycle();
  13. setOrientation(orientation);
  14. }
  15. public void setOrientation(int orientation) { // 设置屏幕方向
  16. if (orientation != HORIZONTAL_LIST && orientation != VERTICAL_LIST) {
  17. throw new IllegalArgumentException("invalid orientation"); // 此异常表明向方法传递了一个不合法或不正确的参数
  18. }
  19. mOrientation = orientation;
  20. }
  21. @Override
  22. public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
  23. if (mOrientation == HORIZONTAL_LIST) { // 水平 画竖线
  24. drawVerricalLine(c, parent, state);
  25. } else { // 子项布局垂直排列时花在两个布局之间(上布局的底部)
  26. drawHorizontalLine(c, parent, state);
  27. }
  28. }
  29. public void drawHorizontalLine(Canvas c, RecyclerView parent, RecyclerView.State state) { // 画横线 Android.graphics
  30. int left = parent.getPaddingLeft(); // 得到布局的左边距
  31. int right = parent.getWidth() - parent.getPaddingRight(); // 布局右边的终点 parent的宽度-(减去)布局的右边距(此程序得到的是一条占满屏幕宽度的线)
  32. int childCount = parent.getChildCount();
  33. for (int i = 0; i < childCount; i++) {
  34. View child = parent.getChildAt(i);
  35. RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams(); // 获取child的布局信息 params 参数
  36. int top = child.getBottom() + params.bottomMargin; // item View(Textview)的底部+布局中的(页下空白)
  37. int bottom = top +mDivider.getIntrinsicHeight(); // 高度 + Drawable的固有高度 dp为单位
  38. mDivider.setBounds(left, top, right, bottom);
  39. mDivider.draw(c);
  40. }
  41. }
  42. public void drawVerricalLine(Canvas c, RecyclerView parent, RecyclerView.State state) { //画竖线
  43. int top = parent.getPaddingTop(); // 获取parent的上边距
  44. int bottom = parent.getHeight() - parent.getPaddingBottom(); //parent的高度-parent的下边距
  45. int childCount = parent.getChildCount();
  46. for (int i = 0; i < childCount; i++) {
  47. View child = parent.getChildAt(i);
  48. RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)child.getLayoutParams(); // 获得child的布局信息
  49. int left = child.getRight() + params.rightMargin ; // 从第一个item(item0)的尾部开始加入分隔线 View的最右边 + 布局的页右空白
  50. int right = left + mDivider.getIntrinsicHeight() ; // 两个布局之间的空隙 (此处应该与getItemOffsets()方法中的偏移数值相呼应)
  51. mDivider.setBounds(left, top, right, bottom);
  52. mDivider.draw(c);
  53. }
  54. }
  55. @Override
  56. public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
  57. if (mOrientation == HORIZONTAL_LIST) { // 画竖线 往右偏移
  58. Log.d("admin",""+mDivider.getIntrinsicWidth()+" "+mDivider.getIntrinsicHeight()); // 为何输出Width为负数
  59. outRect.set(0, 0,mDivider.getIntrinsicHeight(), 0); // mDivider.getIntrinsicHeight()
  60. } else { 画横线,往下偏移一个分割线的高度
  61. outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());
  62. }
  63. }
  64. }

这就是添加分隔线的代码,具体的代码解释含在注释里了,大致上是

  1. 定义一个drawable,
  2. 判断屏幕方向,调用相应的画线方法
  3. 设定item之间的偏移

item的增加删除

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

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

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

  1. public void addData(int position) { // 添加item
  2. String str = "新增item";
  3. mList.add(position, str);
  4. notifyItemInserted(position);
  5. notifyDataSetChanged();
  6. notifyItemRangeChanged(position, mList.size());
  7. }
  8. public void removeData(int position) { // 删除item
  9. mList.remove(position);
  10. notifyItemRemoved(position);
  11. notifyItemRangeChanged(position, mList.size());
  12. }

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

  1. Button addData = (Button) findViewById(R.id.add_btn);
  2. Button deleteData = (Button) findViewById(R.id.delete_btn);
  3. RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recyclerview);
  4. LinearLayoutManager manager = new LinearLayoutManager(this);
  5. final ItemAdapter adapter = new ItemAdapter(this,strings);
  6. manager.setOrientation(LinearLayoutManager.VERTICAL);
  7. recyclerView.setLayoutManager(manager);
  8. recyclerView.setAdapter(adapter);
  9. recyclerView.setItemAnimator(new DefaultItemAnimator()); // 设置默认动画
  10. addData.setOnClickListener(new View.OnClickListener() {
  11. @Override
  12. public void onClick(View view) {
  13. adapter.addData(0);
  14. }
  15. });
  16. deleteData.setOnClickListener(new View.OnClickListener() {
  17. @Override
  18. public void onClick(View view) {
  19. adapter.removeData(0);
  20. }
  21. });

这边没有显示我们增加的分隔线。
运行点击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为垂直的时候,画竖分隔线

  1. public void drawVerricalLine(Canvas c, RecyclerView parent, RecyclerView.State state) { //画竖线
  2. int top = parent.getPaddingTop(); // 获取parent的上边距
  3. int bottom = parent.getHeight() - parent.getPaddingBottom(); //parent的高度-parent的下边距
  4. int childCount = parent.getChildCount();
  5. for (int i = 0; i < childCount; i++) {
  6. View child = parent.getChildAt(i);
  7. RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)child.getLayoutParams(); // 获得child的布局信息
  8. int left = child.getRight() + params.rightMargin ; // 从第一个item(item0)的尾部开始加入分隔线 View的最右边 + 布局的页右空白
  9. int right = left + mDivider.getIntrinsicHeight() ; // 两个布局之间的空隙 (此处为何用mDivider.getIntrinsicHeight())
  10. mDivider.setBounds(left, top, right, bottom);
  11. mDivider.draw(c);
  12. }
  13. }

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

为何此数值为负数

2.

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

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

删除功能一切正常。


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

发表评论

表情:
评论列表 (有 0 条评论,316人围观)

还没有评论,来说两句吧...

相关阅读

    相关 Android学习随笔(1)

    学习流程来自《第一行代码》(第二版) 最近开始了Android的学习,看到很多人都推荐这一本书,就决定按照这一本书的讲解流程熟悉一下Android。 环境配置 ![

    相关 Android RecyclerView

    该文章是一个系列文章,是本人在Android开发的漫漫长途上的一点感想和记录,我会尽量按照先易后难的顺序进行编写该系列。该系列引用了《Android开发艺术探索》以及《深入理解