热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

UI组件化–干掉shape终极一战

好文推荐 作者:大力智能技术 转载地址:https://juejin.cn/post/6956759270478053407 背景 UI组件化对项目有正向收益,不仅能提效,还能保证高度的视觉还

好文推荐
作者:大力智能技术
转载地址:https://juejin.cn/post/6956759270478053407

背景

UI组件化对项目有正向收益,不仅能提效,还能保证高度的视觉还原度,减少和UI设计师沟通成本,所以也得到了大家的认可。所以每个项目都会启动UI组件化建设,但是UI视图是和项目强相关的,项目间无法复用,导致大家疲于实现,重复造轮子,拖延下班时间,那么基于上面的背景,有没有更好的解决方案呢,答案是有的,下面介绍一下UI组件化在项目中的实施经验,下面分为目标工程架构组件架构组件实现来展开。

目标

对现有UI组件化进行容器化抽象,底层UI组件提供最大功能集合,完全解耦业务逻辑,业务方根据自己需求,基于基础组件开发,通过属性配置或者组合的方式达到复杂的效果,所以只要底层组件抽象的足够好、能力足够全,就能大大的提高开发效率,后期适配也不会涉及核心逻辑修改,一定程度的保证了功能的稳定性

工程架构

module划分

所有的ui组件统一收敛到uikit下,其下面moudle划分以是否非常通用为依据,如果非业务属性,并且特别通用的模块组件,抽取单独module,方便解耦和复用,如果不是则统一放在同一个module下,这样uikit模块划分如下:

  • app

空壳工程,可以单独运行

  • demo

对所有的组件提供demo,里面的功能也可以在调试面板中打开

  • uikit

依赖widget****和****module****,业务使用ui组件直接依赖****uikit****即可

  • widget

    • DivideLine
  • XRadioGroup

  • LoadingView

  • ShimmerLayout

  • uikit-module

    • flatButton
  • roundView

  • load

  • dialog

  • imageSelect

  • toast

工程分层

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

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

  • 组合控件:会依赖基础控件,比如Dialog、ImageSelector,这些控件UI会比较复杂,所以会用到FlatButton、ShimmerLayout等基础组件来提高开发效率

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

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

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

架构分层如下图:

UI组件化--干掉shape终极一战

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

UI组件化--干掉shape终极一战

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 $name in path $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迭代历程:

UI组件化--干掉shape终极一战

业务型

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

UI组件化--干掉shape终极一战

组件实现

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

UI组件化--干掉shape终极一战

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

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

normal (ui_standard_bg_btn_corner_28_ripple)

pressed(ui_standard_bg_btn_corner_28_disable)

第二步:定义selector

selector(ui_standard_bg_btn_corner_28)

第三步:使用

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

UI组件化--干掉shape终极一战

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

看下这个shape xml

解析后的对象为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,有了上面铺垫,具体实现如下:

第一步:定义自定义属性
第二步:核心实现逻辑
private fun setBackgroundCompat() {
    val stateListDrawable = createStateListDrawable()
    val pL = paddingLeft
    val pT = paddingTop
    val pR = paddingRight
    val pB = paddingBottom
    background = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && isRippleEnable) {
        val rippleDrawable = RippleDrawable(createRippleColorStateList(), stateListDrawable, null)
        rippleDrawable
    } else {
        stateListDrawable
    }
    setPadding(pL, pT, pR, pB)
}

private fun createStateListDrawable(): StateListDrawable {
    var normalDrawable = StateListDrawable()
    normalDrawable.addState(
            intArrayOf(android.R.attr.state_pressed),
            createPressedDrawable()
    )
    normalDrawable.addState(
            intArrayOf(android.R.attr.state_focused),
            createPressedDrawable()
    )
    normalDrawable.addState(
            intArrayOf(-android.R.attr.state_enabled),
            createDisableDrawable()
    )
    normalDrawable.addState(
            intArrayOf(android.R.attr.state_selected),
            createPressedDrawable()
    )
    normalDrawable.addState(intArrayOf(), createNormalDrawable())
    return normalDrawable
}

private fun createRippleColorStateList(): ColorStateList {
    val stateList = arrayOf(intArrayOf(android.R.attr.state_pressed), intArrayOf(android.R.attr.state_focused), intArrayOf(android.R.attr.state_activated), intArrayOf())
    val normalColor = backgroundStyle.getColorRippleNormalFallback()
    val pressedColor = backgroundStyle.getColorRipplePressedFallback()
    val stateColorList = intArrayOf(
            pressedColor,
            pressedColor,
            pressedColor,
            normalColor
    )
    return ColorStateList(stateList, stateColorList)
}
第三步:UI组件实现

xml中使用

代码中使用

fb_radius_in_code.setBackgroundStyle {
    this.colorNormal = resources.getColor(R.color.uikit_color_FF4081)
    this.colorPressed = resources.getColor(R.color.uikit_color_9C27B0)
    this.colorRippleNormal = resources.getColor(R.color.uikit_color_FF4081)
    this.colorRipplePressed = resources.getColor(R.color.uikit_color_9C27B0)
}.setRadiusStyle {
    this.radiusTL = dp2px(24F)
    this.radius_BR = dp2px(24F)
}

到这里,底层Button能力定义完成,接下来就是组件化实现了,具体实现方式如下:

无法复制加载中的内容

项目中的按钮UI按照UI组件要求,可以基于FlatButton来实现,配置好给种类型的属性,按钮名字可以和设计对齐,到这里就基本完成了

第四步:业务使用

一级按钮、二级按钮、三级按钮的实现可以通过继承FlatButton,设置默认样式,使用的时候就不需要再在xml中定义任何属性,只需记住组件名字,依赖即可,做到真正的开箱即用

