美文网首页
减少自定义布局View嵌套

减少自定义布局View嵌套

作者: CentForever | 来源:发表于2021-01-03 20:58 被阅读0次

    减少自定义布局View嵌套

    通常遇到一些固定用法的组合布局,会把它们做成一个自定义View,以方便重复使用。比如说一个头像加一个Vip标志:

    刚开始的时候,可能会这样写:

    layout:

    <?xml version="1.0" encoding="utf-8"?>
    <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content">
    
        <ImageView
            android:id="@+id/avatar"
            android:layout_width="50dp"
            android:layout_height="50dp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />
    
        <ImageView
            android:id="@+id/vip"
            android:layout_width="16dp"
            android:layout_height="16dp"
            android:src="@drawable/vip"
            app:layout_constraintBottom_toBottomOf="@+id/avatar"
            app:layout_constraintEnd_toEndOf="@+id/avatar" />
    
    </android.support.constraint.ConstraintLayout>
    

    code:

    class AvatarView1 @JvmOverloads constructor(
        context: Context,
        attrs: AttributeSet? = null,
        defStyleAttr: Int = 0
    ) : FrameLayout(context, attrs, defStyleAttr) {
    
        override fun onFinishInflate() {
            super.onFinishInflate()
            LayoutInflater.from(context).inflate(R.layout.avatar_view_1, this, true)
        }
    
        fun setAvatar(url: String) {
            ...
        }
    }
    

    没毛病,但是AvatarView1自身的根布局多产生了一层嵌套:

    那么如何能减少这一层嵌套呢,有两种方法:

    1. 使用merge标签

    • 让自定义View的类继承布局文件的根布局。
    • 将布局文件的根布局改成merge标签。

    code:

    class AvatarView2 @JvmOverloads constructor(
        context: Context,
        attrs: AttributeSet? = null,
        defStyleAttr: Int = 0
    ) : ConstraintLayout(context, attrs, defStyleAttr) {
    
        ...
    }
    

    layout:

    <?xml version="1.0" encoding="utf-8"?>
    <merge xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        xmlns:tools="http://schemas.android.com/tools"
        tools:parentTag="android.support.constraint.ConstraintLayout">
    
        <ImageView
            android:id="@+id/avatar"
            android:layout_width="50dp"
            android:layout_height="50dp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />
    
        <ImageView
            android:id="@+id/vip"
            android:layout_width="16dp"
            android:layout_height="16dp"
            android:src="@drawable/vip"
            app:layout_constraintBottom_toBottomOf="@+id/avatar"
            app:layout_constraintEnd_toEndOf="@+id/avatar" />
    
    </merge>
    

    tools:parentTag=”android.support.constraint.ConstraintLayout”,加入该属性可以保持preview中的预览效果

    使用:

    <com.ezstudio.view.AvatarView2
        android:id="@+id/avatar_view"
        android:layout_width="50dp"
        android:layout_height="50dp" />
    

    运行后可以看到,布局层次少了一层:

    2. 使用view标签

    • 让自定义View的类继承布局文件的根布局,并去掉onFinishInflate中的相关代码。
    • 将布局文件的根布局改成view标签,并利用class属性将布局和类关联起来。

    code:

    class AvatarView3 @JvmOverloads constructor(
        context: Context,
        attrs: AttributeSet? = null,
        defStyleAttr: Int = 0
    ) : ConstraintLayout(context, attrs, defStyleAttr) {
    
        //override fun onFinishInflate() {
        //    super.onFinishInflate()
        //    LayoutInflater.from(context).inflate(R.layout.avatar_view_1, this, true)
        //}
    
        fun setAvatar(url: String) {
            ...
        }
    }
    

    layout:

    <?xml version="1.0" encoding="utf-8"?>
    <view xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        xmlns:tools="http://schemas.android.com/tools"
        class="com.ezstudio.view.AvatarView3"
        tools:parentTag="android.support.constraint.ConstraintLayout">
    
        <ImageView
            android:id="@+id/avatar"
            android:layout_width="50dp"
            android:layout_height="50dp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />
    
        <ImageView
            android:id="@+id/vip"
            android:layout_width="16dp"
            android:layout_height="16dp"
            android:src="@drawable/vip"
            app:layout_constraintBottom_toBottomOf="@+id/avatar"
            app:layout_constraintEnd_toEndOf="@+id/avatar" />
    
    </view>
    

    使用:

    val avatarView = LayoutInflater.from(this).inflate(R.layout.avatar_view_3, main_layout, false) as AvatarView3
    main_layout.addView(avatarView)
    

    运行后可以看到,布局层次一样是少了一层:

    3. 终极大法

    所谓终极大法就是将自定义View的所有子元素,通过Canvas绘制到界面上,这样的效率是最高的,适合对性能要求变态的场景:

    class AvatarView4 @JvmOverloads constructor(
        context: Context,
        attrs: AttributeSet? = null,
        defStyleAttr: Int = 0
    ) : View(context, attrs, defStyleAttr) {
    
        private val paint by lazy {
            Paint().apply {
                isAntiAlias = true
            }
        }
    
        private var avatarBitmap: Bitmap? = null
    
        private val vipBitmap by lazy { BitmapFactory.decodeResource(resources, R.drawable.vip) }
    
        private val vipSize = dip2px(context, 16f)
    
        private val viewRect by lazy { RectF() }
    
        private val vipRect by lazy { RectF(0f, 0f, vipSize, vipSize) }
    
        private val target: SimpleTarget<Bitmap> by lazy {
            object : SimpleTarget<Bitmap>() {
                override fun onResourceReady(resource: Bitmap, transition: Transition<in Bitmap>?) {
                    avatarBitmap = resource
                    invalidate()
                }
            }
        }
    
        override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
            super.onSizeChanged(w, h, oldw, oldh)
            viewRect.set(0f, 0f, measuredWidth.toFloat(), measuredHeight.toFloat())
        }
    
        override fun onDraw(canvas: Canvas?) {
            super.onDraw(canvas)
            canvas?.apply {
                avatarBitmap?.let {
                    drawBitmap(it, null, viewRect, paint)
                }
                save()
                translate(viewRect.right - vipSize, viewRect.bottom - vipSize)
                drawBitmap(vipBitmap, null, vipRect, paint)
                restore()
            }
        }
    
        fun setAvatar(url: String) {
            val requestOptions = RequestOptions.circleCropTransform().dontAnimate()
            Glide.with(context).asBitmap().load(url).apply(requestOptions).into(target)
        }
    
        private fun dip2px(context: Context, @Dimension(unit = Dimension.DP) dpValue: Float): Float {
            return TypedValue.applyDimension(
                TypedValue.COMPLEX_UNIT_DIP,
                dpValue,
                context.resources.displayMetrics
            )
        }
    }
    

    运行后可以看到,布局层次只有一层:

    总结

    • merge标签和view标签都能有效减少自定义View的嵌套层级
    • merge标签适合在其他布局文件直接使用的场景
    • view标签适合通过LayoutInflater加载的场景,比如在RecyclerView中作为Item使用
    • 终极大法效率最高,适合对性能要求变态的场景

    相关文章

      网友评论

          本文标题:减少自定义布局View嵌套

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