本文已参与「新人创作礼」活动,一起开启掘金创作之路。
前言
最近公司新来了一群小伙伴,在学习阶段。每每看到他们不厌其烦的写着Adapter,Activity,Fragment...等等,我都心力憔悴,为此我觉着我得写一个教教他们如何运用Java的抽象、封装、多态、继承来写Base类,减少重复的,无聊的,易错的代码,让代码更好管理。阿弥陀佛。文笔不好,技术不足之处,望评论指出相互交流。
Talk is cheap. Show me the code.
1、RecyclerView.Adapter
那我们来写一个再简单不过的RecyclerView.Adapter的使用吧,我们需要准备下Activity,数据类,和布局,如下。
public class LearningActivity extends AppCompatActivity {
private RecyclerView rvLearning;
private LearningAdapter adapter;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_learning);
rvLearning = findViewById(R.id.rv_learning);
rvLearning.setLayoutManager(new LinearLayoutManager(this, RecyclerView.VERTICAL, false));
rvLearning.setAdapter(adapter = new LearningAdapter());
adapter.setData(generateLearningParams());
adapter.notifyDataSetChanged();
}
private List<LearningParam> generateLearningParams() {
List<LearningParam> learningParams = new ArrayList<>();
for (int i = 0; i < 10; i++) {
learningParams.add(new LearningLearningParam("阿弥陀佛 " + i + 1, "我是一个详细的描述,很详细"));
}
return learningParams;
}
}
数据类
public class LearningParam {
private String title;
private String description;
public LearningParam(String title, String description) {
this.title = title;
this.description = description;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
}
两个布局
<!--activity_learning.xml-->
<?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">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_learning"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
<!--item_learning.xml-->
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:paddingVertical="10dp"
android:paddingHorizontal="15dp"
android:orientation="vertical"
android:layout_height="wrap_content">
<TextView
android:id="@+id/tv_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="--"
android:textSize="18sp"
android:textColor="#000000" />
<TextView
android:id="@+id/tv_description"
android:textSize="12sp"
android:layout_marginTop="10dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="--"
android:textColor="#88000000" />
</LinearLayout>
好了,辅助的东西我们都准备好了,接下来最最最重要的Adapter
public class LearningAdapter extends RecyclerView.Adapter<LearningAdapter.LearningViewHolder> {
private List<LearningParam> data = new ArrayList<>();
public void setData(List<LearningParam> gtp) {
data.clear();
if (gtp != null) {
data.addAll(gtp);
}
}
@NonNull
@Override
public LearningViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_learning, parent, false);
return new LearningViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull LearningViewHolder holder, int position) {
LearningParam itemData = data.get(position);
holder.tvTitle.setText(itemData.getTitle());
holder.tvDescription.setText(itemData.getDescription());
}
@Override
public int getItemCount() {
return data == null ? 0 : data.size();
}
public static class LearningViewHolder extends RecyclerView.ViewHolder {
private TextView tvTitle;
private TextView tvDescription;
public LearningViewHolder(@NonNull View itemView) {
super(itemView);
tvTitle = itemView.findViewById(R.id.tv_title);
tvDescription = itemView.findViewById(R.id.tv_description);
}
}
}
需要一个BaseViewHolder
这是这个LearningRecyclerView的Adapter,如果你这一辈子就只需要这一个Adapter显然,它已经很棒了,没什么大的毛病。可惜显然你可能还需要Adapter001、Adapter002、Adapter004....等等等
又一次,你不厌其烦的写着XXXXAdapter的时候,你意识到,为什么每个Adapter都需要onCreateViewHolder
@NonNull
@Override
public XXXXViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_xxxx, parent, false);
return new XXXXViewHolder(view);
}
而他们的差别只有一个layoutid和ViewHolder不同?于是你觉得可以写一个方法只需要重写一下getLayoutId(),就会返回你需要的ViewHolder,于是你想了想,怎么让每个Adapter都能用到呢?“啊,一个BaseAdapter,让所有的Adapter都继承它!”,于是你写下了BaseAdapter
public class BaseAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>{
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(getLayoutId(), parent, false);
return new RecyclerView.ViewHolder(view);
}
public int getLayoutId(){
return 0;
}
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
}
@Override
public int getItemCount() {
return 0;
}
}
事与愿违的是,你发现一个报错,ViewHolder没有被初始化!原来RecyclerView.ViewHolder是一个抽象对象
"是的,我首先需要一个BaseViewHolder继承RecyclerView.ViewHolder,然后让每一个ViewHolder都继承BaseViewHolder,我就可以只返回BaseViewHolder啦!",你想到。接着写了一个BaseViewHolder,应用到BaseAdapter中
public class BaseViewHolder extends RecyclerView.ViewHolder {
public BaseViewHolder(@NonNull View itemView) {
super(itemView);
}
}
public class BaseAdapter extends RecyclerView.Adapter<BaseViewHolder>{
@NonNull
@Override
public BaseViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(getLayoutId(), parent, false);
return new BaseViewHolder(view);
}
public int getLayoutId(){
return 0;
}
@Override
public void onBindViewHolder(@NonNull BaseViewHolder holder, int position) {}
@Override
public int getItemCount() {
return 0;
}
}
这一切看起来这么完美,“要是有人忘掉了重写ge tLayoutId(),该怎么办?”,你觉得getLayoutId是必须重写的一个方法,那如何让继承的人一定会重写这个方法呢?“抽象”。
abstract class BaseAdapter extends RecyclerView.Adapter<BaseViewHolder> {
@NonNull
@Override
public BaseViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(getLayoutId(), parent, false);
return new BaseViewHolder(view);
}
public abstract int getLayoutId();
@Override
public void onBindViewHolder(@NonNull BaseViewHolder holder, int position) {
}
@Override
public int getItemCount() {
return 0;
}
}
可以见到的是,你把BaseAdapter 和 getLayoutId()都变做了抽象方法,我们来写一个LearningAdapter来继承实现这个BaseAdapter,这样getLayoutId()就一定不会被漏掉了
好了,现在你得到了一个不需要再去create的ViewHolder了,你想那这些findViewById之类的也能去掉嘛?
还记得我们的BaseViewHolder嘛,那我们就要在这里动手脚了,既然所有的XXXViewHolder extends BaseViewHolder ,而BaseViewHolder又有ItemView的对象,那干脆把setText写好,不就可以不findViewById了吗。你构思了下,用id和文本就可以很便捷的使用了!于是你顺手就写了好几个方法。
public void setText(@IdRes int id, CharSequence text) {
View view = itemView.findViewById(id);
if (view instanceof TextView) {
((TextView) view).setText(text);
}
}
public void setTextColor(@IdRes int id, @ColorInt int color) {
View view = itemView.findViewById(id);
if (view instanceof TextView) {
((TextView) view).setTextColor(color);
}
}
public void setTextSize(@IdRes int id, float size) {
View view = itemView.findViewById(id);
if (view instanceof TextView) {
((TextView) view).setTextSize(size);
}
}
我们来用一用
public class LearningAdapter extends BaseAdapter {
private List<LearningParam> data = new ArrayList<>();
public void setData(List<LearningParam> gtp) {
data.clear();
if (gtp != null) {
data.addAll(gtp);
}
}
@Override
public int getItemCount() {
return data == null ? 0 : data.size();
}
@Override
public int getLayoutId() {
return R.layout.item_learning;
}
@Override
public void onBindViewHolder(@NonNull BaseViewHolder holder, int position) {
super.onBindViewHolder(holder, position);
holder.setText(R.id.tv_title, "Title " + position);
holder.setTextSize(R.id.tv_title, 12);
holder.setTextColor(R.id.tv_title, Color.parseColor("#FF0000"));
holder.setText(R.id.tv_description, "Description " + position);
}
}
非常成功!可是一个控件“tv_title”改变多个属性要使用 这么多"holder."太麻烦了,我们可以用链式调用
可以看到我们把每一个返回值都变成了返回BaseViewHolder本身,这样我们就可以如下使用了,美滋滋
holder.setText(R.id.tv_title, "Title " + position).setTextColor(R.id.tv_title, Color.parseColor("#FF0000")).setTextSize(R.id.tv_title, 12);
需要一个通用的数据源
ViewHolder解决了,数据源能不能也优化一下下呢?毕竟容易出错,写起来也麻烦
“写一个BaseParam,所有的数据源继承BaseParam,你想照着BaseViewHolder的操作来一套移魂大法",但是这样所有的数据源都需要改成BaseParam,而你却只是为了保证数据源的可变性。
泛型,是泛型,我在里面加了泛型。
abstract class BaseAdapter<T> extends RecyclerView.Adapter<BaseViewHolder> {
@Nullable
private List<T> mData = new ArrayList<>();
/**
* 存入数据源
*/
protected void setData(@Nullable List<T> data) {
if (mData == null) {
mData = Objects.requireNonNullElseGet(data, ArrayList::new);
} else {
mData.clear();
if (data != null) {
mData.addAll(data);
}
}
}
/**
* 获取单个Item
*
* @param position
* @return T or NULL
*/
@Nullable
protected T getItem(int position) {
if (mData == null || position >= mData.size()) {
return null;
}
return mData.get(position);
}
/**
* 计算条目
* @return count
*/
@Override
public int getItemCount() {
return mData == null ? 0 : mData.size();
}
@Override
public void onBindViewHolder(@NonNull BaseViewHolder holder, int position) {
}
@NonNull
@Override
public BaseViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(getLayoutId(), parent, false);
return new BaseViewHolder(view);
}
public abstract int getLayoutId();
}
这时候回头看我们的LearningAdapter,并不需要自己再写setData啦,base层已经帮我们做了
而用法没有改变
接下来,我会讲述如何优化ViewHolder和封装点击事件,长按事件等
看到这的你也可以思考下给ViewHolder添加更多的方法
就先到这吧,溜了溜了