Android学习随笔(8)------RecyclerView(2)
学习流程来自《第一行代码》(第二版)
在之前的RecyclerView(1)开头给出了对RecyclerView设置的几个模块。在书中只介绍了部分,这边来扩展一下。
GridLayoutManager
RecyclerView能实现的布局有很多,布局都由LayoutManager来控制,可拓展性十分高。
GridLayoutManager layoutManager = new GridLayoutManager(this, 2);
把RecyclerView的LayoutManager设置成GridLayout并传入上下文,和列数这两个参数即可。
我们仍就靠随机生成TextView的值,但是每个item的排列是网格类型的。
增加分隔线
可以明显的看出之前我们的RecyclerView是没有分隔线的。
addItemDecoration(ItemDecoration decoration) :方法就是用来设置分隔线的。
而ItemDecoration是一个抽象类,所以需要实现
- void getItemOffsets ()
- void onDraw ()
- void onDrawOver ()
这三个方法。可以看出分隔线是要自己画上去的。添加Divider,主要是找到添加Divider的位置, 而Divider是在drawable文件中写好了的。 利用onDraw和onDrawOver都差不多,所以创建自己的Decoration类继承RecyclerView.ItemDecoration的时候,只要重写getItemOffsets(),还有onDraw()和onDrawOver两者其中之一就可以了.
getItemOffsets()方法,从字面意思就是Item要偏移, 由于我们在Item和Item之间加入了分隔线,线其实本质就是一个长方形,也是用户自定义的,既然线也有长宽高,就画横线来说,上面的Item加入了分隔线,那下面的Item就要往下平移,平移的量就是分隔线的高度。
先来比较两张图
可以看到第二张图片中的每个item分隔明显(ChildView,也就是平时用到的ListView,RecyclerView中的getChildAt(int position)这个返回的,这一部分指的是图二中红色的部分,白色的部分属于ChildView的布局。)
一个View对应一个布局
可以明显的看出我们要画的就是黑色的这一条线。这条线是加在两个布局之间的。
所以我们可以知道我们画分隔线的位置,是在每一个Item的布局之间。
确定画线的具体的坐标位置
分隔线的left, top, right, Bottom.
在Adapter中,我们很容易通过parent(这个parent它其实就是我们能看到的部分)获取每一个childView:
- left:parent.getPaddingLeft()
- right: parent. getWidth()-parent.getPaddingRight();
- top : 就是红线的上面:我们通过ChildView.getBottom()来得到这个Item的底部的高度,也就是蓝线位置,蓝线和红线之间间距:就是这个Item布局文件的:layout_marginBottom, 然后top的位置就是两者之和。
- bttom: 就是top加上分隔线的高度:top+线高
可以看到,在这张图片中两个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)类似 :
- 初始化填入的数据
- 定义一个LayoutManager,把垂直排列传进去
- 定义item的适配器
- 定义分隔线(告知是垂直的布局)
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);
}
}
}
子项适配器的代码。
可以看到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>
这个效果就是比较好的了。
manager.setOrientation(LinearLayoutManager.HORIZONTAL);
recyclerView.addItemDecoration(new MyDecoration(this, MyDecoration.HORIZONTAL_LIST));
更改一下这两行代码
并且可以把item_layout.xml的layout_width属性改成固定值,如 :200dp
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());
}
}
}
这就是添加分隔线的代码,具体的代码解释含在注释里了,大致上是
- 定义一个drawable,
- 判断屏幕方向,调用相应的画线方法
- 设定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加入 :
点击移除 :
移除的时候还会出现我们在配置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()方法的数值为负。
为何此数值为负数
2.
添加item时,position = 0,自定义的List可以列表更新,但是界面上却没有显示出这个新增item(只是整个RecyclerView闪动了一下),删除的时候也能发现需要多删一下。就是不能在position=0 的位置显示新增item。(position = 1之后一切正常)
增加notifyDataSetChanged();可以在position=0处显示出新增item,但是不出现新增动画。
删除功能一切正常。
此博文为个人学习笔记,仅供个人学习使用,希望对大家有帮助。
还没有评论,来说两句吧...