减少drawable.xml并对其进行管理

我们开发Android的时候经常会碰到给按钮或者文本设置背景,圆角,填充颜色,描边,按压状态这些样式,首先想到的就是用shape,selector生成一个xml文件然后通过drawable引用,但是随着项目维护迭代的时间越长,你会发现shape,selector文件的数量会疯狂增加,可能有时候几个人同事开发也会创建一样的样式,很难进行管理,今天我们就通过自定义View来减少shape这歌文件的数量,只要通过设置属性就可以实现想要的效果。

通用shape样式按钮CommonShapeButton:

package com.blue.view

import android.app.Activity
import android.content.Context
import android.content.ContextWrapper
import android.content.res.ColorStateList
import android.graphics.Canvas
import android.graphics.drawable.Drawable
import android.graphics.drawable.GradientDrawable
import android.graphics.drawable.RippleDrawable
import android.graphics.drawable.StateListDrawable
import android.os.Build
import android.support.v7.widget.AppCompatButton
import android.util.AttributeSet
import android.view.Gravity
import com.blue.R

/**
 * 通用shape样式按钮
 */
class CommonShapeButton @JvmOverloads constructor(
        context: Context,
        attrs: AttributeSet? = null,
        defStyleAttr: Int = 0
) : AppCompatButton(context, attrs, defStyleAttr) {

    private companion object {
        val TOP_LEFT = 1
        val TOP_RIGHT = 2
        val BOTTOM_RIGHT = 4
        val BOTTOM_LEFT = 8
    }

    /**
     * shape模式
     * 矩形(rectangle)、椭圆形(oval)、线形(line)、环形(ring)
     */
    private var mShapeMode = 0

    /**
     * 填充颜色
     */
    private var mFillColor = 0

    /**
     * 按压颜色
     */
    private var mPressedColor = 0

    /**
     * 描边颜色
     */
    private var mStrokeColor = 0

    /**
     * 描边宽度
     */
    private var mStrokeWidth = 0

    /**
     * 圆角半径
     */
    private var mCornerRadius = 0
    /**
     * 圆角位置
     */
    private var mCornerPosition = 0

    /**
     * 点击动效
     */
    private var mActiveEnable = false

    /**
     * 起始颜色
     */
    private var mStartColor = 0

    /**
     * 结束颜色
     */
    private var mEndColor = 0

    /**
     * 渐变方向
     * 0-GradientDrawable.Orientation.TOP_BOTTOM
     * 1-GradientDrawable.Orientation.LEFT_RIGHT
     */
    private var mOrientation = 0

    /**
     * drawable位置
     * -1-null、0-left、1-top、2-right、3-bottom
     */
    private var mDrawablePosition = -1

    /**
     * 普通shape样式
     */
    private val normalGradientDrawable: GradientDrawable by lazy { GradientDrawable() }
    /**
     * 按压shape样式
     */
    private val pressedGradientDrawable: GradientDrawable by lazy { GradientDrawable() }
    /**
     * shape样式集合
     */
    private val stateListDrawable: StateListDrawable by lazy { StateListDrawable() }
    // button内容总宽度
    private var contentWidth = 0f
    // button内容总高度
    private var contentHeight = 0f

    init {
        context.obtainStyledAttributes(attrs, R.styleable.CommonShapeButton).apply {
            mShapeMode = getInt(R.styleable.CommonShapeButton_csb_shapeMode, 0)
            mFillColor = getColor(R.styleable.CommonShapeButton_csb_fillColor, 0xFFFFFFFF.toInt())
            mPressedColor = getColor(R.styleable.CommonShapeButton_csb_pressedColor, 0xFF666666.toInt())
            mStrokeColor = getColor(R.styleable.CommonShapeButton_csb_strokeColor, 0)
            mStrokeWidth = getDimensionPixelSize(R.styleable.CommonShapeButton_csb_strokeWidth, 0)
            mCornerRadius = getDimensionPixelSize(R.styleable.CommonShapeButton_csb_cornerRadius, 0)
            mCornerPosition = getInt(R.styleable.CommonShapeButton_csb_cornerPosition, -1)
            mActiveEnable = getBoolean(R.styleable.CommonShapeButton_csb_activeEnable, false)
            mDrawablePosition = getInt(R.styleable.CommonShapeButton_csb_drawablePosition, -1)
            mStartColor = getColor(R.styleable.CommonShapeButton_csb_startColor, 0xFFFFFFFF.toInt())
            mEndColor = getColor(R.styleable.CommonShapeButton_csb_endColor, 0xFFFFFFFF.toInt())
            mOrientation = getColor(R.styleable.CommonShapeButton_csb_orientation, 0)
            recycle()
        }
    }

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
        // 初始化normal状态
        with(normalGradientDrawable) {
            // 渐变色
            if (mStartColor != 0xFFFFFFFF.toInt() && mEndColor != 0xFFFFFFFF.toInt()) {
                colors = intArrayOf(mStartColor, mEndColor)
                when (mOrientation) {
                    0 -> orientation = GradientDrawable.Orientation.TOP_BOTTOM
                    1 -> orientation = GradientDrawable.Orientation.LEFT_RIGHT
                }
            }
            // 填充色
            else {
                setColor(mFillColor)
            }
            when (mShapeMode) {
                0 -> shape = GradientDrawable.RECTANGLE
                1 -> shape = GradientDrawable.OVAL
                2 -> shape = GradientDrawable.LINE
                3 -> shape = GradientDrawable.RING
            }
            // 统一设置圆角半径
            if (mCornerPosition == -1) {
                cornerRadius = mCornerRadius.toFloat()
            }
            // 根据圆角位置设置圆角半径
            else {
                cornerRadii = getCornerRadiusByPosition()
            }
            // 默认的透明边框不绘制,否则会导致没有阴影
            if (mStrokeColor != 0) {
                setStroke(mStrokeWidth, mStrokeColor)
            }
        }

        // 是否开启点击动效
        background = if (mActiveEnable) {
            // 5.0以上水波纹效果
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                RippleDrawable(ColorStateList.valueOf(mPressedColor), normalGradientDrawable, null)
            }
            // 5.0以下变色效果
            else {
                // 初始化pressed状态
                with(pressedGradientDrawable) {
                    setColor(mPressedColor)
                    when (mShapeMode) {
                        0 -> shape = GradientDrawable.RECTANGLE
                        1 -> shape = GradientDrawable.OVAL
                        2 -> shape = GradientDrawable.LINE
                        3 -> shape = GradientDrawable.RING
                    }
                    cornerRadius = mCornerRadius.toFloat()
                    setStroke(mStrokeWidth, mStrokeColor)
                }

                // 注意此处的add顺序,normal必须在最后一个,否则其他状态无效
                // 设置pressed状态
                stateListDrawable.apply {
                    addState(intArrayOf(android.R.attr.state_pressed), pressedGradientDrawable)
                    // 设置normal状态
                    addState(intArrayOf(), normalGradientDrawable)
                }
            }
        } else {
            normalGradientDrawable
        }
    }

    override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
        super.onLayout(changed, left, top, right, bottom)
        // 如果xml中配置了drawable则设置padding让文字移动到边缘与drawable靠在一起
        // button中配置的drawable默认贴着边缘
        if (mDrawablePosition > -1) {
            compoundDrawables?.let {
                val drawable: Drawable? = compoundDrawables[mDrawablePosition]
                drawable?.let {
                    // 图片间距
                    val drawablePadding = compoundDrawablePadding
                    when (mDrawablePosition) {
                    // 左右drawable
                        0, 2 -> {
                            // 图片宽度
                            val drawableWidth = it.intrinsicWidth
                            // 获取文字宽度
                            val textWidth = paint.measureText(text.toString())
                            // 内容总宽度
                            contentWidth = textWidth + drawableWidth + drawablePadding
                            val rightPadding = (width - contentWidth).toInt()
                            // 图片和文字全部靠在左侧
                            setPadding(0, 0, rightPadding, 0)
                        }
                    // 上下drawable
                        1, 3 -> {
                            // 图片高度
                            val drawableHeight = it.intrinsicHeight
                            // 获取文字高度
                            val fm = paint.fontMetrics
                            // 单行高度
                            val singeLineHeight = Math.ceil(fm.descent.toDouble() - fm.ascent.toDouble()).toFloat()
                            // 总的行间距
                            val totalLineSpaceHeight = (lineCount - 1) * lineSpacingExtra
                            val textHeight = singeLineHeight * lineCount + totalLineSpaceHeight
                            // 内容总高度
                            contentHeight = textHeight + drawableHeight + drawablePadding
                            // 图片和文字全部靠在上侧
                            val bottomPadding = (height - contentHeight).toInt()
                            setPadding(0, 0, 0, bottomPadding)
                        }
                    }
                }

            }
        }
        // 内容居中
        gravity = Gravity.CENTER
        // 可点击
        isClickable = true
        changeTintContextWrapperToActivity()
    }

    override fun onDraw(canvas: Canvas) {
        // 让图片和文字居中
        when {
            contentWidth > 0 && (mDrawablePosition == 0 || mDrawablePosition == 2) -> canvas.translate((width - contentWidth) / 2, 0f)
            contentHeight > 0 && (mDrawablePosition == 1 || mDrawablePosition == 3) -> canvas.translate(0f, (height - contentHeight) / 2)
        }
        super.onDraw(canvas)
    }

    /**
     * 从support23.3.0开始View中的getContext方法返回的是TintContextWrapper而不再是Activity
     * 如果使用xml注册onClick属性,就会通过反射到Activity中去找对应的方法
     * 5.0以下系统会反射到TintContextWrapper中去找对应的方法,程序直接crash
     * 所以这里需要针对5.0以下系统单独处理View中的getContext返回值
     */
    private fun changeTintContextWrapperToActivity() {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
            getActivity()?.let {
                var clazz: Class<*>? = this::class.java
                while (clazz != null) {
                    try {
                        val field = clazz.getDeclaredField("mContext")
                        field.isAccessible = true
                        field.set(this, it)
                        break
                    } catch (e: Exception) {
                        e.printStackTrace()
                    }
                    clazz = clazz.superclass
                }
            }
        }
    }

    /**
     * 从context中得到真正的activity
     */
    private fun getActivity(): Activity? {
        var context = context
        while (context is ContextWrapper) {
            if (context is Activity) {
                return context
            }
            context = context.baseContext
        }
        return null
    }

    /**
     * 根据圆角位置获取圆角半径
     */
    private fun getCornerRadiusByPosition(): FloatArray {
        val result = floatArrayOf(0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f)
        val cornerRadius = mCornerRadius.toFloat()
        if (containsFlag(mCornerPosition, TOP_LEFT)) {
            result[0] = cornerRadius
            result[1] = cornerRadius
        }
        if (containsFlag(mCornerPosition, TOP_RIGHT)) {
            result[2] = cornerRadius
            result[3] = cornerRadius
        }
        if (containsFlag(mCornerPosition, BOTTOM_RIGHT)) {
            result[4] = cornerRadius
            result[5] = cornerRadius
        }
        if (containsFlag(mCornerPosition, BOTTOM_LEFT)) {
            result[6] = cornerRadius
            result[7] = cornerRadius
        }
        return result
    }

    /**
     * 是否包含对应flag
     */
    private fun containsFlag(flagSet: Int, flag: Int): Boolean {
        return flagSet or flag == flagSet
    }
}

