利用LayoutInflater.Factory2 实现圆角背景,减少图片引入

300 阅读1分钟

考虑到圆角背景在应用中多处使用,各种颜色以及radius小调整导致图片泛滥,所以统一使用Factory2配合自定义属性进行替换

attrs.xml内相关自定义属性

<declare-styleable name="rect_style">
    <attr format="dimension" name="rect_radius_lt" />
    <attr format="dimension" name="rect_radius_rt" />
    <attr format="dimension" name="rect_radius_lb" />
    <attr format="dimension" name="rect_radius_rb" />
    <attr format="dimension" name="rect_radius" />
    <attr format="color" name="rect_color" />
</declare-styleable>

考虑到可能还有其余factory使用,比如说一键替换背景,所以我们提供一个out属性,用于兼容

open class BaseActivity: FragmentActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        val rectBgFactory2= RectBgInflateFactory2()
//        val skinFactory2=SkinInflateFactory2()
//        rectBgFactory2.outFactory=skinFactory2
        layoutInflater.factory2=RectBgInflateFactory2()
        super.onCreate(savedInstanceState)
    }
}

接下来是rect factory的实现

主要逻辑在于遍历判断xml解析得到的tagname,通过反射构造对应view,然后创建shape背景设置给view

package com.example.test

import android.content.Context
import android.graphics.drawable.ShapeDrawable
import android.graphics.drawable.shapes.RoundRectShape
import android.util.AttributeSet
import android.view.LayoutInflater
import android.view.View
import androidx.collection.SimpleArrayMap
import java.lang.reflect.Constructor


class RectBgInflateFactory2 : LayoutInflater.Factory2 {

    var outFactory:LayoutInflater.Factory2?=null

    override fun onCreateView(
        parent: View?,
        name: String,
        context: Context,
        attrs: AttributeSet
    ): View? {
        val view=createView(parent, name, context, attrs)
        view?.let { 
            setRectBg(it,context,attrs)
        }
        return view
    }
    
    
    private fun setRectBg(view:View,context: Context,attrs: AttributeSet){
        val rectTa = context.obtainStyledAttributes(attrs,R.styleable.rect_style)
        val rectColor=rectTa.getColor(R.styleable.rect_style_rect_color,-1)

        var rectRadiusLt=0f
        var rectRadiusRt=0f
        var rectRadiusLb=0f
        var rectRadiusRb=0f
        val rectRadius=rectTa.getDimension(R.styleable.rect_style_rect_radius,-1f)
        if (rectRadius!=-1f){
            rectRadiusLt=rectRadius
            rectRadiusRt=rectRadius
            rectRadiusLb=rectRadius
            rectRadiusRb=rectRadius
        }else{
            rectRadiusLt=rectTa.getDimension(R.styleable.rect_style_rect_radius_lt,0f)
            rectRadiusRt=rectTa.getDimension(R.styleable.rect_style_rect_radius_rt,0f)
            rectRadiusLb=rectTa.getDimension(R.styleable.rect_style_rect_radius_lb,0f)
            rectRadiusRb=rectTa.getDimension(R.styleable.rect_style_rect_radius_rb,0f)
        }
        if (rectColor!=-1){
            val rectShape=ShapeDrawable(RoundRectShape(arrayOf(rectRadiusLt,rectRadiusLt,rectRadiusRt,rectRadiusRt,rectRadiusRb,rectRadiusRb,rectRadiusLb,rectRadiusLb).toFloatArray(),null,null))
            rectShape.paint.color=rectColor
            view.background=rectShape
        }
    }
    
    private fun createView(parent: View?,name: String, context: Context, attrs: AttributeSet): View? {
        //外部factory先处理view
        var view:View?= outFactory?.onCreateView(parent, name, context, attrs)

        if (view==null){
            var vClassName = name
            
            if (vClassName == "view") {
                vClassName = attrs.getAttributeValue(null, "class")
            }

            constructorArgs[0] = context
            constructorArgs[1] = attrs
            if (-1 == name.indexOf('.')) {
                for (i in vClassPrefixList.indices) {

                    view = createViewByPrefix(context, vClassName, vClassPrefixList[i])
                    if (view != null) {
                        break
                    }
                }
            } else {
                //全路径
                view=createViewByPrefix(context, vClassName, null)
            }
            constructorArgs[0] = null
            constructorArgs[1] = null
        }
        return view
    }


    private val constructorCacheMap = SimpleArrayMap<String, Constructor<out View?>>()//class 缓存
    private val constructorArgs = arrayOfNulls<Any>(2)//构造参数

    private val vClassPrefixList = arrayOf(
        "android.widget.",
        "android.view.",
        "android.webkit.",
        "androidx.appcompat.widget"
    )

    private fun createViewByPrefix(context: Context, name: String, prefix: String?): View? {
        var constructor = constructorCacheMap[name]
        return try {
            if (constructor == null) {
                val clazz = Class.forName(
                    if (prefix != null) prefix + name else name,
                    false,
                    context.classLoader
                ).asSubclass(View::class.java)
                constructor = clazz.getConstructor(Context::class.java, AttributeSet::class.java)
                constructorCacheMap.put(name, constructor)
            }
            constructor!!.isAccessible = true
            constructor.newInstance(*constructorArgs)
        } catch (e: Exception) {
            null
        }
    }



    override fun onCreateView(name: String, context: Context, attrs: AttributeSet): View? {
        return null
    }
    
}