示例项目采用:
MD + MVP + Retrofit2 + Gson + RxJava2 + Realm
示例项目 GIF:
示例 GIF.gif示例项目已同步到 GitHub:
https://github.com/cnwutianhao/RecyclerViewSample
欢迎交流,我会持续更新,不断完善自己对 RecyclerView 的认识。
这个示例项目没有依赖,基本上照着学会了 RecyclerView 也就掌握了。
五部分 + 彩蛋
- 列表 item
- 列表到详情 item -> detail
- 搜索 search
- 搜索到详情 search -> detail
- 彩蛋(一些新特性)
项目遵循 MVP 架构样式(参考 Google Sample 的 TODO-MVP),项目整体是 一个 Activity + N 个 Fragment 构成。
开始我的 FreeStyle !
导入依赖库:
compile 'com.android.support:recyclerview-v7:26.0.0-alpha1'
列表 item
RecyclerView 也是一个 View , 那么就要在布局里面先定义好:
<android.support.v7.widget.RecyclerView
android:id="@+id/recyclerViewCompaniesList"
android:layout_width="match_parent"
android:layout_height="match_parent" />
在 Fragment 里面初始化 RecyclerView,并使用 LayoutManager 来确定每一个 item 的排列方式
private RecyclerView mRecyclerView;
mRecyclerView = view.findViewById(R.id.recyclerViewCompaniesList);
mRecyclerView.setLayoutManager(layoutManager);
重头戏:自定义 Adapter
继承父类 RecyclerView.Adapter<RecyclerView.ViewHolder>
得到 onCreateViewHolder 、onBindViewHolder 和 getItemCount
onCreateViewHolder 用来创建新 view,被 LayoutManager 所调用。
onBindViewHolder 将数据与界面进行绑定的操作。
getItemCount 获取数据的数量。
除此之外,我们还需要自定义 ViewHolder,持有每个 Item 的所有界面元素。
彩蛋:公开一个 interface ,用于对 RecyclerView 的 item 做点击事件监听。
public interface OnRecyclerViewItemClickListener {
void OnItemClick(View v, int position);
}
自定义 ViewHolder
@Nullable
private OnRecyclerViewItemClickListener mListener;
public class ExpressCompaniesViewHolder extends RecyclerView.ViewHolder
implements View.OnClickListener {
ImageView avatar;
AppCompatTextView textViewCompanyName;
AppCompatTextView textViewAvatar;
AppCompatTextView textViewCompanyTel;
private OnRecyclerViewItemClickListener listener;
public ExpressCompaniesViewHolder(View itemView, OnRecyclerViewItemClickListener listener) {
super(itemView);
avatar = itemView.findViewById(R.id.imageViewAvatar);
textViewAvatar = itemView.findViewById(R.id.textViewAvatar);
textViewCompanyName = itemView.findViewById(R.id.textViewCompanyName);
textViewCompanyTel = itemView.findViewById(R.id.textViewCompanyTel);
this.listener = listener;
itemView.setOnClickListener(this);
}
@Override
public void onClick(View v) {
if (this.listener != null) {
listener.OnItemClick(v, getLayoutPosition());
}
}
}
重写 onCreateViewHolder
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return new ExpressCompaniesViewHolder(mInflater.inflate(R.layout.item_company, parent, false), mListener);
}
重写 onBindViewHolder
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
ExpressCompany company = mList.get(position);
ExpressCompaniesViewHolder cvh = (ExpressCompaniesViewHolder) holder;
cvh.textViewAvatar.setText(company.getName().substring(0, 1).toUpperCase());
cvh.textViewCompanyTel.setText(company.getTel());
cvh.textViewCompanyName.setText(company.getName());
cvh.avatar.setColorFilter(Color.parseColor(company.getAvatarColor()));
}
重写 getItemCount
@Override
public int getItemCount() {
return mList.size();
}
列表到详情 item -> detail
公开一个方法暴露给 Fragment,使得当点击 Item 的时候可以准确的对应到。
public void setOnRecyclerViewItemClickListener(OnRecyclerViewItemClickListener listener) {
this.mListener = listener;
}
在 Fragment 里面初始化自定义的 Adapter,并做点击事件。
private ExpressCompaniesAdapter mAdapter;
mAdapter = new ExpressCompaniesAdapter(getContext(), list);
mAdapter.setOnRecyclerViewItemClickListener(new OnRecyclerViewItemClickListener() {
@Override
public void OnItemClick(View v, int position) {
Intent intent = new Intent(getContext(), ExpressCompanyDetailActivity.class);
intent.putExtra(ExpressCompanyDetailActivity.COMPANY_ID, list.get(position).getId());
startActivity(intent);
}
});
mRecyclerView.setAdapter(mAdapter);
将 Key:唯一的 ID , Value:对应的 Position 传给详情页。
这样我们就完成了从 item 到 detail 的第一步。
在详情页拿到唯一的 ID 之后,根据调取的 JSON 数据,我们就可以拿到整个字段,并将字段里面的信息,逐一填到对应的 View 上。
搜索 search
搜索一般会显示两种情况:有数据、没有数据
继承父类
RecyclerView.Adapter<RecyclerView.ViewHolder>
就会有 onCreateViewHolder 、 onBindViewHolder 、 getItemCount 和 自定义 ViewHolder 。
自定义没有数据时的 ViewHolder
private class EmptyHolder extends RecyclerView.ViewHolder {
AppCompatTextView textView;
EmptyHolder(View itemView) {
super(itemView);
textView = itemView.findViewById(R.id.tv_title);
}
}
自定义有数据时的 ViewHolder
private class CompanyHolder extends RecyclerView.ViewHolder
implements View.OnClickListener {
ImageView avatar;
AppCompatTextView textViewCompanyName, textViewAvatar, textViewCompanyTel;
private OnRecyclerViewItemClickListener listener;
CompanyHolder(View itemView, OnRecyclerViewItemClickListener listener) {
super(itemView);
avatar = itemView.findViewById(R.id.imageViewAvatar);
textViewAvatar = itemView.findViewById(R.id.textViewAvatar);
textViewCompanyName = itemView.findViewById(R.id.textViewCompanyName);
textViewCompanyTel = itemView.findViewById(R.id.textViewCompanyTel);
this.listener = listener;
itemView.setOnClickListener(this);
}
@Override
public void onClick(View v) {
if (listener != null) {
listener.OnItemClick(v, getLayoutPosition());
}
}
}
重写 onCreateViewHolder
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
RecyclerView.ViewHolder viewHolder = null;
switch (viewType) {
case ItemWrapper.TYPE_EMPTY:
viewHolder = new EmptyHolder(mInflater.inflate(R.layout.item_search_result_empty, parent, false));
break;
case ItemWrapper.TYPE_COMPANY:
viewHolder = new CompanyHolder(mInflater.inflate(R.layout.item_company, parent, false), mListener);
break;
}
return viewHolder;
}
重写 onBindViewHolder
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
ItemWrapper iw = mList.get(position);
switch (iw.viewType) {
case ItemWrapper.TYPE_EMPTY:
EmptyHolder emptyHolder = (EmptyHolder) holder;
emptyHolder.textView.setText(mCompanies == null ?
R.string.item_loading : R.string.no_result);
break;
case ItemWrapper.TYPE_COMPANY:
ExpressCompany company = mCompanies.get(iw.index);
CompanyHolder companyHolder = (CompanyHolder) holder;
companyHolder.textViewAvatar.setText(company.getName().substring(0, 1).toUpperCase());
companyHolder.textViewCompanyTel.setText(company.getTel());
companyHolder.textViewCompanyName.setText(company.getName());
companyHolder.avatar.setColorFilter(Color.parseColor(company.getAvatarColor()));
break;
}
}
重写 getItemCount
@Override
public int getItemCount() {
return mList != null ? mList.size() : 0;
}
搜索到详情 search -> detail
公开一个方法暴露给 Fragment,使得当点击 Item 的时候可以准确的对应到。
public void setOnItemClickListener(OnRecyclerViewItemClickListener listener) {
this.mListener = listener;
}
在 Fragment 里面初始化自定义的 Adapter,并做点击事件。
private SearchAdapter mSearchAdapter;
if (mSearchAdapter == null) {
mSearchAdapter = new SearchAdapter(getContext(), companies);
mSearchAdapter.setOnItemClickListener(new OnRecyclerViewItemClickListener() {
@Override
public void OnItemClick(View v, int position) {
Intent intent = new Intent(getContext(), ExpressCompanyDetailActivity.class);
intent.putExtra(ExpressCompanyDetailActivity.COMPANY_ID, companies.get(mSearchAdapter.getOriginalIndex(position)).getId());
startActivity(intent);
}
});
mRecyclerView.setAdapter(mSearchAdapter);
}
将 Key:唯一的 ID , Value:对应的 Position 传给详情页。
这样我们就完成了从 item 到 detail 的第一步。
在详情页拿到唯一的 ID 之后,根据调取的 JSON 数据,我们就可以拿到整个字段,并将字段里面的信息,逐一填到对应的 View 上。
彩蛋
留心的同学会发现我们这里有一个弹出网页页面的动作。
示例项目没有添加 webview ,而是使用了更有好的,Google 推荐的方式实现网页显示,给用户一种感觉就是好像我们的 App 里面嵌套了浏览器一样。
导入官方库:
compile 'com.android.support:customtabs:26.0.0-alpha1'
没接触过此库的,第一感觉一定是这是一个自定义的 Tab,但并非如此,后续我会针对 Google 新推出的几个库做一个小结。
自定义 CustomTabsHelper 类
public class CustomTabsHelper {
public static void openUrl(Context context, String url) {
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
if (sharedPreferences.getBoolean(SettingsUtil.KEY_CUSTOM_TABS, true)) {
CustomTabsIntent.Builder builder = new CustomTabsIntent.Builder();
builder.setToolbarColor(ContextCompat.getColor(context, R.color.colorPrimaryDark));
builder.build().launchUrl(context, Uri.parse(url));
} else {
try {
context.startActivity(new Intent(Intent.ACTION_VIEW).setData(Uri.parse(url)));
} catch (ActivityNotFoundException e) {
Toast.makeText(context, R.string.error_no_browser, Toast.LENGTH_SHORT).show();
}
}
}
}
这样当我们点击 URL 的时候,直接调用 openUrl 方法即可。
注:如果你的 Android 手机或模拟器没有 Chrome ,会看不到效果。
至此,我们的 RecyclerView 从理论到实践就到此结束。
网友评论