attr.xml中的代码:

<resources>
    <declare-styleable name="CommonShapeButton">
        <!--shape模式-->
        <attr name="csb_shapeMode" format="enum">
            <enum name="rectangle" value="0" />
            <enum name="oval" value="1" />
            <enum name="line" value="2" />
            <enum name="ring" value="3" />
        </attr>
        <!--填充颜色-->
        <attr name="csb_fillColor" format="color" />
        <!--按压颜色-->
        <attr name="csb_pressedColor" format="color" />
        <!--描边颜色-->
        <attr name="csb_strokeColor" format="color" />
        <!--描边宽度-->
        <attr name="csb_strokeWidth" format="dimension" />
        <!--圆角大小-->
        <attr name="csb_cornerRadius" format="dimension" />
        <!--圆角位置-->
        <attr name="csb_cornerPosition">
            <flag name="topLeft" value="1" />
            <flag name="topRight" value="2" />
            <flag name="bottomRight" value="4" />
            <flag name="bottomLeft" value="8" />
        </attr>
        <!--是否开启动效-->
        <attr name="csb_activeEnable" format="boolean" />
        <!--drawable位置-->
        <attr name="csb_drawablePosition" format="enum">
            <enum name="left" value="0" />
            <enum name="top" value="1" />
            <enum name="right" value="2" />
            <enum name="bottom" value="3" />
        </attr>
        <!--渐变开始颜色-->
        <attr name="csb_startColor" format="color" />
        <!--渐变结束颜色-->
        <attr name="csb_endColor" format="color" />
        <!--渐变方向-->
        <attr name="csb_orientation" format="enum">
            <enum name="TOP_BOTTOM" value="0" />
            <enum name="LEFT_RIGHT" value="1" />
        </attr>
    </declare-styleable>