举一个例子,定义一个线框button

class StrokeButton : FlatButton {
    constructor(context: Context) : this(context, null)
    constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
        config(context, attrs)
    }

    private fun config(context: Context, attrs: AttributeSet?){
        .setBackgroundStyle {
            this.colorNormal = resources.getColor(R.color.uikit_color_FF4081)
            this.colorPressed = resources.getColor(R.color.uikit_color_9C27B0)
            this.colorRippleNormal = resources.getColor(R.color.uikit_color_FF4081)
            this.colorRipplePressed = resources.getColor(R.color.uikit_color_9C27B0)
        }.setRadiusStyle {
            this.radiusTL = dp2px(28F)
            this.radius_BR = dp2px(28F)
        }
    }
    private fun dp2px(dp: Float): Float {
        return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, resources.displayMetrics)
    }
}

业务使用

最后

大家如果还想了解更多Android 相关的更多知识点,可以点进我的GitHub项目中:https://github.com/733gh/GH-Android-Review-master自行查看,里面记录了许多的Android 知识点。最后还请大家点点赞支持下!!!

UI组件化--干掉shape终极一战

推荐阅读
  • 这是原文链接:sendingformdata许多情况下,我们使用表单发送数据到服务器。服务器处理数据并返回响应给用户。这看起来很简单,但是 ... [详细]
  • 本文介绍了Web学习历程记录中关于Tomcat的基本概念和配置。首先解释了Web静态Web资源和动态Web资源的概念,以及C/S架构和B/S架构的区别。然后介绍了常见的Web服务器,包括Weblogic、WebSphere和Tomcat。接着详细讲解了Tomcat的虚拟主机、web应用和虚拟路径映射的概念和配置过程。最后简要介绍了http协议的作用。本文内容详实,适合初学者了解Tomcat的基础知识。 ... [详细]
  • VS2010MFC(对话框:为对话框添加控件)
    转自:http:www.jizhuomi.comsoftware151.html上一讲创建了一个名为“Addition”的工程,目的是生成一个实现加法运 ... [详细]
  • 基于事件驱动的并发编程及其消息通信机制的同步与异步、阻塞与非阻塞、IO模型的分类
    本文介绍了基于事件驱动的并发编程中的消息通信机制,包括同步和异步的概念及其区别,阻塞和非阻塞的状态,以及IO模型的分类。同步阻塞IO、同步非阻塞IO、异步阻塞IO和异步非阻塞IO等不同的IO模型被详细解释。这些概念和模型对于理解并发编程中的消息通信和IO操作具有重要意义。 ... [详细]
  • 拥抱Android Design Support Library新变化(导航视图、悬浮ActionBar)
    转载请注明明桑AndroidAndroid5.0Loollipop作为Android最重要的版本之一,为我们带来了全新的界面风格和设计语言。看起来很受欢迎࿰ ... [详细]
  • 有没有一种方法可以在不继承UIAlertController的子类或不涉及UIAlertActions的情况下 ... [详细]
  • 本文介绍了Android 7的学习笔记总结,包括最新的移动架构视频、大厂安卓面试真题和项目实战源码讲义。同时还分享了开源的完整内容,并提醒读者在使用FileProvider适配时要注意不同模块的AndroidManfiest.xml中配置的xml文件名必须不同,否则会出现问题。 ... [详细]
  • 单点登录原理及实现方案详解
    本文详细介绍了单点登录的原理及实现方案,其中包括共享Session的方式,以及基于Redis的Session共享方案。同时,还分享了作者在应用环境中所遇到的问题和经验,希望对读者有所帮助。 ... [详细]
  • Android工程师面试准备及设计模式使用场景
    本文介绍了Android工程师面试准备的经验,包括面试流程和重点准备内容。同时,还介绍了建造者模式的使用场景,以及在Android开发中的具体应用。 ... [详细]
  • 本文介绍了H5游戏性能优化和调试技巧,包括从问题表象出发进行优化、排除外部问题导致的卡顿、帧率设定、减少drawcall的方法、UI优化和图集渲染等八个理念。对于游戏程序员来说,解决游戏性能问题是一个关键的任务,本文提供了一些有用的参考价值。摘要长度为183字。 ... [详细]
  • 本文介绍了Python函数的定义与调用的方法,以及函数的作用,包括增强代码的可读性和重用性。文章详细解释了函数的定义与调用的语法和规则,以及函数的参数和返回值的用法。同时,还介绍了函数返回值的多种情况和多个值的返回方式。通过学习本文,读者可以更好地理解和使用Python函数,提高代码的可读性和重用性。 ... [详细]
  • 本文介绍了Cocos2dx学习笔记中的更新函数scheduleUpdate、进度计时器CCProgressTo和滚动视图CCScrollView的用法。详细介绍了scheduleUpdate函数的作用和使用方法,以及schedule函数的区别。同时,还提供了相关的代码示例。 ... [详细]
  • 带添加按钮的GridView,item的删除事件
    先上图片效果;gridView无数据时显示添加按钮,有数据时,第一格显示添加按钮,后面显示数据:布局文件:addr_manage.xml<?xmlve ... [详细]
  • 抽空写了一个ICON图标的转换程序
    抽空写了一个ICON图标的转换程序,支持png\jpe\bmp格式到ico的转换。具体的程序就在下面,如果看的人多,过两天再把思路写一下。 ... [详细]
  • 本篇文章笔者在上海吃饭的时候突然想到的这段时间就有想写几篇关于返回系统的笔记,所以回家到之后就奋笔疾书的写出来发布了事先在网上找了很多方法,发现有 ... [详细]
author-avatar
麦尔小哈PICA
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有