Android 无聊分享Day02

271 阅读5分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

前言

​ 最近公司新来了一群小伙伴,在学习阶段。每每看到他们不厌其烦的写着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是一个抽象对象

image-20220124030840873

​ "是的,我首先需要一个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()就一定不会被漏掉了

image-20220125112944796

​ 好了,现在你得到了一个不需要再去create的ViewHolder了,你想那这些findViewById之类的也能去掉嘛?

image-20220125113949176

​ 还记得我们的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."太麻烦了,我们可以用链式调用

image-20220125115651010

​ 可以看到我们把每一个返回值都变成了返回BaseViewHolder本身,这样我们就可以如下使用了,美滋滋

holder.setText(R.id.tv_title, "Title " + position).setTextColor(R.id.tv_title, Color.parseColor("#FF0000")).setTextSize(R.id.tv_title, 12);
需要一个通用的数据源

​ ViewHolder解决了,数据源能不能也优化一下下呢?毕竟容易出错,写起来也麻烦

image-20220125150942613

​ “写一个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层已经帮我们做了

image-20220125152931485

​ 而用法没有改变

image-20220125160608344

​ 接下来,我会讲述如何优化ViewHolder和封装点击事件,长按事件等

看到这的你也可以思考下给ViewHolder添加更多的方法

​ 就先到这吧,溜了溜了