背景
随着公司项目越来越多,会使用到公共的UI组件,那么将组件抽离出来独立成一个lib库是有必要的。但是,不同的项目存在一定的差异化是不可避免的,这时候,如何在提高公共组件可复用性又可实现UI差异?那么UI定制化就是本文的重点。
组件模块化
组件模块化根据粒度大小,将其分为:控件模块化和功能模块化。
控件模块化,就是将某个控件抽离出来,它仅仅用来显示效果,不包括业务逻辑;
相对的,功能模块化,就是包含了业务逻辑,一个较完整的功能模块。
控件模块化
控件模块化中的UI定制,和Android中自定义View一样,在res/attr.xml文件下声明一个declare-styleable
<declare-styleable name="YLCalendar">
<attr name="todayBorderColor" format="color"/>
<attr name="todayShape">
<enum name="circle" value="0"/>
<enum name="rectangle" value="1"/>
</attr>
<attr name="todayFill" format="boolean"/>
<attr name="todayTextColor" format="color"/>
<attr name="todayTextSize" format="dimension"/>
<!--公历字体颜色-->
<attr name="solarTextColor" format="color" />
<!--农历字体颜色-->
<attr name="lunarTextColor" format="color" />
<!--不在当月日期的颜色-->
<attr name="hintColor" format="color" />
</declare-styleable>
然后在自定义view中去获取属性值
public abstract class CalendarViewPager extends ViewPager{
public CalendarViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
initAttr(context,attrs);
}
private void initAttr(Context context, AttributeSet attrs){
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.YLCalendar);
...
ta.recycle();
}
}
最后在引用自定义view时,给它配置属性值
<com.yealink.calendar.calendar.MWCalendar
android:id="@+id/mWCalendar"
android:layout_width="match_parent"
android:layout_height="match_parent"
style="@style/YLCalendarStyle">
</com.yealink.calendar.calendar.MWCalendar>
其中YLCalendarStyle包含了R.styleable.YLCalendar的属性项
<style name="YLCalendarStyle">
<!--今天绘制形状:圆形、矩形-->
<item name="todayShape">circle</item>
<!--今天形状是否实心-->
<item name="todayFill">true</item>
<!--今天字体颜色-->
<item name="todayTextColor">@android:color/white</item>
<!--今天字体大小-->
<item name="todayTextSize">@dimen/solarTextSize</item>
</style>
这是将控件进行封装,将一些属性暴露出来,然后使用的时候通过xml进行动态设置。
功能模块化
功能模块与控件模块UI定制的不同之处在于,功能模块已经将控件进行组合显示,并包含了一些业务逻辑操作,因此它无法像控件那样通过xml进行引用,外界只能通过接口调起该功能模块,因此它的UI定制将通过另外一种方式。
第一种方式:通过theme主题
在attr.xml文件下声明属性
<resources>
<!--标题栏背景色-->
<attr name="titleBarBackground" format="color"/>
<!--状态栏高度-->
<attr name="statusBarHeight" format="dimension"/>
</resources>
和之前的styleable属性不同,它不在declare-styleable节点,而是在根节点直接声明。接着使用的时候,如下:
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<View
android:id="@+id/status_bar"
android:layout_width="match_parent"
android:layout_height="?attr/statusBarHeight"
android:background="?attr/titleBarBackground"/>
</LinearLayout>
可以看到,通过?attr进行引用,它标示预定义样式,属性值会随着主题而改变。
最后定制UI的时候,可以在应用的主题theme进行赋值,如下:
<style name="AppBaseTheme" parent="Theme.AppCompat.Light.NoActionBar">
...
<item name="statusBarHeight">0dp</item>
<item name="titleBarBackground">@color/app_primary_white</item>
</style>
第二种方式:通过style样式
这种方式和控件模块有点相似,只是它不是通过xml配置。在res/attr.xml文件下声明一个declare-styleable
<declare-styleable name="YlAlbum">
<!--状态栏和标题栏颜色-->
<attr name="statusBarColor" format="color"/>
<!--是否显示标题栏左边按钮文字“返回”-->
<attr name="showTitleBarLeftText" format="boolean"/>
<!--要显示的图片格式-->
<attr name="imageFormat">
<flag name="jpg" value="1"/>
<flag name="jpeg" value="2"/>
<flag name="png" value="4"/>
<flag name="bmp" value="8"/>
<flag name="gif" value="16"/>
<flag name="all" value="31"/>
</attr>
<attr name="sendButtonText" format="string"/>
</declare-styleable>
接着在style.xml定义一个style,配置属性值
<style name="YlAlbumStyle">
<!--状态栏和标题栏颜色-->
<item name="statusBarColor">@color/album_lib_status_bar_color</item>
<!--是否显示标题栏左边按钮文字“返回”-->
<item name="showTitleBarLeftText">false</item>
<!--要显示的图片格式-->
<item name="imageFormat">gif|jpg|jpeg|bmp|png</item>
<!--发送按钮显示的文字内容-->
<item name="sendButtonText">@string/album_lib_send</item>
</style>
style中的属性项都是来于declare-styleable(上面的YlAlbum)
最后在代码中读取属性值,并使用。
private void initAttributes(){
int styleID = getIntent().getIntExtra("styleID",R.style.YlAlbumStyle);
TypedArray typedArray = obtainStyledAttributes(styleID,R.styleable.YlAlbum);
Attrs.statusBarColor = typedArray.getColor(R.styleable.YlAlbum_statusBarColor,getResources().getColor(R.color.album_lib_status_bar_color));
Attrs.showTitleBarLeftText = typedArray.getBoolean(R.styleable.YlAlbum_showTitleBarLeftText,true);
Attrs.imageFormat = typedArray.getInt(R.styleable.YlAlbum_imageFormat,Attrs.ImageFormat.ALL.getFlag());
Attrs.sendButtonText = typedArray.getText(R.styleable.YlAlbum_sendButtonText);
typedArray.recycle();
}
主要是通过getTheme().obtainStyledAttributes方法读取styleable的属性值,代码中使用这些属性值。
总结
UI定制有三种形式:
1. 自定义view通过xml配置
2. 通过在xml预定义?/attr,配置theme主题
3. 通过自定义style,代码中读取style属性值
第一种相对较灵活,在xml引用自定义view时即可配置属性值;第二种,由于是跟随主题,而一个activity或者application只配置一次主题,导致项目中只能使用一套UI;第三种,可以动态的使用不同的style,定制不同的UI,且属性项是在一个declare-styleable内,较集中,方便管理。因此,一般来说,控件模块化使用第一种方式,功能模块化使用第三种方式。