为什么有了ListView还需要RecyclerView?
主要有这几个原因:
RecyclerView相比于ListView的优缺点:
RecyclerView的一般使用和ListView在总体上差不多。区别主要有以下几点:
RecyclerView rv = findViewById(R.id.rv_second);
// 设置布局管理器,这里是最简单的竖直线性排列的布局
rv.setLayoutManager(new LinearLayoutManager(this));
// 设置Adapter
rv.setAdapter(adapter);
// 设置分割线
rv.addItemDecoration(new DividerItemDecoration(this, DividerItemDecoration.VERTICAL));
Adatper需要继承RecyclerView.Adapter类,且需要额外增加一个ViewHolder类继承RecyclerView.ViewHolder。然后将自定义的Holder作为Adapter的泛型类型。关于Holder的处理,放到后面再说
public class FruitAdapter extends RecyclerView.Adapter<FruitAdapter.Holder> {
static class Holder extends RecyclerView.ViewHolder {
}
}
Adapter需要复写父类的三个抽象方法,分别如下。
// 直接返回数量,固定式写法
@Override
public int getItemCount() {
return data == null ? 0 : data.size();
}
// 创建ItemView,将ItemView和Holder绑定,当然也要绑定itemView中的控件
onCreateViewHolder()
// 在这里处理数据,将position对应的JavaBean对象中的数据设置进holder.xx控件中
onBindViewHolder()
其实写法和ListView 的Adapter在优化之后的写法是一样的。只是将ListView.Adapter中的getView()方法中的代码分开放到onCreateViewHolder(),Holder类,和onBindViewHolder()方法三部分中去。
ListView.Adapter的getView方法和RecyclerView.Adapter的onCreateViewHolder、onBindViewHolder方法的比较:
ListView的Adapter
@Override
public View getView(int position, View convertView, ViewGroup parent) {
// 对convertView和view中的控件的复用
ViewHolder holder;
if (convertView == null) {
convertView = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_fruit_first, parent, false);
holder = new ViewHolder();
holder.iv = convertView.findViewById(R.id.iv_fruit);
holder.tv = convertView.findViewById(R.id.tv_fruit);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
// 下面是将数据设置进具体的convertView的控件中
Fruit fruit = data.get(position);
holder.iv.setImageResource(fruit.getDrawableResId());
holder.tv.setText(fruit.getName());
return convertView;
}
RecyclerView的Adapter
/**
* 创建ItemView,将ItemView和Holder绑定,当然也要绑定itemView中的控件
*
* @param parent 就是RecyclerView对象本身
* @param viewType 如果有多种布局,根据这个viewType的值不同,要加载不同的布局
* @return 在onBindViewHolder方法中使用的Holder对象
*/
@NonNull
@Override
public Holder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_fruit_first, parent, false);
// 这里将view和holder的绑定方式与ListViewAdapter不同,不再是使用Tag的方式了,而是将view作为holder的成员变量
Holder holder = new Holder(view);
holder.iv = view.findViewById(R.id.iv_fruit);
holder.tv = view.findViewById(R.id.tv_fruit);
return holder;
}
/**
* 在这里处理数据,将position对应的JavaBean对象中的数据设置进holder.xx控件中
*
* @param holder 就是或新建,或复用的Holder对象
* @param position item对应的索引
*/
@Override
public void onBindViewHolder(@NonNull Holder holder, int position) {
Fruit fruit = data.get(position);
holder.iv.setImageResource(fruit.getDrawableResId());
holder.tv.setText(fruit.getName());
}
private ArrayList<Fruit> data;
public FruitAdapter(ArrayList<Fruit> data) {
this.data = data;
}
与ListView中的Holder不同,RecyclerViewAdapter中的Holder需要继承RecyclerView.ViewHolder,且因为RecyclerView.ViewHolder只有一个要itemView作为参数的构造方法,所以我们自定义的ViewHolder也要必须添加构造方法。
/**
* 因为父类只有一个需要view参数的构造方法,所以Holder类必须添加一个构造方法,能够调用父类的这个构造方法
*/
static class Holder extends RecyclerView.ViewHolder {
ImageView iv;
TextView tv;
/**
* 构造方法,将itemView与holder对象绑定,并调用父类的有itemView作为参数的构造方法
*
* @param itemView 就是Adapter的onCreateViewHolder方法创建的View
*/
public Holder(@NonNull View itemView) {
super(itemView);
}
}
/**
* 1. 一般情况下我们同时需要自定义Holder类继承Rv中的ViewHolder,然后将Holder类设置为Adapter中的泛型
* 2. 继承Rv.Adapter类之后,需要复写3个抽象方法
*/
public class FruitAdapter extends RecyclerView.Adapter<FruitAdapter.Holder> {
private ArrayList<Fruit> data;
public FruitAdapter(ArrayList<Fruit> data) {
this.data = data;
}
/**
* 和ListView一样,也是获取列表需要渲染加载的数据的数量
*/
@Override
public int getItemCount() {
return data == null ? 0 : data.size();
}
/**
* 创建ItemView,将ItemView和Holder绑定,当然也要绑定itemView中的控件
*
* @param parent 就是RecyclerView对象本身
* @param viewType 如果有多种布局,根据这个viewType的值不同,要加载不同的布局
* @return 在onBindViewHolder方法中使用的Holder对象
*/
@NonNull
@Override
public Holder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_fruit_first, parent, false);
Holder holder = new Holder(view);
holder.iv = view.findViewById(R.id.iv_fruit_first);
holder.tv = view.findViewById(R.id.tv_fruit_first);
return holder;
}
/**
* 在这里处理数据,将position对应的JavaBean对象中的数据设置进holder.xx控件中
*
* @param holder 就是或新建,或复用的Holder对象
* @param position item对应的索引
*/
@Override
public void onBindViewHolder(@NonNull Holder holder, int position) {
Fruit fruit = data.get(position);
holder.iv.setImageResource(fruit.getDrawableResId());
holder.tv.setText(fruit.getName());
}
/**
* 因为父类只有一个需要view参数的构造方法,所以Holder类必须添加一个构造方法,能够调用父类的这个构造方法
*/
static class Holder extends RecyclerView.ViewHolder {
ImageView iv;
TextView tv;
/**
* 构造方法,将itemView与holder对象绑定,并调用父类的有itemView作为参数的构造方法
*
* @param itemView 就是Adapter的onCreateViewHolder方法创建的View
*/
public Holder(@NonNull View itemView) {
super(itemView);
}
}
}
常用的布局管理器有2种,分别是线性和网格。可以达到普通的列表、横向列表、网格状、瀑布流布局的效果。
最常用的就是LinearLayoutManager。
下面就是创建一个最普通的类似ListView的布局管理器。
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
rv.setLayoutManager(linearLayoutManager);
LinearLayoutManager还有一个常用的构造方法。
/**
* @param context Current context, will be used to access resources.
* @param orientation Layout orientation. Should be {@link #HORIZONTAL} or {@link
* #VERTICAL}.
* @param reverseLayout When set to true, layouts from end to start.
*/
public LinearLayoutManager(Context context, @RecyclerView.Orientation int orientation,
boolean reverseLayout) {
setOrientation(orientation);
setReverseLayout(reverseLayout);
}
因此我们如果想要显示水平方向列表,直接使用这个构造方法即可。下面的代码,再将itemView的宽度不设置为match_parent,就可以实现水平方向的排列。当然就算itemView的宽度是match_parent,也是水平排列的列表,但是每个item的宽度就会都占用屏幕的宽度了。
LinearLayoutManager linearLayoutManager =
new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false);
rv.setLayoutManager(linearLayoutManager);
如果想要网格状的布局,就可以使用StaggeredGridLayoutManager来完成。
和LinearLayoutManager不同,我们常用的StaggeredGridLayoutManager构造方法是不传Context,而必须指定方向和行列数的构造方法。
/**
* Creates a StaggeredGridLayoutManager with given parameters.
*
* @param spanCount If orientation is vertical, spanCount is number of columns. If
* orientation is horizontal, spanCount is number of rows.
* @param orientation {@link #VERTICAL} or {@link #HORIZONTAL}
*/
public StaggeredGridLayoutManager(int spanCount, int orientation) {
mOrientation = orientation;
setSpanCount(spanCount);
mLayoutState = new LayoutState();
createOrientationHelpers();
}
StaggeredGridLayoutManager的使用如下,不过要注意itemView的宽高的设置。
// 创建一个竖直方向排列,一共只有2列的网格状布局管理器
StaggeredGridLayoutManager staggeredGridLayoutManager
= new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL);
rv.setLayoutManager(staggeredGridLayoutManager);
// 创建一个水平方向排列,一共只有2行的网格状布局管理器
StaggeredGridLayoutManager staggeredGridLayoutManager
= new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.HORIZONTAL);
rv.setLayoutManager(staggeredGridLayoutManager);
瀑布流就是控件的宽度或者高度不等的网格型布局。
瀑布流的实现很简单,就是在StaggeredGridLayoutManager的基础之上,更改ItemView的高度或者宽度即可。只是这里注意,要使用控件的LayoutParams来修改宽高等尺寸属性。
/**
* 将数据设置进itemView中的控件,也就是ViewHolder中的成员变量。
*/
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
// 不能通过view直接设置它的宽高,需要通过一个LayoutParams的成员变量来修改宽高。
ViewGroup.LayoutParams layoutParams = holder.itemView.getLayoutParams();
// 想办法让itemView的高度在某个范围内变化
layoutParams.height = DensityUtil.dip2px(holder.itemView.getContext(), 60) +
DensityUtil.dip2px(holder.itemView.getContext(), new Random().nextInt(60));
// 不要忘记设置数据
Fruit fruit = data.get(position);
holder.iv.setImageResource(fruit.getDrawableRes());
holder.tv.setText(fruit.getName());
}
dp转px的工具方法也很简单。获取系统的屏幕像素密度,乘以要dp数值,就是像素值。
public class DensityUtil {
/**
* 根据手机的分辨率从 dp 的单位 转成为 px(像素)
*/
public static int dip2px(Context context, float dpValue) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (dpValue * scale + 0.5f);
}
/**
* 根据手机的分辨率从 px(像素) 的单位 转成为 dp
*/
public static int px2dip(Context context, float pxValue) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (pxValue / scale + 0.5f);
}
}
一般情况下,我们在RecyclerView中添加分割线的方式是将线画在itemView中,然后根据条件决定线是否显示。
如果想要itemView之间有间距,我们一般也是用在itemView中添加margin的方式完成。
但是如果有比较复杂的的对于itemView分割线、背景样式等的处理的时候,我们就需要使用ItemDecoration来完成了。
案例完整代码:
public class FruitDecoration extends RecyclerView.ItemDecoration {
private final Paint mPaint;
public FruitDecoration(Context context) {
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setColor(context.getResources().getColor(R.color.purple_200));
}
/**
* 可以实现类似于Padding的效果。就是控制各个Item之间的间距等。
*
* @param outRect 就是ItemView的四周的边距。就是系统会根据outRect的值来扩展item的区域。
* @param view 当前ItemView
* @param parent 就是RecyclerView
* @param state 存储一些RecyclerView的状态等,用的不多
*/
@Override
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
/**
* 这里有两个获取position的方法。他们一般情况下没有区别。
* 仅在RecyclerView刷新布局后,layoutPosition会晚adapterPosition大约16ms。
* 因为绘制完成之后,layoutPosition才有正确的值,所以我们一般用adapterPosition就好。
*
* 只有在使用findViewHolderForLayoutPosition获取当前item的ViewHolder时,用layoutPosition才更好,因为此时layoutPosition 和用户在屏幕上看到的一定是一样的
*/
int position = parent.getChildAdapterPosition(view);
int layoutPosition = parent.getChildLayoutPosition(view);
/**
* 这里注意,下面两个count的值的不同。
* viewCount是当前RecyclerView中的itemView的数量。并不是数据的数量,Adapter中的getItemCount才是
* 因为RecyclerView不会一次性创建所以的itemView,而是会进行view的复用。
*/
int viewCount = parent.getChildCount();
int childCount = parent.getAdapter().getItemCount();
if (position == 0) {
outRect.bottom = 20;
} else if (position == childCount - 1) {
outRect.top = 20;
} else {
outRect.top = 20;
outRect.bottom = 20;
}
}
/**
* 绘制ItemView的背景。意思就是这里画出来的图像,会显示在itemView的下面。这个方法会在绘制itemView之前调用.
* 这里的绘制区域是根据上面的getItemOffsets决定的。
* 注意这里的canvas指的是RecyclerView的布局部分,而不是itemView的界面。如果想在每一个ItemView的相同位置画图案,需要计算对应的坐标。
*
* @param c 绘画的布
* @param parent 就是RecyclerView对象本身
* @param state state本身
*/
@Override
public void onDraw(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
super.onDraw(c, parent, state);
}
/**
* 绘制ItemView的前景。意思就是这里画出来的图像,会显示在itemView的上面
*
* @param c 绘画的布
* @param parent 就是RecyclerView对象本身
* @param state state本身
*/
@Override
public void onDrawOver(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
super.onDrawOver(c, parent, state);
int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
View child = parent.getChildAt(i);
int radius = child.getHeight() / 2;
int centerX = child.getRight() - 20 - radius;
int centerY = child.getTop() + child.getHeight() / 2;
c.drawCircle(centerX, centerY, radius, mPaint);
}
}
}
主要的实现方式还是在Adapter的onBindViewHolder中设置点击事件。
对应的Layout布局文件
getItemViewType
onCreateViewHolder
Holder类
onBindViewHolder
BRVAH(BaseRecyclerViewAdapterHelper)(RecyclerView使用框架):http://www.recyclerview.org/
SmartRefrshLayout(下拉刷新框架):https://gitee.com/scwang90/SmartRefreshLayout
Android RecyclerView 使用完全解析 体验艺术般的控件
Android 优雅的为RecyclerView添加HeaderView和FooterView
我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div
我有一个Ruby程序,它使用rubyzip压缩XML文件的目录树。gem。我的问题是文件开始变得很重,我想提高压缩级别,因为压缩时间不是问题。我在rubyzipdocumentation中找不到一种为创建的ZIP文件指定压缩级别的方法。有人知道如何更改此设置吗?是否有另一个允许指定压缩级别的Ruby库? 最佳答案 这是我通过查看rubyzip内部创建的代码。level=Zlib::BEST_COMPRESSIONZip::ZipOutputStream.open(zip_file)do|zip|Dir.glob("**/*")d
类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc
很好奇,就使用rubyonrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提
假设我做了一个模块如下:m=Module.newdoclassCendend三个问题:除了对m的引用之外,还有什么方法可以访问C和m中的其他内容?我可以在创建匿名模块后为其命名吗(就像我输入“module...”一样)?如何在使用完匿名模块后将其删除,使其定义的常量不再存在? 最佳答案 三个答案:是的,使用ObjectSpace.此代码使c引用你的类(class)C不引用m:c=nilObjectSpace.each_object{|obj|c=objif(Class===objandobj.name=~/::C$/)}当然这取决于
我正在尝试使用ruby和Savon来使用网络服务。测试服务为http://www.webservicex.net/WS/WSDetails.aspx?WSID=9&CATID=2require'rubygems'require'savon'client=Savon::Client.new"http://www.webservicex.net/stockquote.asmx?WSDL"client.get_quotedo|soap|soap.body={:symbol=>"AAPL"}end返回SOAP异常。检查soap信封,在我看来soap请求没有正确的命名空间。任何人都可以建议我
关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。
我在我的项目目录中完成了compasscreate.和compassinitrails。几个问题:我已将我的.sass文件放在public/stylesheets中。这是放置它们的正确位置吗?当我运行compasswatch时,它不会自动编译这些.sass文件。我必须手动指定文件:compasswatchpublic/stylesheets/myfile.sass等。如何让它自动运行?文件ie.css、print.css和screen.css已放在stylesheets/compiled。如何在编译后不让它们重新出现的情况下删除它们?我自己编译的.sass文件编译成compiled/t
我想将html转换为纯文本。不过,我不想只删除标签,我想智能地保留尽可能多的格式。为插入换行符标签,检测段落并格式化它们等。输入非常简单,通常是格式良好的html(不是整个文档,只是一堆内容,通常没有anchor或图像)。我可以将几个正则表达式放在一起,让我达到80%,但我认为可能有一些现有的解决方案更智能。 最佳答案 首先,不要尝试为此使用正则表达式。很有可能你会想出一个脆弱/脆弱的解决方案,它会随着HTML的变化而崩溃,或者很难管理和维护。您可以使用Nokogiri快速解析HTML并提取文本:require'nokogiri'h
我想为Heroku构建一个Rails3应用程序。他们使用Postgres作为他们的数据库,所以我通过MacPorts安装了postgres9.0。现在我需要一个postgresgem并且共识是出于性能原因你想要pggem。但是我对我得到的错误感到非常困惑当我尝试在rvm下通过geminstall安装pg时。我已经非常明确地指定了所有postgres目录的位置可以找到但仍然无法完成安装:$envARCHFLAGS='-archx86_64'geminstallpg--\--with-pg-config=/opt/local/var/db/postgresql90/defaultdb/po