写点什么

UI 组件化 -- 干掉 shape 终极一战,android 模块化框架

用户头像
Android架构
关注
发布于: 刚刚
  • LoadingView

  • ShimmerLayout

  • uikit-module

  • flatButton

  • roundView

  • load

  • dialog

  • imageSelect

  • toast

工程分层

工程架构可以分为 5 层,分别是:基础控件、组合控件、业务 UI 组件、桥接、demo。


  • 基础控件:提供原子能力,单点控件,比如层叠布局 FlowLayout、骨架控件 ShimmerLayout、按钮 Flatbutton

  • 组合控件:会依赖基础控件,比如 Dialog、ImageSelector,这些控件 UI 会比


《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》
浏览器打开:qq.cn.hn/FTe 免费领取
复制代码


较复杂,所以会用到 FlatButton、ShimmerLayout 等基础组件来提高开发效率


  • 业务 UI 组件:这个就是我们真正要实现的 UI 组件,基于设计要求定制开发,在基础控件和组合控件上配置业务偏好,组合成业务组件,开发工作量比较小

  • 桥接:业务层不感知 UI 组件的个数和依赖关系,业务层只依赖 uikit,而 UI 组件的依赖管理收敛到 uikit 中,这样的好处就是,后续迭代只在 uikit 维护依赖关系即可

  • Demo: 一个好的组件除了使用文档,还需要有直观的示例代码,demo 可以直接集成到调试面板中


架构分层如下图:



一个好的架构应该层次分明、低耦合、高扩展,对组件的增删支持的足够友好,任何组件都能准确的找到对应的分层,并且不会改动到已有代码,所以 review 一下刚刚设计的架构,基本上满足需求,架构设计符合预期。架构设计好之后的步骤就是实施了,如何和现有的工程做结合呢,UI 组件按阶段可以分为:开发阶段、稳定阶段,理想的开发模式为开发阶段在宿主工程中开发调试,但是放宿主工程中会带了编译慢的问题,组件开发和业务是接耦的,所以希望代码在宿主工程,demo 和组件开发可以单独运行,当组件开发完成,到了稳定阶段,组件代码修改频率降低,同时加快编译速度,UIKit 组件发布到远程 maven 仓库,最终 uikit 工程独立出来,单独迭代,下面是工程架构实现



UI 组件和宿主打包编译


settings.gradle


includeIfAbsent ':uikit:uikit'


includeIfAbsent ':uikit:demo'


includeIfAbsent ':uikit:imgselector'


includeIfAbsent ':uikit:roundview'


includeIfAbsent ':uikit:widget'


includeIfAbsent ':uikit:photodraweeview'


includeIfAbsent ':uikit:flatbutton'


includeIfAbsent ':uikit:dialog'


includeIfAbsent ':uikit:widgetlayout'


includeIfAbsent ':uikit:statusbar'


includeIfAbsent ':uikit:toolbar'


common_business.gradle 中一键依赖


apply from: rootProject.file("library_base.gradle")


dependencies {


.?..


implementation project(":uikit:uikit")


}


UI 组件独立编译


uikit/shell/settings.gradle


include ':app'


includeModule('widget','../')


includeModule('demo','../')


includeModule('flatbutton','../')


includeModule('imgselector','../')


includeModule('photodraweeview','../')


includeModule('roundview','../')


includeModule('uikit','../')


includeModule('widgetlayout','../')


includeModule('dialog','../')


includeModule('statusbar','../')


includeModule('toolbar','../')


def includeModule(name, filePath = name) {


def projectDir = new File(filePath+name)


if (projectDir.exists()) {


include ':uikit:' + name


project(':uikit:' + name).projectDir = projectDir


} else {


print("settings:could not find module filePath")


}


}


UI 组件 lib 的 build.gradle 中


if (rootProject.ext.is_in_uikit_project) {


apply from: rootProject.file('../uikit.gradle')


} else {


apply from: rootProject.file('uikit/uikit.gradle')


}


这样就实现了宿主工程 UIKit 代码单独运行的效果了


组件架构




组件可以分为 2 类:工具型、业务类型,2 个类型的组件迭代思路差异非常的大,工具型组件,只要单点做到极致就 ok 了,整体比较简单,复用性也比较强,而业务型组件就会稍显复杂,既要考虑复用性,也要考虑可扩展性,下面分别介绍这 2 个类型组件的实现思路

工具型

工具型组件迭代的思路就是不断的完善基础能力,尽可能的功能全面,在已有的能力上不断的支持新的功能,比较重要的就是兼容已有 api,比较代表性的组件有 FlatButton、RoundView、StatusBar,可以参考下 FlatButton&RoundView 迭代历程:


业务型

如何做好一个业务组件呢,实现可以是具象的,也可以是抽象的,好的组件设计应该是 2 者兼备,最底层的实现应该是足够抽象,而上层实现又应该是具象的,所以需要带着容器化的思路来实现,那么怎么个思路呢,如下图:



组件实现




