标签:resid mic 设置 可变 gre 种类型 return tms 命名
在模板模式(Template Pattern)中,一个抽象类公开定义了执行它的方法的方式/模板。它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行。这种类型的设计模式属于行为型模式。
模版设计模式的本质便是固定算法框架。
上面的概念中有三个要点:
代码复用,避免重复
下面,让我们来介绍一个例子。假设我们需要设计一个即时聊天工具,这个工具可以显示图片、语音、文本、表情等等类型的消息。并且可以显示消息发送的状态,显示消息的时间,显示成员头像,显示成员名称。显示有哪些人读了你的消息。相信使用过 QQ 或者微信的人对这些一定都不陌生。我们暂且将这些功能称为:消息内容、消息时间、消息回执、消息状态、成员头像、成员名称。以发送端的消息显示为例,其可以长这个样子。
根据我们对 QQ 和微信的使用,可以很明显的看出,以下的部分是公有布局:
除开上面的公共部分,其实每种消息类型的不同之处便是消息内容部分,对应上图中的主布局区域。
理清了这些思路,我们可以很轻松的根据模版模式构建出一个消息的显示流程。
首先,我们应该确定在哪个类里面进行消息的绑定操作。对于 Android,现在已经开始流行使用 RecyclerView,使用 RecyclerView,我们应该在 ViewHolder 中,进行数据的绑定。通过定义一个基类 ViewHolder,实现公共的逻辑。然后定义子类,实现不同消息类型的消息内容的绑定,便可以定义出一套消息绑定的流程。
下面,我们来一一讲解。首先是流程。
布局就不详讲了。主要是采用 include 标签,将公共的布局包含到对应的消息类型中。我们重点讲解第2、3步。
基类的定义属于第二步,又可以具体细分为三步。其定义方式如下(注:以下所有的代码,只讲解流程,不会涉及具体的代码):
public abstract class BaseChatItemHolder extends RecyclerView.ViewHolder {
// 2.1 定义外部类调用入口,绑定数据
public void onBindViewHolder(Msg msg, int position) {
// 绑定消息公共的数据部分
bindCommonData(msg, position);
// 绑定消息私有的数据部分
bindPrivateData(msg, position);
}
// 2.2 定义公共的数据绑定流程,流程不可更改
public void bindCommonData(Msg msg, int position) {
// 设置消息时间
setTime(msg);
// 设置成员头像
setHead(msg);
// 设置成员名称
setName(msg);
// 设置消息状态
setState(msg);
// 设置消息回执
setMsgReceipt(msg);
// 设置公共操作
setCommonOperation(msg);
}
// 2.3 定义公共方法,非抽象,需要父类提供实现
// 设置消息时间
public void setTime(Msg msg) {
// 省略具体绑定过程
}
// 设置成员头像
public void setHead(Msg msg) {
// 省略具体绑定过程
}
// 设置成员名称
public void setName(Msg msg) {
// 省略具体绑定过程
}
// 设置消息状态
public void setState(Msg msg) {
// 省略具体绑定过程
}
// 设置消息回执
public void setMsgReceipt(Msg msg) {
// 省略具体绑定过程
}
// 设置公共操作
public void setCommonOperation(Msg msg) {
// 省略具体绑定过程
}
// 2.4 定义消息私有内容绑定的抽象方法
public abstract void bindPrivateData(Msg msg, int position);
}
在上面的流程设计中,我们定义了公共方法,以表示公共消息布局的绑定过程,公共的方法不必声明为 abstract。子类特有的消息内容区域需要设置为抽象类型的,表示子类必须自己处理消息内容布局的绑定过程,并且消息内容的布局需要自己设计。但必须置于一个公共的父布局下。比如所有消息类型的内容布局,其根布局必须是 ConstraintLayout。
Msg 表示消息,是所有消息类型的父类。如图片消息 PhotoMsg、语音 VoiceMsg 等等这些消息类型都由其衍生出来。其包含了所有消息类型共有的一些属性,比如消息的 id,发送者的 id,接受者的 id,聊天框的 id 等等。当然,具体的内容视业务而定,这里只是举个例子。
接下来,我讲讲子类如何利用父类定义的流程。这里举两个例子。以图片消息(PhotoMsg)、语音消息(VoiceMsg)为例。图片消息无需什么特殊的操作,而语音消息需要特殊的长按操作。
注:前面提到过,消息内容的布局应该包裹在 ConstraintLayout 中,如下:
<!-- 消息内容的布局区域,可以统一命名,方便设置公共操作 -->
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/chat_msg_item_content"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<!-- 消息内容的布局 -->
</androidx.constraintlayout.widget.ConstraintLayout>
图片消息
根据上面提到的原则,图片消息的布局如下:
<!-- 消息内容的布局区域,可以统一命名,方便设置公共操作 -->
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/chat_msg_item_content"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<!-- 显示图片的 ImageView,布局参数省略 -->
<ImageView
android:id="@+id/iv_photo"
android:layout_width="40dp"
android:layout_height="40dp" />
</androidx.constraintlayout.widget.ConstraintLayout>
public class PhotoChatItemHolder extends BaseChatItemHolder {
public PhotoChatItemHolder(ViewGroup parent, @LayoutRes int resId) {
super(parent, resId);
}
@Override
public void bindPrivateData(Msg msg, int position) {
// 父类消息转成图片消息,进行私有消息内容数据部分的绑定
PhotoMsg photoMsg = (PhotoMsg) msg;
// 省略具体绑定过程
...
}
}
图片消息除了私有部分,其他无须特殊处理,便可以复用父类的绑定流程。无需重写父类中的非 abstract 类型的方法,使用父类提供的默认实现即可。
语音消息
语音消息的布局如下:
<!-- 消息内容的布局区域,可以统一命名,方便设置公共操作 -->
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/chat_msg_item_content"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<!-- 显示图片的 ImageView,布局参数省略 -->
<VoiceView
android:id="@+id/vv_voice"
android:layout_width="40dp"
android:layout_height="40dp" />
</androidx.constraintlayout.widget.ConstraintLayout>
上面我们假设了语音需要特殊的长按操作。则可以构建如下代码:
public class VoiceChatItemHolder extends BaseChatItemHolder {
public VoiceChatItemHolder(ViewGroup parent, @LayoutRes int resId) {
super(parent, resId);
}
@Override
public void setCommonOperation(Msg msg) {
// 1. 子类重写绑定过程,代执行到此处,会采用子类的实现,不会调用父类的方法
...
// 2. 如果任性一点,不要长按事件,这里的实现甚至可以返回空。做到差异化。
}
@Override
public void bindPrivateData(Msg msg, int position) {
// 父类消息转成语音消息,进行私有数据部分的绑定
VoiceMsg voiceMsg = (VoiceMsg) msg;
...
// 3. 甚至你可以在这里调用公共操作方法,自定义部分加载流程,覆盖上面的长按事件调用。
setCommonOperation(msg);
...
}
}
上面的例子,我们重写了父类中的非抽象公共方法。但是却没有改变绑定流程,便达到了我们想要的效果。并且我们应该将私有部分消息内容的数据绑定放到最后,这样可以给予最大的自由度。另外给了三点说明,写在了注释中。
学习 Java 的时候,我们都知道 Java 类有声明类型和实际类型。此处我们要想正确的调用,达到正确效果,便需要返回正确的实际类型。创建 ViewHolder 时,可以这么写。以图片和语音消息为例。
public class ViewHolderManager {
public BaseChatItemHolder createViewHolder(ViewGroup parent, int msgType) {
int resId = MsgLayoutManager.getInstance().getLayoutResId(msgType);
switch(msgType) {
case MsgType.PHOTO:
return new PhotoChatItemHolder(parent, resId);
break;
case MsgType.VOICE:
return new VoiceChatItemHolder(parent, resId);
break;
}
}
}
上面的实现很简单。但却能达到我们想要的效果。
讲完了构建,下面就讲下调用,很简单,在真正需要绑定布局的地方,比如消息适配器 MsgAdapter 的绑定方法中,调用 ViewHolder 的绑定方法即可。
public class MsgAdapter extends RecyclerView.Adapter<BaseChatItemHolder>{
List<Msg> msgList;
@Override
public BaseChatItemViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return ViewHolderManager.getInstance().createViewHolder(parent, viewType);
}
@Override
public void onBindViewHolder(BaseChatItemViewHolder holder, int position) {
Msg msg = getItem(position);
holder.onBindViewHolder(msg, position);
}
public Msg getItem(int position) {
return msgList.get(i);
}
}
上面的调用过程,创建和绑定数据的流程都很简单。
下面来总结一下。
标签:resid mic 设置 可变 gre 种类型 return tms 命名
原文地址:https://www.cnblogs.com/wellcherish/p/12236918.html