美文网首页
Silhouette——更方便的Shape/Selector实现

Silhouette——更方便的Shape/Selector实现

作者: BlueSocks | 来源:发表于2022-03-16 15:42 被阅读0次
    在这里插入图片描述

    shape和selector是Android UI设计中经常用到的,比如我们要自定义一个圆角Button,点击Button有些效果的变化,就要用到shape和selector。可以这样说,shape和selector在美化控件中的作用是至关重要的。奈何编写各种shape/selector等文件会有各种弊端,这个时候我不得不推荐小而实用的库:Silhouette给大家。

    Silhouette是什么?

    Silhouette意为“剪影”,取名并没有特别的含义,只是单纯地觉得意境较美。

    Silhouette是一系列基于GradientDrawable及StateListDrawable封装的组件集合,主要用于实现在Android Layout XML中直接支持Shape/Selector等功能。

    我们都知道在Android开发中,不同的TextView及Button各种样式(形状、背景色、描边、圆角、渐变等)的传统实现方式是在drawable文件夹中编写各种shape/selector等文件,这种方式至少会存在以下几种弊端:

    1. shape/selector文件过多,项目体积增大;

    2. shape/selector文件命名困难,命名规范时往往会存在功能重复的文件;

    3. 功能存在局限性:例如gradient渐变色。传统shape方式只支持三种颜色过渡(startColor/centerColor/endColor),如果设计稿存在四种以上颜色渐变,shape gradient无能为力。再比如TextView在常态和按下态需要同时改变背景色及文字颜色时,传统方式只能在代码中动态设置等。

    4. 开发效率低;

    5. 难以维护等。

    综上所述,我们迫切需要一个库来解决以上问题,Silhouette正具备这些能力。接下来,我们来具体看看Silhouette能做什么吧。

    Silhouette能做什么?

    上面说到Silhouette是一系列组件集合,具体包含以下组件:

    1. SleTextButton

    基于AppCompatTextView封装;

    具备定义各种样式(形状、背景色、描边、圆角、渐变等)的能力 ;

    具备不同状态(常态、按下态、不可点击态)下文字颜色指定等。

    2. SleImageButton

    基于ShapeableImageView封装;

    通过指定sle_ib_type属性使ImageView支持按下态遮罩层、透明度改变、自定义图片,同时支持CheckBox功能;

    通过指定sle_ib_style属性使ImageView支持Normal、圆角、圆形等形状。

    3. SleConstraintLayout

    基于ConstraintLayout封装;

    具备定义各种样式(形状、背景色、描边、圆角、渐变等)的功能。

    4. SleRelativeLayout

    基于RelativeLayout封装;

    具备定义各种样式(形状、背景色、描边、圆角、渐变等)的功能。

    5. SleLinearLayout

    基于LinearLayout封装;

    具备定义各种样式(形状、背景色、描边、圆角、渐变等)的功能。

    6. SleFrameLayout

    基于FrameLayout封装;

    具备定义各种样式(形状、背景色、描边、圆角、渐变等)的功能。

    设计、封装思路及原理

    1. 项目结构

    com.freddy.silhouette

    由此可见,项目结构非常简单,所以Silhouette也是一个比较轻量级的库。

    • button

    • layout

    • config(配置相关,存放全局注解及公共常量、默认值等)

    • ext(kotlin扩展相关,可选择用或不用)

    utils(工具类相关,可选择用或不用)

    widget(控件相关)

    2. 封装思路及原理

    由于该库非常简单,实际上就是根据Shape/Selector进行自定义属性,从而利用GradientDrawable及StateListDrawable提供的API进行封装,不存在什么难度,在此就不展开讲了。

    下面贴一下代码片段,基本上几个组件的实现原理都大同小异,都是利用GradientDrawable及StateListDrawable实现组件的Shape及Selector功能:

    private fun init() {
        val normalDrawable =
            getDrawable(normalBackgroundColor, normalStrokeColor, normalGradientColors)
        var pressedDrawable: GradientDrawable? = null
        var disabledDrawable: GradientDrawable? = null
        var selectedDrawable: GradientDrawable? = null
        when (type) {
            TYPE_MASK -> {
                pressedDrawable = getDrawable(
                    normalBackgroundColor,
                    normalStrokeColor,
                    normalGradientColors
                ).apply {
                    colorFilter =
                        PorterDuffColorFilter(maskBackgroundColor, PorterDuff.Mode.SRC_ATOP)
                }
                disabledDrawable =
                    getDrawable(disabledBackgroundColor, disabledBackgroundColor)
            }
            TYPE_SELECTOR -> {
                pressedDrawable =
                    getDrawable(pressedBackgroundColor, pressedStrokeColor, pressedGradientColors)
                disabledDrawable = getDrawable(
                    disabledBackgroundColor,
                    disabledStrokeColor,
                    disabledGradientColors
                )
            }
        }
        selectedDrawable = getDrawable(
            selectedBackgroundColor,
            selectedStrokeColor,
            selectedGradientColors
        )
        setTextColor(normalTextColor)
        background = StateListDrawable().apply {
            if (type != TYPE_NONE) {
                addState(intArrayOf(android.R.attr.state_pressed), pressedDrawable)
            }
            addState(intArrayOf(-android.R.attr.state_enabled), disabledDrawable)
            addState(intArrayOf(android.R.attr.state_selected), selectedDrawable)
            addState(intArrayOf(), normalDrawable)
        }
    
        setOnTouchListener(this)
    }
    
    private fun getDrawable(
        backgroundColor: Int,
        strokeColor: Int,
        gradientColors: IntArray? = null
    ): GradientDrawable {
        // 背景色相关
        val drawable = GradientDrawable()
        setupColor(drawable, backgroundColor)
    
        // 形状相关
        (drawable.mutate() as GradientDrawable).shape = shape
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            drawable.innerRadius = innerRadius
            if (innerRadiusRatio > 0f) {
                drawable.innerRadiusRatio = innerRadiusRatio
            }
            drawable.thickness = thickness
            if (thicknessRatio > 0f) {
                drawable.thicknessRatio = thicknessRatio
            }
        }
    
        // 描边相关
        if (strokeColor != 0) {
            (drawable.mutate() as GradientDrawable).setStroke(
                strokeWidth,
                strokeColor,
                dashWidth,
                dashGap
            )
        }
    
        // 圆角相关
        setupCornersRadius(
            drawable,
            cornersRadius,
            cornersTopLeftRadius,
            cornersTopRightRadius,
            cornersBottomRightRadius,
            cornersBottomLeftRadius
        )
    
        // 渐变相关
        (drawable.mutate() as GradientDrawable).gradientType = gradientType
        if (gradientCenterX != 0.0f || gradientCenterY != 0.0f) {
            (drawable.mutate() as GradientDrawable).setGradientCenter(
                gradientCenterX,
                gradientCenterY
            )
        }
        gradientColors?.let { colors ->
            (drawable.mutate() as GradientDrawable).colors = colors
        }
        var orientation: GradientDrawable.Orientation? = null
        when (gradientOrientation) {
            GRADIENT_ORIENTATION_TOP_BOTTOM -> {
                orientation = GradientDrawable.Orientation.TOP_BOTTOM
            }
            GRADIENT_ORIENTATION_TR_BL -> {
                orientation = GradientDrawable.Orientation.TR_BL
            }
            GRADIENT_ORIENTATION_RIGHT_LEFT -> {
                orientation = GradientDrawable.Orientation.RIGHT_LEFT
            }
            GRADIENT_ORIENTATION_BR_TL -> {
                orientation = GradientDrawable.Orientation.BR_TL
            }
            GRADIENT_ORIENTATION_BOTTOM_TOP -> {
                orientation = GradientDrawable.Orientation.BOTTOM_TOP
            }
            GRADIENT_ORIENTATION_BL_TR -> {
                orientation = GradientDrawable.Orientation.BL_TR
            }
            GRADIENT_ORIENTATION_LEFT_RIGHT -> {
                orientation = GradientDrawable.Orientation.LEFT_RIGHT
            }
            GRADIENT_ORIENTATION_TL_BR -> {
                drawable.orientation = GradientDrawable.Orientation.TL_BR
            }
        }
        orientation?.apply {
            (drawable.mutate() as GradientDrawable).orientation = this
        }
        return drawable
    }
    

    感兴趣的同学可以到官方文档了解GradientDrawable及StateListDrawable的原理。

    自定义属性列表

    自定义属性分为通用属性和特有属性。

    1. 通用属性
    类型

    在这里插入图片描述

    形状相关

    在这里插入图片描述

    背景色相关

    在这里插入图片描述

    描边相关

    在这里插入图片描述

    圆角相关

    在这里插入图片描述

    渐变相关

    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

    其他

    在这里插入图片描述
    2. 特有属性
    SleConstraintLayout/SleRelativeLayout/SleFrameLayout/SleLinearLayout

    在这里插入图片描述

    SleTextButton

    在这里插入图片描述

    SleImageButton

    在这里插入图片描述 在这里插入图片描述

    使用方式

    1. 添加依赖

    implementation "io.github.freddychen:silhouette:$lastest_version"
    

    Note:最新版本可在maven central silhouette中找到。

    2. 使用

    由于自定义属性太多,在此就不一一列举了。下面给出几种常见的场景示例,大家可以根据自定义属性表自行编写:

    常态


    在这里插入图片描述

    按下态


    image.png

    以上布局代码为:

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@color/black"
        android:gravity="center_horizontal"
        android:orientation="vertical">
    
        <com.freddy.silhouette.widget.button.SleTextButton
            android:id="@+id/stb_1"
            android:layout_width="match_parent"
            android:layout_height="54dp"
            android:layout_marginHorizontal="48dp"
            android:layout_marginTop="14dp"
            android:gravity="center"
            android:text="SleTextButton1"
            android:textSize="20sp"
            app:sle_cornersRadius="28dp"
            app:sle_normalBackgroundColor="#f88789"
            app:sle_normalTextColor="@color/white"
            app:sle_type="mask" />
    
        <com.freddy.silhouette.widget.button.SleTextButton
            android:id="@+id/stb_2"
            android:layout_width="match_parent"
            android:layout_height="54dp"
            android:layout_marginHorizontal="48dp"
            android:layout_marginTop="14dp"
            android:gravity="center"
            android:text="SleTextButton2"
            android:textSize="20sp"
            app:sle_cornersBottomRightRadius="24dp"
            app:sle_cornersTopLeftRadius="14dp"
            app:sle_normalBackgroundColor="#338899"
            app:sle_normalTextColor="@color/white"
            app:sle_pressedBackgroundColor="#aeeacd"
            app:sle_type="selector" />
    
        <com.freddy.silhouette.widget.button.SleTextButton
            android:id="@+id/stb_3"
            android:layout_width="120dp"
            android:layout_height="120dp"
            android:layout_marginHorizontal="48dp"
            android:layout_marginTop="14dp"
            android:enabled="false"
            android:gravity="center"
            android:text="SleTextButton2"
            android:textSize="14sp"
            app:sle_cornersBottomRightRadius="24dp"
            app:sle_cornersTopLeftRadius="14dp"
            app:sle_normalBackgroundColor="#cc688e"
            app:sle_normalTextColor="@color/white"
            app:sle_pressedBackgroundColor="#34eeac"
            app:sle_shape="oval"
            app:sle_type="selector" />
    
        <com.freddy.silhouette.widget.button.SleImageButton
            android:id="@+id/sib_1"
            android:layout_width="84dp"
            android:layout_height="84dp"
            android:layout_marginTop="14dp"
            app:sle_ib_type="mask"
            app:sle_normalResId="@drawable/ic_launcher_background" />
    
        <com.freddy.silhouette.widget.button.SleImageButton
            android:id="@+id/sib_2"
            android:layout_width="128dp"
            android:layout_height="128dp"
            android:layout_marginTop="14dp"
            app:sle_ib_type="alpha"
            app:sle_normalResId="@drawable/ic_launcher_background" />
    
        <com.freddy.silhouette.widget.button.SleImageButton
            android:id="@+id/sib_3"
            android:layout_width="72dp"
            android:layout_height="72dp"
            android:layout_marginTop="14dp"
            app:sle_ib_type="selector"
            app:sle_normalResId="@mipmap/ic_launcher"
            app:sle_pressedResId="@drawable/ic_launcher_foreground" />
    
        <com.freddy.silhouette.widget.layout.SleConstraintLayout
            android:id="@+id/scl_1"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginHorizontal="48dp"
            android:layout_marginTop="14dp"
            android:paddingHorizontal="14dp"
            android:paddingVertical="8dp"
            app:sle_cornersRadius="10dp"
            app:sle_interceptType="intercept_super"
            app:sle_normalBackgroundColor="@color/white">
    
            <ImageView
                android:layout_width="72dp"
                android:layout_height="48dp"
                android:scaleType="centerCrop"
                android:src="@mipmap/ic_launcher_round" />
    
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="UserName"
                android:textColor="@color/black"
                android:textSize="18sp"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toTopOf="parent" />
        </com.freddy.silhouette.widget.layout.SleConstraintLayout>
    
        <com.freddy.silhouette.widget.layout.SleLinearLayout
            android:id="@+id/sll_1"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginHorizontal="48dp"
            android:layout_marginTop="14dp"
            android:gravity="center_vertical"
            android:paddingHorizontal="14dp"
            app:sle_type="selector"
            android:paddingVertical="8dp"
            app:sle_cornersTopRightRadius="24dp"
            app:sle_cornersBottomRightRadius="18dp"
            app:sle_interceptType="intercept_true"
            app:sle_pressedBackgroundColor="#fe9e87"
            app:sle_normalBackgroundColor="#aee949">
    
            <ImageView
                android:layout_width="72dp"
                android:layout_height="48dp"
                android:scaleType="centerCrop"
                android:src="@mipmap/ic_launcher_round" />
    
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginStart="14dp"
                android:text="UserName"
                android:textColor="@color/black"
                android:textSize="18sp"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toTopOf="parent" />
        </com.freddy.silhouette.widget.layout.SleLinearLayout>
    </LinearLayout>
    

    Note:需要给组件设置setOnClickListener才能看到效果。
    至于更多的功能,就让大家去试试吧,篇幅有限,就不一一列举了。

    写在最后

    终于写完了,Shape/Selector在每个项目中基本都会用到,而且频率还不算低。

    Silhouette原理虽然简单,但确实能解决很多问题,这些都是平时开发中的积累,希望对大家能有所帮助。

    转载自:FreddyChen

    相关文章

      网友评论

          本文标题:Silhouette——更方便的Shape/Selector实现

          本文链接:https://www.haomeiwen.com/subject/orupdrtx.html