下面以 FlatButton 为例介绍组件实现方式,其它组件实现思路类似。在实现前,我们先看下视觉稿



按钮样式特别多,实现方式也可以有很多种,现有工程也给出了实现方案,具体如下:


第一步:分别定义 noraml 下的 shape 和 pressed 的 shape,如果 enable = false,还得再定义一个 dissable 的 shape


normal (ui_standard_bg_btn_corner_28_ripple)


<?xml version="1.0" encoding="utf-8"?>


<ripple xmlns:android="http://schemas.android.com/apk/res/android"


android:color="@color/button_pressed_cover">


<item


android:drawable="@drawable/ui_standard_bg_btn_corner_28_enable">


</item>


</ripple>


pressed(ui_standard_bg_btn_corner_28_disable)


<?xml version="1.0" encoding="utf-8"?>


<shape xmlns:android="http://schemas.android.com/apk/res/android"


android:shape="rectangle">


<gradient


android:angle="0"


android:endColor="@color/button_disable_end"


android:startColor="@color/button_disable_start"


android:useLevel="false"


android:type="linear" />


<corners android:radius="28dp" />


</shape>


第二步:定义 selector


selector(ui_standard_bg_btn_corner_28)


<?xml version="1.0" encoding="utf-8"?>


<selector xmlns:android="http://schemas.android.com/apk/res/android">


<item android:state_enabled="true" android:drawable="@drawable/ui_standard_bg_btn_corner_28_ripple" />


<item android:state_enabled="false" android:drawable="@drawable/ui_standard_bg_btn_corner_28_disable" />


</selector>


第三步:使用


<TextView


...


android:background="@drawable/ui_standard_bg_btn_corner_28"


android:textColor="@color/white"/>


这样按钮的背景按压就实现了,如果在此基础上,文字也需要按压态,那么就重复上面的步骤,对颜色再创建一个选择器,当实现完上面 UI 定义的样式后,工程中的画风如下:



我是谁,我在哪里,这该怎么玩,长得都差不多,基本没有开发体验,复用性、扩展性都非常的差,如果来个 UI 大改版,又得从头再来一次。那怎么解决上面的问题呢,答案是定义按钮通用能力,业务上层再实现,按这个思路做,需要删除上面所有 shape、selector,然后自定义控件,我们都知道,上面定义的 shape、selector xml 文件,android 系统最终都是会解析生成对应的对象,所以我们借鉴一下系统代码,实现起来就 so easy


看下这个 shape xml


<shape xmlns:android="http://schemas.android.com/apk/res/android"


android:shape="rectangle">


<gradient


android:angle="0"


android:endColor="@color/button_disable_end"


android:startColor="@color/button_disable_start"


android:useLevel="false"


android:type="linear" />


<corners android:radius="28dp" />


</shape>


解析后的对象为 GradientDrawable


public void setOrientation(Orientation orientation)


public void setColors(@Nullable @ColorInt int[] colors)


public void setCornerRadii(@Nullable float[] radii)


public void setStroke(int width, @ColorInt int color)


...


也就是说,xml 中定义的属性,代码中都可以实现,除了 GradientDrawable,还会用到 RippleDrawable 实现水波纹,同理文字颜色选择器代码中对应的为 ColorStateList,有了上面铺垫,具体实现如下:

第一步:定义自定义属性

<declare-styleable name="FlatButton">


<attr name="fb_colorNormal" format="color" />


<attr name="fb_colorPressed" format="color" />


<attr name="fb_colorDisable" format="color" />


<attr name="fb_colorNormalStart" format="color" />


<attr name="fb_colorNormalEnd" format="color" />


<attr name="fb_colorPressedStart" format="color" />


<attr name="fb_colorPressedEnd" format="color" />


<attr name="fb_colorDisableStart" format="color" />


<attr name="fb_colorDisableEnd" format="color" />


<attr name="fb_gradientOrientation">


<enum name="left_right" value="0" />


<enum name="right_left" value="1" />


<enum name="top_bottom" value="2" />


<enum name="bottom_top" value="3" />


<enum name="tr_bl" value="4" />


<enum name="bl_tr" value="5" />


<enum name="br_tl" value="6" />


<enum name="tl_br" value="7" />


</attr>


<attr name="fb_colorNormalText" format="color" />


<attr name="fb_colorPressedText" format="color" />


<attr name="fb_colorDisableText" format="color" />


<attr name="fb_strokeColor" format="color" />


<attr name="fb_strokePressColor" format="color" />


<attr name="fb_strokeDisableColor" format="color" />


<attr name="fb_strokeWidth" format="dimension" />


<attr name="fb_isRippleEnable" format="boolean" />


<attr name="fb_colorRippleNormal" format="color" />


<attr name="fb_colorRipplePressed" format="color" />


用户头像

Android架构

关注

还未添加个人签名 2021.10.31 加入

还未添加个人简介

评论

发布
暂无评论
UI组件化--干掉shape终极一战,android模块化框架