![128a586241c709d7aba72d4df9c9c124.png](https://i-blog.csdnimg.cn/blog_migrate/1c9f753eb67c1252ec9a38c1f5f2b1c5.jpeg)
一年前,用 Java 写了一个高可扩展选择按钮库。单个控件实现单选、多选、菜单选,且选择模式可动态扩展。
一年后,一个新的需求要用到这个库,项目代码已经全 Kotlin 化,强硬地插入一些 Java 代码显得格格不入,Java 冗余的语法也降低了代码的可读性,于是决定用 Kotlin 重构一番,在重构的时候也增加了一些新的功能。这一篇分享下重构的过程。
建议关注:不定时分享Android方面的技术、及大厂面试真题分析Android技术进阶屋zhuanlan.zhihu.com
![226196731d44c3295139782ba7400d7a.png](https://i-blog.csdnimg.cn/blog_migrate/000c86de255716f7c3c2bae4b3b5da6c.jpeg)
选择按钮的可扩展性主要体现在 4 个方面:
- 选项按钮布局可扩展
- 选项按钮样式可扩展
- 选中样式可扩展
- 选择模式可扩展
扩展布局
原生的单选按钮通过RadioButton+ RadioGroup实现,他们在布局上必须是父子关系,而RadioGroup继承自LinearLayout,遂单选按钮只能是横向或纵向铺开,这限制的单选按钮布局的多样性,比如下面这种三角布局就难以用原生控件实现:
![92fe9860f0360e747e0f6858b8f5fcfa.gif](https://i-blog.csdnimg.cn/blog_migrate/1b7135d66196dbc965304ad3f05a7c23.gif)
为了突破这个限制,单选按钮不再隶属于一个父控件,它们各自独立,可以在布局文件中任意排列,图中 Activity 的布局文件如下(伪码):
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Selector age"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<test.taylor.AgeSelector
android:id="@+id/selector_teenager"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/title"
app:layout_constraintStart_toStartOf="parent"/>
<test.taylor.AgeSelector
android:id="@+id/selector_man"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintEnd_toStartOf="@id/selector_old_man"
app:layout_constraintTop_toBottomOf="@id/selector_teenager"
app:layout_constraintStart_toStartOf="parent"/>
<test.taylor.AgeSelector
android:id="@+id/selector_old_man"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/selector_teenager"
app:layout_constraintStart_toEndOf="@id/selector_man"/>
</androidx.constraintlayout.widget.ConstraintLayout>
AgeSelector表示一个具体的按钮,本例中它是一个“上面是图片,下面是文字”的单选按钮。它继承自抽象的Selector。
扩展样式
从业务上讲,Selector长什么样是一个频繁的变化点,遂把“构建按钮样式”这个行为设计成Selector的抽象函数onCreateView(),供子类重写以实现扩展。
public abstract class Selector extends FrameLayout{
public Selector(Context context) {
super(context);
initView(context, null);
}
private void initView(Context context, AttributeSet attrs) {
// 初始化按钮算法框架
View view = onCreateView();
LayoutParams params = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
this.addView(view, params);
}
// 如何构建按钮视图,延迟到子类实现
protected abstract View onCreateView();
}
Selector继承自FrameLayout,实例化时会构建按钮视图,并把该视图作为孩子添加到自己的布局中。子类通过重写onCreateView()扩展按钮样式:
public class AgeSelector extends Selector {
@Override
protected View onCreateView() {
View view = LayoutInflater.from(this.getContext()).inflate(R.layout.age_selector, null);
return view;
}
}
AgeSelector的样式被定义在 xml 中。
按钮被选中之后的样式,也是一个业务上的变化点,用同样的思路可以将Selector这样设计:
// 抽象按钮实现点击事件
public abstract class Selector extends FrameLayout implements View.OnClickListener {
public Selector(Context context) {
super(context);
initView(context, null);
}
private void initView(Context context, AttributeSet attrs) {
View view = onCreateView();
LayoutParams params = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
this.addView(view, params);
// 设置点击事件
this.setOnClickListener(this);
}
@Override
public void onClick(View v) {
// 原有选中状态
boolean isSelect = this.isSelected();
// 反转选中状态
this.setSelected(!isSelect);
// 展示选中状态切换效果
onSwitchSelected(!isSelect);
return !isSelect;
}
// 按钮选中状态变化时的效果延迟到子类实现
protected abstract void onSwitchSelected(boolean isSelect);
}
将选中按钮状态变化的效果抽象成一个算法,延迟到子类实现:
public class A