Android RecyclerView初体验
很早之前就听说过RecyclerView这个组件了,但一直很忙没时间学习。趁着周末,就花了一天时间来学习RecyclerView。
准备工作
在Android Studio里新建一个Android项目,添加以下工具:
compile 'com.android.support:support-v4:25.2.0'
compile 'com.android.support:appcompat-v7:25.2.0'
compile 'com.android.support:recyclerview-v7:25.2.0'
compile 'com.github.bumptech.glide:glide:3.7.0'
前两个工具就不说了,基本每个Android项目都会用到,第三个就是使用RecyclerView需要添加的工具了,而最后一个工具是Google推荐使用的一个开源图像加载工具,它使得加载图像变得更为流畅,并且使用起来容易上手。
在drawable目录下放入一些图像,并将这些图像名命名为"image[a-zA-Z0-9]*"的形式(因为在程序里会用反射来查找所有以"image"开头的图像资源)。
在drawable目录下放入"add.png"和"remove.png"等用于顶部工具栏的图像资源。
基本用法
使用RecyclerView,可以依照如下步骤:
1. 在资源文件中定义一个RecyclerView组件;
2. 在Activity中获取定义的RecyclerView组件,变量名就简单叫作view;
3. 为view设置LayoutManager(布局);
4. 为view设置Adapter(适配器);
5. 为view添加ItemDecoration(分隔符,可选);
6. 为view设置ItemAnimator(动画,可选)。
下面在资源文件中定义了一个RecyclerView组件:
<android.support.v7.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:id="@+id/vert_view" />
然后就可以在Activity中取得这个RecyclerView组件:
RecyclerView view=(RecyclerView) findViewById(R.id.vert_view);
至于其他的细节将在下文介绍。
Adapter
用于RecyclerView的适配器需要继承自RecyclerView.Adapter<T>类,并重写其中的 T onCreateViewHolder(final ViewGroup parent, int viewType) 、 void onBindViewHolder(T holder, final int position) 和 int getItemCount() 方法。下面是一个自定义的Adapter:
public class ViewAdapter extends RecyclerView.Adapter<ViewAdapter.ViewHolder> {
private Context context;
private List<Animal> datas;
private int layoutResId;
private int imageViewId;
private int textViewId;
private boolean isCenterCrop; public ViewAdapter(Context c, List<Animal> list, int lri, int ivi, int tvi, boolean centerCrop) {
context=c;
datas=list;
layoutResId=lri;
imageViewId=ivi;
textViewId=tvi;
isCenterCrop=centerCrop;
} @Override
public ViewHolder onCreateViewHolder(final ViewGroup parent, int viewType) {
View view= LayoutInflater.from(context).inflate(layoutResId, parent, false);
return new ViewHolder(view, imageViewId, textViewId);
} @Override
public void onBindViewHolder(ViewHolder holder, final int position) {
final Animal animal=datas.get(position);
if(isCenterCrop)
Glide.with(context).load(animal.getImgResId()).centerCrop().into(holder.imageView);
else
Glide.with(context).load(animal.getImgResId()).into(holder.imageView);
holder.textView.setText(animal.getDescription());
holder.view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent=new Intent(context, DetailActivity.class);
intent.putExtra("IMG_RES_ID", animal.getImgResId());
intent.putExtra("DESCRIPTION", animal.getDescription());
context.startActivity(intent);
}
});
holder.view.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
Toast.makeText(context, "第"+position+"个元素", Toast.LENGTH_SHORT).show();
return true;
}
});
} @Override
public int getItemCount() {
return datas.size();
} public void addItem(int position) {
datas.add(position, ResourceUtils.getRandomAnimal());
notifyItemInserted(position);
} public void removeItem(int position) {
if(position>=0 && position<datas.size()) {
datas.remove(position);
notifyItemRemoved(position);
}
} static class ViewHolder extends RecyclerView.ViewHolder {
View view;
ImageView imageView;
TextView textView; public ViewHolder(View itemView, int ivi, int tvi) {
super(itemView);
view=itemView;
imageView=(ImageView) itemView.findViewById(ivi);
textView=(TextView) itemView.findViewById(tvi);
}
}
}
注意:
- 其中的Animal类封装了一张(动物)图像的资源ID和一段描述(字符串),Animal对象将作为我们的RecyclerView中的元素;
- onCreateViewHolder方法负责创建一个ViewHolder,因此我们需要提供一个元素使用的资源文件ID,我们需要使用到的view(这里包括一个ImageView和一个TextView)都可以通过这个ViewHolder得到;
- onBindViewHolder方法负责对某些view进行设置,这里我们为ImageView设置了图像,为TextView设置了文本,并为它们的父view注册了点击和长按监听器;
- 这里的addItem和removeItem方法是自定义方法,用于向/从RecyclerView中添加/移除一个元素(将在后面介绍),这里可以先忽略;
- 这里的ViewHolder是ViewAdapter.ViewHolder,它继承自RecyclerView.ViewHolder,我们将需要使用到的view交给它管理。
定义好Adapter后,就可以为RecyclerView设置适配器了:
List<Animal> datas= ResourceUtils.getAnimals();
adapter=new ViewAdapter(this, datas, R.layout.item_vertical, R.id.vert_image, R.id.vert_text, true);
view.setAdapter(adapter);
垂直布局
要想让RecyclerView使用和ListView一样的布局,只需要为RecyclerView设置一个线性布局管理器就行了:
view.setLayoutManager(new LinearLayoutManager(this));
水平布局
使用水平布局只需设置LinearLayoutManager的方向即可:
LinearLayoutManager layoutManager=new LinearLayoutManager(this);
layoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
view.setLayoutManager(layoutManager);
网格布局
使用网格布局需要借助GridLayoutManager类:
GridLayoutManager manager=new GridLayoutManager(this, 3);
// manager.setOrientation(GridLayoutManager.HORIZONTAL);
view.setLayoutManager(manager);
创建GridLayoutManager时,第二个参数使用列数/行数。GridLayoutManager默认是上下滑动的,如果想要左右滑动,可以取消第二行的注释。
瀑布流布局
使用瀑布流布局只需使用StaggeredGridLayoutManager即可:
view.setLayoutManager(new StaggeredGridLayoutManager(3, StaggeredGridLayoutManager.VERTICAL));
StaggeredGridLayoutManager的第一个参数指定列数/行数,第二个参数指定滑动方向,这里设置成上下滑动。如果使用StaggeredGridLayoutManager时所有元素的大小一致,则效果和网格布局一样。
添加分割线
RecyclerView默认是不显示分割线的,这点和ListView不同,但这也为更灵活的定制自己的分割线提供了机会。
要想定制自己的分割线,需要定义类继承自RecyclerView.ItemDecoration,并实现如下方法:
- void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) :此方法负责绘制分割线;
- void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) :此方法和onDraw方法一样,也是绘制分割线,不过绘制的时机是在绘制元素View完成后,通常只需要重写onDraw和此方法中的一个即可;
- void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) :此方法计算分割线的尺寸,以方便计算元素View的偏移。
这里以绘制垂直布局的分割线为例,介绍如何定义自己的分割线,至于其他几种布局的分割线可以参考着来写,文章最后也会提供所有源码:
public class VerticalItemDecoration extends RecyclerView.ItemDecoration {
private Drawable divider;
private static final int[] ATTRS=new int[] {
android.R.attr.listDivider,
}; public VerticalItemDecoration(Context c) {
TypedArray array=c.obtainStyledAttributes(ATTRS);
divider=array.getDrawable(0);
array.recycle();
} @Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDraw(c, parent, state);
DecorationUtils.onVerticalDraw(c, parent, divider);
} @Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
int position=parent.getChildLayoutPosition(view);
int childCount=parent.getAdapter().getItemCount();
if((position+1)<childCount)
outRect.set(0, 0, 0, divider.getIntrinsicHeight());
}
}
public class DecorationUtils {
public static void onVerticalDraw(Canvas c, RecyclerView parent, Drawable divider) {
int left=parent.getPaddingLeft();
int right=parent.getWidth()-parent.getPaddingRight(); for(int i=0; i<parent.getChildCount()-1; i++) {
View child=parent.getChildAt(i);
RecyclerView.LayoutParams params=(RecyclerView.LayoutParams) child.getLayoutParams();
int top=child.getBottom()+params.bottomMargin;
int bottom=top+divider.getIntrinsicHeight();
divider.setBounds(left, top, right, bottom);
divider.draw(c);
}
}
// ...
}
这里的分隔符样式使用了Android提供的“listDivider”,你可以重定义listDivider属性来修改分割线样式:
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
<item name="android:listDivider">@drawable/divider</item>
</style>
添加动画
你可能会好奇,RecyclerView为什么需要添加动画,不是就一个滑动吗?难道是添加滑动的动画?
考虑这种情况,当需要向/从RecyclerView中添加/移除元素时,如果一个元素突然插进来或者突然消失,用户会是什么感受(肯定觉得这APP是不是犯二了)。如果在添加/移除元素时,能有一个动画显示元素的出现/消失,那用户体验就更好了。所以,我们这里是为元素添加动画。
RecyclerView提供了一个默认动画,只需要简单的一行代码:
view.setItemAnimator(new DefaultItemAnimator());
好了,现在我们已经为元素的出现/消失添加了动画了。那么问题又来了,我们怎么添加/移除元素呢?
还记得我们上面介绍适配器时提供的ViewAdapter吗?当时让你暂时忽略了两个方法——addItem和removeItem,这两个方法就是用于添加/移除元素的。
我们现在来看下细节:
public void addItem(int position) {
datas.add(position, ResourceUtils.getRandomAnimal());
notifyItemInserted(position);
} public void removeItem(int position) {
if(position>=0 && position<datas.size()) {
datas.remove(position);
notifyItemRemoved(position);
}
}
注意到调用了notifyItemInserted和notifyItemRemoved方法,它们就是用于通知RecyclerView元素发生改变的。
现在已经有了添加/移除元素的接口,下面还需要在Activity中添加交互的动作。我们提供了一个Toolbar(用于代替ActionBar的一个组件),并提供了两个item。用户可以点击这两个item来添加/移除元素:
@Override
protected void onCreate(Bundle savedInstanceState) {
// ...
Toolbar toolbar=(Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
// ...
} @Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.toolbar, menu);
return true;
} @Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.add:
adapter.addItem(0);
view.smoothScrollToPosition(0);
break;
case R.id.remove:
adapter.removeItem(0);
break;
default:break;
}
return true;
}
}
好了,现在你就可以在添加/移除元素时看到动画效果了。
源代码
整个项目的源代码已经上传到GitHub:
https://github.com/jzyhywxz/RecyclerView
Android RecyclerView初体验的更多相关文章
- Android wear 初体验
近期一直在研究android wear SDK,整体感受来说就是和现有的android 其它的开发SDK还是有非常多新的东西.比如手机终端与手表端的通信机制,手表端的UI规范.可是从开发本身来讲,还是 ...
- Android JNI初体验
欢迎转载,转载请注明出处:http://www.cnblogs.com/lanrenxinxin/p/4696991.html 开始接触Android JNI层面的内容,推荐一本不错的入门级的书< ...
- RecyclerView 初体验
网上看了很多 RecyclerView 的教程,也结合学长的代码,终于实现了一个不错看的过去的List 可以通过左滑删除Item 长按Item或者点击按钮,可以对Item进行拖拽 更具体的内容会写在代 ...
- Android程序初体验
第一个程序的实现的最终功能是: 点击"正确"或者"错误"会得到一个是否正确的提示. 直接上效果图. 此次涉及代码编写的文件有4个: package co ...
- Android开发初体验
本文通过开发一个应用来学习Android基本概念及构成应用的UI组件. 开发的应用名叫GeoQuiz,它能给出一道道地理知识问题.用户点击true或false按钮回答问题,应用即时做出反馈 第一步请先 ...
- .net程序员的android studio 初体验 (环境设置2022年10月)
很久以前用DevExtreme写的一个Hybird APP要添加蓝牙打印功能,但是用来打包APP的phonegap被adobe关闭了,所以,只能自己用cordova去打包安卓APP,不得已,研究了 ...
- Android Studio 初体验
Google在I/O */
- Android开发学习之路--百度地图之初体验
手机都有gps和网络,通过gps或者网络可以定位到自己,然后通过百度,腾讯啊之类的地图可以显示我们的地理位置.这里学习下百度地图的使用.首先就是要申请开发者了,这个详细就不多讲了.http://dev ...
- (转载) Android RecyclerView 使用完全解析 体验艺术般的控件
Android RecyclerView 使用完全解析 体验艺术般的控件 标签: Recyclerviewpager瀑布流 2015-04-16 09:07 721474人阅读 评论(458) 收藏 ...
随机推荐
- 【LeetCode Weekly Contest 26 Q2】Longest Uncommon Subsequence II
[题目链接]:https://leetcode.com/contest/leetcode-weekly-contest-26/problems/longest-uncommon-subsequence ...
- Redis学习总结(1)——Redis内存数据库详细教程
1.redis是什么 2.redis的作者何许人也 3.谁在使用redis 4.学会安装redis 5.学会启动redis 6.使用redis客户端 7.redis数据结构 – 简介 8.redis数 ...
- ansible special topics
1.加速模式运行playbook accelerate 对于使用ansible 1.5 及之后版本的用户,加速模式只在以下情况下有用处: (A) 管理红帽企业版 Linux 6 或者更早的那些依然使用 ...
- kendo grid结合ajax功能
我感觉使用ajax结合表格绑定效率更好一些,可以灵活的控制点击前后的事件,现在grid前后的事件我不能控制
- Spring MVC-表单(Form)标签-错误处理(Error Handling)示例(转载实践)
以下内容翻译自:https://www.tutorialspoint.com/springmvc/springmvc_errors.htm 说明:示例基于Spring MVC 4.1.6. 以下示例显 ...
- Android Studio第一次启动的Fetching android sdk component information的问题
1)进入刚安装的Android Studio文件夹下的bin文件夹.找到idea.properties文件,用文本编辑器打开. 2)在idea.properties文件末尾加入一行: disable. ...
- umask函数的使用方法 - 怎样进行权限位的设置
以下程序创建了两个文件,创建foo文件时,umask值为0,创建第二个时,umask值禁止全部组和其它用户的訪问权限. 測试结果: 測试结果能够看出更改进程的文件模式掩码并不影响其父进程(经常是she ...
- BEGINNING SHAREPOINT® 2013 DEVELOPMENT 第9章节--client对象模型和REST APIs概览 介绍SP2013中远程APIs
BEGINNING SHAREPOINT® 2013 DEVELOPMENT 第9章节--client对象模型和REST APIs概览 介绍SP2013中远程APIs 当SP首次開始 ...
- Android studio第一次使用配置(三)gradle项目构建
1.gradle的概念 2.gradle配置jar包.和libs目录导入jar包的差别 3.签名打包: (1)Studio (2)命令行 (3)gradle wrapper的原理 4.BuildCon ...
- [Java]LeetCode57 Insert Interval
Given a set of non-overlapping intervals, insert a new interval into the intervals (merge if necessa ...