引言
RecyclerView是Google建议使用用于替换GridView,ListView等控件的新控件,这里主要记录在替换过程中所遇到的一些问题, 以及它们的解决办法。
如何定义RecyclerView
RecyclerView相比于GridView等控件,比较大的区别有:
1、默认使用ViewHolder进行View的重用。
2、使用LayoutManager进行布局的控制,更加灵活。
在使用时,需要分别定义并设置:
RecyclerView recyclerView = findViewById(R.id.content_grid_view);
mRecyclerViewAdapter = new RecyclerViewAdapter(MainPage.this);
recyclerView.setAdapter(mRecyclerViewAdapter);
int column = getResources().getInteger(R.integer.grid_columns);
final StaggeredGridLayoutManager layoutManager =
new StaggeredGridLayoutManager(column, StaggeredGridLayoutManager.VERTICAL);
recyclerView.setLayoutManager(layoutManager);
其中StaggeredGridLayoutManager是原生提供的瀑布流布局,只需要指定列数及方向即可。
注意到这里绑定了我们自定义的RecyclerViewAdapter,其实现如下:
class RecyclerViewAdapter extends RecyclerView.Adapter<RecyclerViewAdapter.RecyclerViewHolder> {
private Context mAdapterContext;
private RecyclerViewAdapter(Context context) {
mAdapterContext = context;
}
@Override
public @NonNull RecyclerViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(mAdapterContext).inflate(R.layout.grid_item, parent, false);
return new RecyclerViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull RecyclerViewHolder viewHolder, int position) {
HashMap<String, String> map = mList.get(position);
if (map == null) {
Log.d(TAG, "map is null");
return;
}
String imageData = map.get(IMAGE_KEY);
String textData = map.get(TEXT_KEY);
Util.setPicFromUrl(MainPage.this, imageData, viewHolder.imageView);
viewHolder.textView.setText(textData);
setOnClickListener(viewHolder, position);
setMenuListener(viewHolder, position);
}
......
@Override
public int getItemCount() {
return mList.size();
}
class RecyclerViewHolder extends RecyclerView.ViewHolder {
private View itemView;
private ImageView imageView;
private TextView textView;
RecyclerViewHolder(View view) {
super(view);
itemView = view;
imageView = view.findViewById(R.id.image_item);
textView = view.findViewById(R.id.text_item);
}
}
}
可以看到对应的ViewHolder的定义,及其在onBindViewHolder方法中是如何使用的。
使用Glide
在对GridView进行替换时,也将Glide从3.x升级到了4.x版本。使用方法有些许变化:
1、默认的loading图片从直接设置改为通过option传递
RequestOptions options = new RequestOptions().placeholder(R.drawable.ic_loading);
Glide.with(context).load(url).apply(options).into(imageView);
2、下载图片时参数类型发生了变化
Glide.with(viewPage).asBitmap().load(url).into(new SimpleTarget<Bitmap>() {
@Override
public void onResourceReady(@NonNull Bitmap bitmap, Transition<? super Bitmap> transition) {
......
}
});
滑动刷新数据
在GridView或者ListView时,如果要实现滑动到底部加载更多数据,一般会通过对View榜定滑动方法实现,比如:
mListView.setOnScrollListener(new AbsListView.OnScrollListener() {
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
if (view.getLastVisiblePosition() == view.getCount() - 1) {
updateImage();
}
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
}
});
但是在修改时发现存在问题,RecyclerView在使用瀑布流的情况下,并没有所谓的最后一个对象,取而代之的是LayoutManager中,获取最后一组对象的方法:
final StaggeredGridLayoutManager layoutManager =
new StaggeredGridLayoutManager(column, StaggeredGridLayoutManager.VERTICAL);
recyclerView.setLayoutManager(layoutManager);
layoutManager.findLastVisibleItemPositions(null);
可见过去的判断方法不能继续使用,我们可以考虑对滑动行为进行判断:
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
if (!recyclerView.canScrollVertically(-1)) {
mWebOperation.updatePage();
}
}
});
这里-1表示向下滑动(1表示向上滑动),从而触发滑动到底部时的行为。
增加click事件
Google没有为RecyclerView提供默认的click事件,这可能是由于RecyclerView将Layout交给用户自行控制,此时View是可以重叠的,没办法给出默认的行为?
因此,我们没有办法直接在RecyclerView整体绑定click事件,经过查阅资料,此时比较好的方法是在onBindViewHolder时,对于单个的View进行事件的处理:
class RecyclerViewAdapter extends RecyclerView.Adapter<RecyclerViewAdapter.RecyclerViewHolder> {
......
@Override
public void onBindViewHolder(@NonNull RecyclerViewHolder viewHolder, int position) {
......
setOnClickListener(viewHolder, position);
......
}
private void setOnClickListener(RecyclerViewHolder viewHolder, final int pos) {
viewHolder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
......
}
});
}
......
}
通过对整个itemView设置clickListener,实现监听click的行为。
增加menu及menu事件
注册menu,对于GridView或者ListVIew,其中一种写法是:
registerForContextMenu(mListView);
对于对应的控件注册菜单
@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
if (v.getId() == R.id.content_list_view) {
getMenuInflater().inflate(R.menu.viewpage_menu, menu);
}
}
重写Activity的onCreateContextMenu方法,通过判断触发事件的控件,决定是否需要绘制菜单。
@Override
public boolean onContextItemSelected(MenuItem item) {
AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) item.getMenuInfo();
int menuItemIndex = item.getItemId();
Log.d(TAG, "menuItem is " + menuItemIndex);
......
return true;
}
重写Activity的onContextItemSelected方法,如果处理的菜单是ListView或者GridView的,从AdapterView.AdapterContextMenuInfo中获取哪个菜单选项被选中。
在切换到RecyclerView后,下面两条都不再生效,这是因为:
1、Google没有为RecyclerView提供默认的menu事件
2、触发menu后也不再有AdapterView.AdapterContextMenuInfo可用
变通的方法和click时类似,仍然通过在itemView绑定时设置:
class RecyclerViewAdapter extends RecyclerView.Adapter<RecyclerViewAdapter.RecyclerViewHolder> {
......
@Override
public void onBindViewHolder(@NonNull RecyclerViewHolder viewHolder, int position) {
......
setMenuListener(viewHolder, position);
}
......
private void setMenuListener(RecyclerViewHolder viewHolder, final int pos) {
viewHolder.itemView.setOnCreateContextMenuListener(new View.OnCreateContextMenuListener() {
@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
......
getMenuInflater().inflate(R.menu.mainpage_menu, menu);
for (int index = 0; index < menu.size(); index++) {
MenuItem item = menu.getItem(index);
if (item.getItemId() == R.id.mainpage_delete_set) {
item.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
......
return false;
}
});
}
}
}
});
}
......
}
这样就可以正确弹出菜单,并获取具体点击了菜单的哪个选项。