</resources>

style.xml中的代码:

<resources>
    <!-- 自定义按钮样式 -->
    <style name="CommonShapeButtonStyle" parent="@style/Widget.AppCompat.Button">
        <item name="android:minWidth">0dp</item>
        <item name="android:minHeight">0dp</item>
        <item name="android:padding">0dp</item>
    </style>
</resources>

在使用的时候只需要像使用自定义View一样在布局文件中设置相应的属性即可实现shape样式。

例如下面的例子:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <data>

    </data>

    <android.support.v4.widget.NestedScrollView
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="#fff"
            android:orientation="vertical">

            <com.blue.view.CommonShapeButton
                android:layout_width="300dp"
                android:layout_height="50dp"
                android:layout_margin="10dp"
                android:text="文本样式+填充颜色+圆角"
                android:textColor="#fff"
                app:csb_cornerRadius="50dp"
                app:csb_fillColor="#00bc71" />

            <com.blue.view.CommonShapeButton
                style="@style/CommonShapeButtonStyle"
                android:layout_width="300dp"
                android:layout_height="50dp"
                android:layout_margin="10dp"
                android:text="按钮样式+填充颜色+圆角"
                android:textColor="#fff"
                app:csb_cornerRadius="50dp"
                app:csb_fillColor="#00bc71" />

            <com.blue.view.CommonShapeButton
                style="@style/CommonShapeButtonStyle"
                android:layout_width="300dp"
                android:layout_height="50dp"
                android:layout_margin="10dp"
                android:text="按钮样式+水波纹+填充颜色+圆角"
                android:textColor="#fff"
                app:csb_activeEnable="true"
                app:csb_cornerRadius="50dp"
                app:csb_fillColor="#00bc71" />

            <com.blue.view.CommonShapeButton
                android:layout_width="300dp"
                android:layout_height="50dp"
                android:layout_margin="10dp"
                android:text="文本样式+填充颜色+描边"
                android:textColor="#fff"
                app:csb_activeEnable="true"
                app:csb_fillColor="#00bc71"
                app:csb_strokeColor="#000"
                app:csb_strokeWidth="1dp" />

            <com.blue.view.CommonShapeButton
                android:layout_width="300dp"
                android:layout_height="50dp"
                android:layout_margin="10dp"
                android:text="文本样式+水波纹+左右渐变"
                android:textColor="#fff"
                app:csb_activeEnable="true"
                app:csb_endColor="#00bc71"
                app:csb_orientation="LEFT_RIGHT"
                app:csb_startColor="#8800bc71" />

            <com.blue.view.CommonShapeButton
                android:layout_width="50dp"
                android:layout_height="50dp"
                android:layout_margin="10dp"
                android:text="椭圆"
                android:textColor="#fff"
                app:csb_fillColor="#00bc71"
                app:csb_shapeMode="oval"
                app:csb_strokeColor="#000"
                app:csb_strokeWidth="1dp" />

            <Button
                android:layout_width="180dp"
                android:layout_height="wrap_content"
                android:layout_margin="10dp"
                android:drawableLeft="@drawable/ic_changebtn_change"
                android:text="默认按钮图标无法居中"
                android:textColor="#808080"
                android:textSize="11dp" />

            <com.blue.view.CommonShapeButton
                android:layout_width="150dp"
                android:layout_height="30dp"
                android:layout_margin="10dp"
                android:drawableLeft="@drawable/ic_changebtn_change"
                android:text="带图标样式居中"
                android:textColor="#808080"
                android:textSize="11dp"
                app:csb_cornerRadius="50dp"
                app:csb_drawablePosition="left"
                app:csb_strokeColor="#e6e6e6"
                app:csb_strokeWidth="0.5dp" />

            <com.blue.view.CommonShapeButton
                android:layout_width="150dp"
                android:layout_height="30dp"
                android:layout_margin="10dp"
                android:drawableRight="@drawable/ic_changebtn_change"
                android:text="带图标样式居中"
                android:textColor="#808080"
                android:textSize="11dp"
                app:csb_cornerRadius="50dp"
                app:csb_drawablePosition="right"
                app:csb_strokeColor="#e6e6e6"
                app:csb_strokeWidth="0.5dp" />

            <RelativeLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content">

                <com.blue.view.CommonShapeButton
                    android:id="@+id/btn"
                    android:layout_width="50dp"
                    android:layout_height="130dp"
                    android:layout_margin="10dp"
                    android:drawableTop="@drawable/ic_changebtn_change"
                    android:text="带图标样式居中"
                    android:textColor="#808080"
                    android:textSize="11dp"
                    app:csb_cornerRadius="50dp"
                    app:csb_drawablePosition="top"
                    app:csb_strokeColor="#e6e6e6"
                    app:csb_strokeWidth="0.5dp" />

                <com.blue.view.CommonShapeButton
                    android:id="@+id/btn2"
                    android:layout_width="50dp"
                    android:layout_height="130dp"
                    android:layout_margin="10dp"
                    android:layout_toRightOf="@+id/btn"
                    android:drawableBottom="@drawable/ic_changebtn_change"
                    android:text="带图标样式居中"
                    android:textColor="#808080"
                    android:textSize="11dp"
                    app:csb_cornerRadius="50dp"
                    app:csb_drawablePosition="bottom"
                    app:csb_strokeColor="#e6e6e6"
                    app:csb_strokeWidth="0.5dp" />

                <com.blue.view.CommonShapeButton
                    android:id="@+id/btn3"
                    android:layout_width="130dp"
                    android:layout_height="50dp"
                    android:layout_margin="10dp"
                    android:layout_toRightOf="@+id/btn2"
                    android:drawableTop="@drawable/ic_changebtn_change"
                    android:text="带图标样式居中"
                    android:textColor="#808080"
                    android:textSize="11dp"
                    app:csb_cornerRadius="50dp"
                    app:csb_drawablePosition="top"
                    app:csb_strokeColor="#e6e6e6"
                    app:csb_strokeWidth="0.5dp" />

                <com.blue.view.CommonShapeButton
                    android:id="@+id/btn4"
                    android:layout_width="130dp"
                    android:layout_height="50dp"
                    android:layout_below="@+id/btn3"
                    android:layout_margin="10dp"
                    android:layout_toRightOf="@+id/btn2"
                    android:drawableBottom="@drawable/ic_changebtn_change"
                    android:text="带图标样式居中"
                    android:textColor="#808080"
                    android:textSize="11dp"
                    app:csb_cornerRadius="50dp"
                    app:csb_drawablePosition="bottom"
                    app:csb_strokeColor="#e6e6e6"
                    app:csb_strokeWidth="0.5dp" />
            </RelativeLayout>

        </LinearLayout>
    </android.support.v4.widget.NestedScrollView>
</layout>

感兴趣的同学可以在自己的项目中试一试!

build.gradle文件中的设置如下(可根据自己的需要进行设置):

apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'

android {
    compileSdkVersion 26
    defaultConfig {
        applicationId "com.blue"
        minSdkVersion 16
        targetSdkVersion 26
        versionCode 1
        versionName "1.0"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
    dataBinding {
        enabled = true
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation"org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
    implementation 'com.android.support:appcompat-v7:26.1.0'
    implementation 'com.android.support.constraint:constraint-layout:1.1.2'

    // databinding
    kapt "com.android.databinding:compiler:3.0.0"
}

在github上看到另一种替代drawable.xml的方案,有兴趣的同学可以点击查看https://github.com/whataa/noDrawable;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值