Android Drawable类型集合

Drawable

1.BitmapDrawable

位图图像。Android支持三种格式的位图文件:.png(首选)、.jpg(可接受)、.gif(不建议)。我们可以直接使用文件名作为资源 ID 来引用位图文件,也可以在 XML 文件中创建别名资源 ID,这就叫做 XML位图。

<1> 使用xml位图

<?xml version="1.0" encoding="utf-8"?>
<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
    android:antialias="true"
    android:gravity="fill"
    android:src="@drawable/ic_launcher_background"
    android:tileMode="disabled"/>

关于<bitmap> 属性:

  1. android:src:引用可绘制对象资源,必备。

  2. android:tileMode:定义平铺模式。当平铺模式启用时,位图会重复,且注意:一旦平铺模式启用, android:gravity 属性就将会被忽略。定义平铺属性的值必须是以下值之一:

    • disabled:不平铺位图,默认值。
    • clamp:当着色器绘制范围超出其原边界时复制边缘颜色。
    • repeat:水平和垂直重复着色器的图像。
    • mirror:水平和垂直重复着色器的图像,交替镜像图像以使相邻图像始终相接。

    注意:在平铺模式启用时android:gravity属性将被忽略。

  3. android:gravity:定义位图的重力属性,当位图小于容器时,可绘制对象在其容器中放置的位置。

    • top:将对象放在其容器顶部,不改变其大小。

    • bott****om:将对象放在其容器底部,不改变其大小。

    • left:将对象放在其容器左边缘,不改变其大小。

    • right:将对象放在其容器右边缘,不改变其大小。

    • center_vertical:将对象放在其容器的垂直中心,不改变其大小。

    • fill_vertical:按需要扩展对象的垂直大小,使其完全适应其容器。

    • center_horizontal:将对象放在其容器的水平中心,不改变其大小。

    • fill_horizontal:按需要扩展对象的水平大小,使其完全适应其容器。

    • center:将对象放在其容器的水平和垂直轴中心,不改变其大小。

    • fill:按需要扩展对象的垂直大小,使其完全适应其容器。这是默认值。

    • clip_vertical:可设置为让子元素的上边缘和/或下边缘裁剪至其容器边界的附加选项。裁剪基于垂直重力:顶部重力裁剪上边缘,底部重力裁剪下边缘,任一重力不会同时裁剪两边。

    • clip_horizontal:可设置为让子元素的左边和/或右边裁剪至其容器边界的附加选项。裁剪基于水平重力:左边重力裁剪右边缘,右边重力裁剪左边缘,任一重力不会同时裁剪两边。

<2>通过代码来实现,即BitmapDrawable

 binding.bitmapDrawableInclude.apply {
    tv1.setText(R.string.bitmap_drawable)
    tv1.background = ContextCompat.getDrawable(requireContext(), R.drawable.bitmap_drawable)
    tv2.setText(R.string.bitmap_drawable)

    val bitmap = BitmapFactory.decodeResource(resources, R.drawable.nick)
    val bitmapShape = BitmapDrawable(resources, bitmap)
    tv2.background = bitmapShape
}

2.LayerDrawable

图层列表(LayerDrawable):是可绘制对象列表组成的可绘制对象。列表中的每个可绘制对象均按照列表顺序绘制,列表中的最后一个可绘制对象绘于顶部。

每个可绘制对象由单一<layer-list> 元素内的<item>元素表示。

<1>XML

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item>
        <bitmap android:src="@drawable/nick" />
    </item>
    <item
        android:bottom="150dp"
        android:left="0dp"
        android:right="250dp"
        android:top="0dp">
        <shape android:shape="oval">
            <solid android:color="@color/royal_blue" />
        </shape>
    </item>
    <item
        android:bottom="75dp"
        android:left="125dp"
        android:right="125dp"
        android:top="75dp">
        <shape android:shape="oval">
            <solid android:color="@color/indian_red" />
        </shape>
    </item>
    <item
        android:bottom="0dp"
        android:left="250dp"
        android:right="0dp"
        android:top="150dp">
        <shape android:shape="oval">
            <solid android:color="@color/yellow" />
        </shape>
    </item>

</layer-list>
  1. <layer-list>:必备的根元素。包含一个或多个 <item> 元素。

  2. <item>:是 <layer-list> 元素的子项,其属性支持定义在图层中所处的位置。

  3. <item> 标签属性:

    android:top:整型。顶部偏移(像素)。

    android:right:整型。右边偏移(像素)。

    android:bottom:整型。底部偏移(像素)。

    android:left:整型。左边偏移(像素)。

<2>代码实现

 binding.layerDrawableInclude.apply {
    tv1.setText(R.string.layer_drawable)
    tv1.background = ContextCompat.getDrawable(requireContext(), R.drawable.layer_drawable)
    tv2.setText(R.string.layer_drawable)

    val itemLeft = GradientDrawable().apply {
        setColor(ContextCompat.getColor(requireContext(), R.color.royal_blue))
        setSize(50.px, 50.px)
        shape = GradientDrawable.OVAL
    }
    val itemCenter = GradientDrawable().apply {
        setColor(ContextCompat.getColor(requireContext(), R.color.indian_red))
        shape = GradientDrawable.OVAL
    }
    val itemRight = GradientDrawable().apply {
        setColor(ContextCompat.getColor(requireContext(), R.color.yellow))
        shape = GradientDrawable.OVAL
    }
    val arr = arrayOf(
        ContextCompat.getDrawable(requireContext(), R.drawable.nick)!!,
        itemLeft,
        itemCenter,
        itemRight
    )
    val ld = LayerDrawable(arr).apply {
        setLayerInset(1, 0.px, 0.px, 250.px, 150.px)
        setLayerInset(2, 125.px, 75.px, 125.px, 75.px)
        setLayerInset(3, 250.px, 150.px, 0.px, 0.px)
    }
    tv2.background = ld
}

3.StateListDrawable

状态列表(StateListDrawable):会根据对象状态,使用多个不同的图像来表示同一个图形。

<1> XML

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android" android:constantSize="false">
    <item android:drawable="@drawable/nick" android:state_pressed="false" />
    <item android:drawable="@drawable/basketball" android:state_pressed="true" />
</selector>

其中的属性:

  1. <selector>:必备的根元素。包含一个或多个 <item> 元素。

    2.<item>:定义在某些状态期间使用的可绘制对象,必须是 <selector> 元素的子项。其属性:

  • android:drawable:引用可绘制对象资源,必备。

  • android:state_pressed:布尔值。是否按下对象(例如触摸/点按某按钮)。

  • android:state_checked:布尔值。是否选中对象。

  • android:state_enabled:布尔值。是否能够接收触摸或点击事件。

<2> 代码

 binding.stateListDrawableTv.apply {
    setOnClickListener {
        Log.e(TAG, "stateListDrawableTv: isPressed = $isPressed")
    }
}

val sld = StateListDrawable().apply {
    addState(
        intArrayOf(android.R.attr.state_pressed),
        ContextCompat.getDrawable(requireContext(), R.drawable.basketball)
    )
    addState(StateSet.WILD_CARD, ContextCompat.getDrawable(requireContext(), R.drawable.nick))
}
binding.stateListDrawableTv2.apply {
    background = sld
    setOnClickListener {
        Log.e(TAG, "stateListDrawableTv2: isPressed = $isPressed")
    }
}

4.LevelListDrawable

级别列表(LevelListDrawable):管理可绘制对象列表,每个可绘制对象都有设置Level等级限制,当使用setLevel时,会加载级别列表中android:maxLevel值大于或等于传递至方法的值的可绘制对象资源。

<1>xml

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

    <item
        android:drawable="@drawable/nick"
        android:maxLevel="1" />

    <item
        android:drawable="@drawable/tom1"
        android:maxLevel="2" />

    <item
        android:drawable="@drawable/tom2"
        android:maxLevel="3" />

    <item
        android:drawable="@drawable/tom3"
        android:maxLevel="4" />

    <item
        android:drawable="@drawable/tom4"
        android:maxLevel="5" />

    <item
        android:drawable="@drawable/tom5"
        android:maxLevel="6" />

    <item
        android:drawable="@drawable/tom6"
        android:maxLevel="7" />

    <item
        android:drawable="@drawable/tom7"
        android:maxLevel="8" />

    <item
        android:drawable="@drawable/tom8"
        android:maxLevel="9" />

    <item
        android:drawable="@drawable/tom9"
        android:maxLevel="10" />

</level-list>
  1. <level-list>:必备的根元素。包含一个或多个 <item> 元素。

  2. <item>:在特定级别下使用的可绘制对象。

    android:drawable:必备。引用可绘制对象资源。

    android:maxLevel:整型。表示该Item允许的最高级别。

    android:minLevel:整型。表示该Item允许的最低级别。

<2>代码

class LevelListDrawableFragment : BaseFragment<FragmentLevelListDrawableBinding>() {

    private val lld by lazy {
        LevelListDrawable().apply {
            addLevel(0, 1, getDrawable(R.drawable.nick))
            addLevel(0, 2, getDrawable(R.drawable.tom1))
            addLevel(0, 3, getDrawable(R.drawable.tom2))
            addLevel(0, 4, getDrawable(R.drawable.tom3))
            addLevel(0, 5, getDrawable(R.drawable.tom4))
            addLevel(0, 6, getDrawable(R.drawable.tom5))
            addLevel(0, 7, getDrawable(R.drawable.tom6))
            addLevel(0, 8, getDrawable(R.drawable.tom7))
            addLevel(0, 9, getDrawable(R.drawable.tom8))
            addLevel(0, 10, getDrawable(R.drawable.tom9))
        }
    }

    private fun getDrawable(id: Int): Drawable {
        return (ContextCompat.getDrawable(requireContext(), id)
            ?: ContextCompat.getDrawable(requireContext(), R.drawable.nick)) as Drawable
    }

    private val levelListDrawable by lazy {
        ContextCompat.getDrawable(requireContext(), R.drawable.level_list_drawable)
    }

    override fun initView() {

        binding.levelListDrawableInclude.apply {
            tv1.setText(R.string.level_list_drawable)
            tv1.background = levelListDrawable
            tv2.setText(R.string.level_list_drawable)

            tv2.background = lld
        }

        binding.seekBar.apply {
            //init level
            levelListDrawable?.level = progress
            lld.level = progress
            //add listener
            setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
                override fun onProgressChanged(
                    seekBar: SeekBar?,
                    progress: Int,
                    fromUser: Boolean
                ) {
                    levelListDrawable?.level = progress
                    lld.level = progress
                    Log.e(TAG, "onProgressChanged: progreess = $progress")
                }

                override fun onStartTrackingTouch(seekBar: SeekBar?) {

                }

                override fun onStopTrackingTouch(seekBar: SeekBar?) {

                }
            })
        }

    }
}

5.TransitionDrawable

转换可绘制对象(TransitionDrawable):可在两种可绘制对象资源之间交错淡出。

<1>xml

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

    <item
        android:id="@+id/ship"
        android:drawable="@drawable/nick" />

    <item
        android:id="@+id/plane"
        android:drawable="@drawable/basketball" />

</transition>

其中的属性:

  1. <transition>:必备的根元素。包含一个或多个 <item>元素。

  2. <item>:转换部分的可绘制对象。

    android:drawable:必备。引用可绘制对象资源。

    android:top、android:bottom、android:left、android:right:整型。偏移量(像素)。

注意:不能超过两个Item,调用 startTransition向前转换,调用 reverseTransition 向后转换。

<2>代码

class TransitionDrawableFragment : BaseFragment<FragmentTransitionDrawableBinding>() {

    private var isShow = false
    private lateinit var bgDrawable: TransitionDrawable
    private lateinit var manualDrawable: TransitionDrawable

    override fun initView() {
        binding.transitionDrawableInclude.apply {
            tv1.setText(R.string.transition_drawable)
            tv1.background =
                ContextCompat.getDrawable(requireContext(), R.drawable.transition_drawable)
            tv2.setText(R.string.transition_drawable)

            bgDrawable = tv1.background as TransitionDrawable
            val drawableArray = arrayOf(
                ContextCompat.getDrawable(requireContext(), R.drawable.nick),
                ContextCompat.getDrawable(requireContext(), R.drawable.basketball)
            )
            manualDrawable = TransitionDrawable(drawableArray)
            tv2.background = manualDrawable
        }
    }

    private fun setTransition() {
        if (isShow) {
            bgDrawable.reverseTransition(1500)
            manualDrawable.reverseTransition(3000)
        } else {
            bgDrawable.startTransition(1500)
            manualDrawable.startTransition(3000)
        }
    }

    override fun onResume() {
        super.onResume()
        setTransition()
        isShow = !isShow
    }
}

6.InsetDrawable

插入可绘制对象(InsetDrawable):以指定距离插入其他可绘制对象,当视图需要小于视图实际边界的背景时,此类可绘制对象很有用。

<1>xml

<?xml version="1.0" encoding="utf-8"?>
<inset xmlns:android="http://schemas.android.com/apk/res/android"
    android:drawable="@drawable/nick"
    android:insetTop="50dp"
    android:insetLeft="150dp">

</inset>

<inset>:必备。根元素。

android:drawable:必备。引用可绘制对象资源。

android:insetTop、android:insetBottom、android:insetLeft、android:insetRight:尺寸。插入的,表示为尺寸

<2>代码

binding.insetDrawableInclude.apply {
    tv1.setText(R.string.inset_drawable)
    tv1.background = ContextCompat.getDrawable(requireContext(), R.drawable.inset_drawable)
    tv2.setText(R.string.inset_drawable)

    val insetDrawable = InsetDrawable(
        ContextCompat.getDrawable(requireContext(), R.drawable.nick),
        0f, 0f, 0.5f, 0.25f
    )
    tv2.background = insetDrawable
}

7.ClipDrawable

裁剪可绘制对象(ClipDrawable):根据level等级对可绘制对象进行裁剪,可以根据level与gravity来控制子可绘制对象的宽度与高度。

<1>xml

<?xml version="1.0" encoding="utf-8"?>
<clip xmlns:android="http://schemas.android.com/apk/res/android"
    android:drawable="@drawable/nick"
    android:clipOrientation="horizontal"
    android:gravity="center">

</clip>
  1. <clip>:必备。根元素。

  2. android:drawable:必备。引用可绘制对象资源。

  3. android:clipOrientation:裁剪方向。

    horizontal:水平裁剪。

    vertical:垂直裁剪。

  4. android:gravity:重力属性。

最后通过设置level等级来实现裁剪,level 默认级别为 0,即完全裁剪,使图像不可见。当级别为 10,000 时,图像不会裁剪,而是完全可见。

<2>代码

class ClipDrawableFragment : BaseFragment<FragmentClipDrawableBinding>() {

    private val clipDrawable by lazy {
        ContextCompat.getDrawable(requireContext(), R.drawable.clip_drawable)
    }
    private val manualClipDr1awable by lazy {
        ClipDrawable(
            ContextCompat.getDrawable(requireContext(), R.drawable.nick),
            Gravity.CENTER,
            ClipDrawable.VERTICAL
        )
    }

    override fun initView() {
        binding.clipDrawableInclude.apply {
            tv1.setText(R.string.clip_drawable)
            tv1.background = clipDrawable
            tv2.setText(R.string.clip_drawable)
            tv2.background = manualClipDrawable
        }

        //level 默认级别为 0,即完全裁剪,使图像不可见。当级别为 10,000 时,图像不会裁剪,而是完全可见。
        binding.seekBar.apply {
            //init level
            clipDrawable?.level = progress
            manualClipDrawable.level = progress
            //add listener
            setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
                override fun onProgressChanged(
                    seekBar: SeekBar?,
                    progress: Int,
                    fromUser: Boolean
                ) {
                    clipDrawable?.level = progress
                    manualClipDrawable.level = progress
                }

                override fun onStartTrackingTouch(seekBar: SeekBar?) {

                }

                override fun onStopTrackingTouch(seekBar: SeekBar?) {

                }

            })
        }
    }

}

8.ScaleDrawable

缩放可绘制对象(ScaleDrawable):根据level等级来更改其可绘制对象大小。

<1>xml

<?xml version="1.0" encoding="utf-8"?>
<scale xmlns:android="http://schemas.android.com/apk/res/android"
    android:drawable="@drawable/nick"
    android:scaleWidth="100%"
    android:scaleHeight="100%"
    android:scaleGravity="center">

</scale>

其属性:

  • <scale>:必备。根元素。

    • android:drawable:必备。引用可绘制对象资源。

    • android:scaleGravity:指定缩放后的重力位置。

    • android:scaleHeight:百分比。缩放高度,表示为可绘制对象边界的百分比。值的格式为 XX%。例如:100%、12.5% 等。

    • android:scaleWidth:百分比。缩放宽度,表示为可绘制对象边界的百分比。值的格式为 XX%。例如:100%、12.5% 等。

<2>代码

 binding.scaleDrawableInclude.apply {
    tv1.setText(R.string.scale_drawable)
    tv1.background = ContextCompat.getDrawable(requireContext(), R.drawable.scale_drawable)
    tv2.setText(R.string.scale_drawable)

    val scaleDrawable = ScaleDrawable(
        ContextCompat.getDrawable(requireContext(), R.drawable.nick),
        Gravity.CENTER,
        1f,
        1f
    )
    tv2.background = scaleDrawable

    binding.seekBar.apply {
        //init level
        tv1.background.level = progress
        scaleDrawable.level = progress
        //add listener
        setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
            override fun onProgressChanged(
                seekBar: SeekBar?,
                progress: Int,
                fromUser: Boolean
            ) {
                tv1.background.level = progress
                scaleDrawable.level = progress
                Log.e(TAG, "onProgressChanged: progreess = $progress")
            }

            override fun onStartTrackingTouch(seekBar: SeekBar?) {

            }

            override fun onStopTrackingTouch(seekBar: SeekBar?) {

            }

        })
    }
}

9.ShapeDrawable

形状可绘制对象(ShapeDrawable):通过XML来定义各种形状的可绘制对象。

<1>xml

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

    <corners
        android:topLeftRadius="20dp"
        android:topRightRadius="20dp" />

    <solid android:color="@color/royal_blue" />

    <stroke
        android:width="3dp"
        android:color="@color/purple_200"
        android:dashWidth="10dp"
        android:dashGap="5dp" />

</shape>

其属性:

  1. <shape>:必备。根元素。

    • android:shape:定义形状的类型。

      • rectangle:默认形状,填充包含视图的矩形。

      • oval:适应包含视图尺寸的椭圆形状。

      • line:跨越包含视图宽度的水平线。此形状需要 元素定义线宽。

      • ring:环形。

    • android:innerRadius:尺寸。环内部(中间的孔)的半径。

    • android:thickness:尺寸。环的厚度。

  1. <corners>:圆角,仅当形状为矩形时适用。

    • android:radius:尺寸。所有角的半径。如果想要设置单独某个角,可以使用 android:topLeftRadius、android:topRightRadius、android:bottomLeftRadius、android:bottomRightRadius。
  1. <padding>:设置内边距。

    • android:left:尺寸。设置左内边距。同样还有 android:right、android:top、android:bottom供选择。
  1. <size>:形状的大小。

    • android:height:尺寸。形状的高度。

    • android:width:尺寸。形状的宽度。

  1. <solid>:填充形状的纯色。

    • android:color:颜色。
  1. <stroke>:形状的笔画

    • android:width:尺寸。线宽。

    • android:color:颜色。线的颜色。

    • android:dashGap:尺寸。短划线的间距。虚线效果。

    • android:dashWidth:尺寸。每个短划线的大小。虚线效果。

<2>代码

class ShapeDrawableFragment : BaseFragment<FragmentShapeDrawableBinding>() {

    override fun initView() {
        binding.shapeDrawableInclude.apply {
            tv1.setText(R.string.shape_drawable)
            tv1.background = ContextCompat.getDrawable(requireContext(), R.drawable.shape_drawable)
            tv2.setText(R.string.shape_drawable)

            val roundRectShape =
                RoundRectShape(
                    floatArrayOf(20f.px, 20f.px, 20f.px, 20f.px, 0f, 0f, 0f, 0f),
                    null,
                    null
                )
            tv2.background = MyShapeDrawable(roundRectShape)
        }
    }

    /**
     * TODO: //使用 GradientDrawable 效果更好
     */
    class MyShapeDrawable(shape: Shape) : ShapeDrawable(shape) {
        private val fillPaint = Paint().apply {
            style = Paint.Style.FILL
            color = Color.parseColor("#4169E1")
        }
        private val strokePaint = Paint().apply {
            style = Paint.Style.STROKE
            color = Color.parseColor("#FFBB86FC")
//            strokeCap = Paint.Cap.ROUND
//            strokeJoin = Paint.Join.ROUND
            strokeMiter = 10f
            strokeWidth = 5f.px
            pathEffect = DashPathEffect(floatArrayOf(10f.px, 5f.px), 0f)
        }

        override fun onDraw(shape: Shape?, canvas: Canvas?, paint: Paint?) {
            super.onDraw(shape, canvas, paint)
            shape?.draw(canvas, fillPaint)
            shape?.draw(canvas, strokePaint)
        }
    }

}

10.GradientDrawable

渐变可绘制对象(GradientDrawable):如其名,实现渐变颜色效果。其实也是属于ShapeDrawable。

<1>xml

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

    <gradient
        android:endColor="#BBFFFF"
        android:gradientRadius="50%p"
        android:startColor="#00F5FF"
        android:type="radial" />

</shape>
  1. <shape>:必备。根元素。

  2. gradient:表示渐变的颜色。

    • android:angle:整型。表示渐变的角度。0 表示为从左到右,90 表示为从上到上。注意:必须是 45 的倍数。默认值为 0。

    • android:centerX:浮点型。表示渐变中心相对 X 轴位置 (0 - 1.0)。 android:centerY同理。

    • android:startColor:颜色。起始颜色。 android:endColor、android:centerColor分别表示结束颜色与中间颜色。

    • android:gradientRadius:浮点型。渐变的半径。仅在 android:type="radial" 时适用。

    • android:type:渐变的类型。

    • linear:线性渐变。默认为该类型。

    • radial:径向渐变,也就是雷达式渐变,起始颜色为中心颜色。

    • sweep:流线型渐变。

<2>代码

 binding.gradientDrawableInclude.apply {
    tv1.setText(R.string.gradient_drawable)
    tv1.background =
        ContextCompat.getDrawable(requireContext(), R.drawable.gradient_drawable)
    tv2.setText(R.string.gradient_drawable)

    val gradientDrawable = GradientDrawable().apply {
        shape = GradientDrawable.OVAL
        gradientType = GradientDrawable.RADIAL_GRADIENT
        colors = intArrayOf(Color.parseColor("#00F5FF"), Color.parseColor("#BBFFFF"))
        gradientRadius = 100f.px
    }
    tv2.background = gradientDrawable
}

11.AnimationDrawable

动画可绘制对象(AnimationDrawable):用于创建逐帧动画的可绘制对象。

<1>xml

<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item
        android:drawable="@drawable/nick"
        android:duration="1000" />
    <item
        android:drawable="@drawable/basketball"
        android:duration="1000" />

</animation-list>
  1. <animation-list>:必备。根元素。

  2. <item>:每一帧的可绘制对象。

    • android:drawable:必备。引用可绘制对象资源。

    • android:duration:该帧的持续时间,单位为毫秒。

    • android:oneshot:布尔值。代表是否只单次展示该动画,默认为false。

<2>代码

class AnimationDrawableFragment : BaseFragment<FragmentAnimationDrawableBinding>() {
    private val animationDrawable by lazy {
        ContextCompat.getDrawable(
            requireContext(),
            R.drawable.animation_drawable
        ) as AnimationDrawable
    }

    override fun initView() {
        binding.animationDrawableInclude.apply {
            tv1.setText(R.string.animation_drawable)
            tv1.background = animationDrawable
            tv2.setText(R.string.animation_drawable)

            animationDrawable.start()

            val animationDrawable = AnimationDrawable().apply {
                ContextCompat.getDrawable(requireContext(), R.drawable.nick)
                    ?.let { addFrame(it, 1000) }
                ContextCompat.getDrawable(requireContext(), R.drawable.basketball)
                    ?.let { addFrame(it, 1000) }
            }
            tv2.background = animationDrawable
            animationDrawable.start()
        }
    }

}

2.多动画联动

class AnimatedVectorDrawableFragment : BaseFragment<FragmentAnimatedVectorDrawableBinding>() {
    override fun initView() {
        (binding.vectorDrawableIv.drawable as Animatable).start()


        //如果你想对 VectorDrawable (也就是binding.vectorDrawableIv2.drawable) 做动画处理,你需要使用 AnimatedVectorDrawable
        //不然你会发现报错信息 Method setScaleX() with type float not found on target class class android.graphics.drawable.VectorDrawable
        //refer link:https://stackoverflow.com/a/32007436/11641198
        val iv = binding.vectorDrawableIv2
        val an1 = ObjectAnimator.ofFloat(iv, "scaleX", 0f, 1f).apply {
            duration = 3000
            repeatCount = ValueAnimator.INFINITE
            repeatMode = ValueAnimator.REVERSE
        }
        val an2 = ObjectAnimator.ofFloat(iv, "scaleY", 0f, 1f).apply {
            duration = 3000
            repeatCount = ValueAnimator.INFINITE
            repeatMode = ValueAnimator.REVERSE
        }
        AnimatorSet().apply {
            duration = 3000
            playTogether(an1, an2)
            start()
        }
    }
}

12.自定义 Drawable

package com.drawable.learning.fragment.custom.line_chart

import android.graphics.*
import android.graphics.drawable.Drawable
import com.drawable.learning.tools.px

/**
 * @author jere
 *
 * Draws a mesh dotted background
 */
class BgGridDrawable : Drawable() {
    private val lineCount = 4
    private val columnCount = 5
    private val path: Path = Path()

    private val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
        style = Paint.Style.STROKE
        strokeWidth = 1f.px
        color = Color.parseColor("#80B5B5B5")
        pathEffect = DashPathEffect(floatArrayOf(5f.px, 5f.px, 5f.px, 5f.px), 0f)
    }

    private var pixelX = 0f
    private var pixelY = 0f
    override fun draw(canvas: Canvas) {
        for (i in 0 until lineCount) {
            pixelY = (bounds.top + bounds.height() / lineCount * i).toFloat()
            path.moveTo(bounds.left.toFloat(), pixelY)
            path.lineTo(bounds.right.toFloat(), pixelY)
            canvas.drawPath(path, paint)
        }
        for (i in 0 until columnCount) {
            pixelX = (bounds.left + bounds.width() / columnCount * i).toFloat()
            path.moveTo(pixelX, bounds.top.toFloat())
            path.lineTo(pixelX, bounds.bottom.toFloat())
            canvas.drawPath(path, paint)
        }
    }

    override fun setAlpha(alpha: Int) {
        paint.alpha = alpha
    }

    override fun getOpacity(): Int {
        return when (paint.alpha) {
            0xff -> PixelFormat.OPAQUE
            0x00 -> PixelFormat.TRANSPARENT
            else -> PixelFormat.TRANSLUCENT
        }
    }

    override fun setColorFilter(colorFilter: ColorFilter?) {
        paint.colorFilter = colorFilter
    }

}
package com.drawable.learning.fragment.custom.line_chart

import android.graphics.*
import android.graphics.drawable.Drawable
import com.drawable.learning.fragment.BaseFragment
import com.drawable.learning.databinding.FragmentLineChartBinding
import com.drawable.learning.tools.px

/**
 * @author jere
 *
 * drawing line chart
 */
class LineChartFragment : BaseFragment<FragmentLineChartBinding>() {
    override fun initView() {
        binding.customDrawableTv.apply {
            background = MyDrawable()
        }
    }

    class MyDrawable : Drawable() {
        private val bgDrawable = BgGridDrawable()

        private val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
            style = Paint.Style.FILL_AND_STROKE
            strokeWidth = 2f.px
            color = Color.parseColor("#96CDCD")
        }

        //hard code the points of the line chart
        private val path = Path()

        private fun calculatePath() {
            path.apply {
                reset()
                moveTo(bounds.left.toFloat(), bounds.bottom.toFloat())
                lineTo(bounds.left.toFloat(), 150f)
                lineTo(bounds.left.toFloat() + bounds.width() * 0.05f, 200f)
                lineTo(bounds.left.toFloat() + bounds.width() * 0.1f, 130f)
                lineTo(bounds.left.toFloat() + bounds.width() * 0.15f, 250f)
                lineTo(bounds.left.toFloat() + bounds.width() * 0.2f, 80f)
                lineTo(bounds.left.toFloat() + bounds.width() * 0.25f, 220f)
                lineTo(bounds.left.toFloat() + bounds.width() * 0.3f, 200f)
                lineTo(bounds.left.toFloat() + bounds.width() * 0.35f, 30f)
                lineTo(bounds.left.toFloat() + bounds.width() * 0.4f, 240f)
                lineTo(bounds.left.toFloat() + bounds.width() * 0.45f, 260f)
                lineTo(bounds.left.toFloat() + bounds.width() * 0.5f, 160f)
                lineTo(bounds.left.toFloat() + bounds.width() * 0.55f, 100f)
                lineTo(bounds.left.toFloat() + bounds.width() * 0.6f, 80f)
                lineTo(bounds.left.toFloat() + bounds.width() * 0.65f, 20f)
                lineTo(bounds.left.toFloat() + bounds.width() * 0.7f, 150f)
                lineTo(bounds.left.toFloat() + bounds.width() * 0.75f, 170f)
                lineTo(bounds.left.toFloat() + bounds.width() * 0.8f, 320f)
                lineTo(bounds.left.toFloat() + bounds.width() * 0.85f, 220f)
                lineTo(bounds.left.toFloat() + bounds.width() * 0.9f, 300f)
                lineTo(bounds.left.toFloat() + bounds.width() * 0.95f, 120f)
                lineTo(bounds.left.toFloat() + bounds.width() * 1f, 360f)
                lineTo(bounds.left.toFloat() + bounds.width() * 1f, bounds.bottom.toFloat())
            }
        }

        override fun draw(canvas: Canvas) {
            paint.shader = null
            calculatePath()

            canvas.drawRect(
                bounds.left.toFloat(),
                bounds.top.toFloat(),
                bounds.right.toFloat(),
                bounds.bottom.toFloat(),
                paint
            )

            bgDrawable.bounds = bounds
            bgDrawable.draw(canvas)


//            paint.apply {
//                style = Paint.Style.STROKE
//                strokeWidth = 2f.px
//                color = Color.WHITE
//            }
//            canvas.drawPath(path, paint)

            //draw the shader for path
            path.close()
            paint.apply {
                style = Paint.Style.FILL
                shader = LinearGradient(
                    bounds.left.toFloat(), bounds.top.toFloat(),
                    bounds.left.toFloat(), bounds.bottom.toFloat(),
                    intArrayOf(Color.parseColor("#FF6A6A"), Color.TRANSPARENT),
                    null,
                    Shader.TileMode.CLAMP
                )
            }
            canvas.drawPath(path, paint)
        }

        override fun setAlpha(alpha: Int) {
            paint.alpha = alpha
        }

        override fun getOpacity(): Int {
            return when (paint.alpha) {
                0xff -> PixelFormat.OPAQUE
                0x00 -> PixelFormat.TRANSPARENT
                else -> PixelFormat.TRANSLUCENT
            }
        }

        override fun setColorFilter(colorFilter: ColorFilter?) {
            paint.colorFilter = colorFilter
        }

    }

}
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".fragment.custom.line_chart.LineChartFragment">

    <TextView
        android:id="@+id/customDrawableTv"
        android:layout_width="300dp"
        android:layout_height="200dp"
        android:layout_marginTop="50dp"
        android:gravity="start|center_horizontal"
        android:text="@string/line_chart"
        android:textColor="@color/white"
        android:textSize="20sp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
package com.drawable.learning.fragment.custom.ball

import android.graphics.*
import android.graphics.drawable.Drawable
import com.drawable.learning.tools.px

/**
 * @author jere
 */
class BallDrawable : Drawable() {
    private val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
        style = Paint.Style.FILL
        color = Color.parseColor("#D2691E")
    }

    private val linePaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
        style = Paint.Style.STROKE
        strokeWidth = 1f.px
        color = Color.BLACK
    }

    override fun draw(canvas: Canvas) {
        val radius = bounds.width().toFloat() / 2
        canvas.drawCircle(
            bounds.width().toFloat() / 2,
            bounds.height().toFloat() / 2,
            radius,
            paint
        )

        //the vertical line of the ball
        canvas.drawLine(
            bounds.width().toFloat() / 2,
            0f,
            bounds.width().toFloat() / 2,
            bounds.height().toFloat(),
            linePaint
        )
        //the transverse line of the ball
        canvas.drawLine(
            0f,
            bounds.height().toFloat() / 2,
            bounds.width().toFloat(),
            bounds.height().toFloat() / 2,
            linePaint
        )

        val path = Path()
        val sinValue = kotlin.math.sin(Math.toRadians(45.0)).toFloat()
        //left curve
        path.moveTo(radius - sinValue * radius,
            radius - sinValue * radius
        )
        path.cubicTo(radius - sinValue * radius,
            radius - sinValue * radius,
            radius,
            radius,
            radius - sinValue * radius,
            radius + sinValue * radius
        )
        //right curve
        path.moveTo(radius + sinValue * radius,
            radius - sinValue * radius
        )
        path.cubicTo(radius + sinValue * radius,
            radius - sinValue * radius,
            radius,
            radius,
            radius + sinValue * radius,
            radius + sinValue * radius
        )
        canvas.drawPath(path, linePaint)
    }

    override fun setAlpha(alpha: Int) {
        paint.alpha = alpha
    }

    override fun getOpacity(): Int {
        return when (paint.alpha) {
            0xff -> PixelFormat.OPAQUE
            0x00 -> PixelFormat.TRANSPARENT
            else -> PixelFormat.TRANSLUCENT
        }
    }

    override fun setColorFilter(colorFilter: ColorFilter?) {
        paint.colorFilter = colorFilter
    }
}
package com.drawable.learning.fragment.custom.ball

import android.animation.AnimatorSet
import android.animation.ObjectAnimator
import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.Path
import android.util.AttributeSet
import android.view.Gravity
import android.view.MotionEvent
import android.widget.FrameLayout
import android.widget.ImageView
import com.drawable.learning.tools.px

/**
 * @author jere
 */
class CustomBallMovingSiteView(context: Context, attributeSet: AttributeSet?, defStyleAttr: Int) :
    FrameLayout(context, attributeSet, defStyleAttr) {

    constructor(context: Context) : this(context, null, 0)
    constructor(context: Context, attributeSet: AttributeSet?) : this(context, attributeSet, 0)

    private lateinit var ballContainerIv: ImageView
    private val ballDrawable = BallDrawable()
    private val radius = 50

    private var rippleAlpha = 0
    private var rippleRadius = 10f

    private var rawTouchEventX = 0f
    private var rawTouchEventY = 0f
    private var touchEventX = 0f
    private var touchEventY = 0f
    private var lastTouchEventX = 0f
    private var lastTouchEventY = 0f

    private val ripplePaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
        isDither = true
        color = Color.RED
        style = Paint.Style.STROKE
        strokeWidth = 2f.px
        alpha = rippleAlpha
    }

    init {
        initView(context, attributeSet)
    }

    private fun initView(context: Context, attributeSet: AttributeSet?) {
        //generate a ball by dynamic
        ballContainerIv = ImageView(context).apply {
            layoutParams = LayoutParams(radius * 2, radius * 2).apply {
                gravity = Gravity.CENTER
            }

            setImageDrawable(ballDrawable)
            //setBackgroundColor(Color.BLUE)
        }

        addView(ballContainerIv)
        setWillNotDraw(false)
    }

    override fun onTouchEvent(event: MotionEvent?): Boolean {
        lastTouchEventX = touchEventX
        lastTouchEventY = touchEventY

        event?.let {
            rawTouchEventX = it.x
            rawTouchEventY = it.y
            touchEventX = it.x - radius
            touchEventY = it.y - radius
        }

        ObjectAnimator.ofFloat(this, "rippleValue", 0f, 1f).apply {
            duration = 1000
            start()
        }

        val path = Path().apply {
            moveTo(lastTouchEventX, lastTouchEventY)
            quadTo(
                lastTouchEventX,
                lastTouchEventY,
                touchEventX,
                touchEventY
            )
        }

        val oaMoving = ObjectAnimator.ofFloat(ballContainerIv, "x", "y", path)
        val oaRotating = ObjectAnimator.ofFloat(ballContainerIv, "rotation", 0f, 360f)

        AnimatorSet().apply {
            duration = 1000
            playTogether(oaMoving, oaRotating)
            start()
        }

        return super.onTouchEvent(event)
    }

    fun setRippleValue(currentValue: Float) {
        rippleRadius = currentValue * radius
        rippleAlpha = ((1 - currentValue) * 255).toInt()
        invalidate()
    }

    override fun onDraw(canvas: Canvas?) {
        super.onDraw(canvas)
        ripplePaint.alpha = rippleAlpha
        //draw ripple for click event
        canvas?.drawCircle(rawTouchEventX, rawTouchEventY, rippleRadius, ripplePaint)
    }
}
/**
 * @author jere
 */
class MoveBallFragment : BaseFragment<FragmentMoveBallBinding>() {

    override fun initView() {

    }

}
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".fragment.custom.ball.MoveBallFragment">

    <com.drawable.learning.fragment.custom.ball.CustomBallMovingSiteView
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/try_click_screen"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>
package com.drawable.learning.fragment.custom.stroke

import android.graphics.*
import android.graphics.drawable.Drawable
import com.drawable.learning.tools.px

class BubbleChatRectDrawable : Drawable() {
    private val pathPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
        color = Color.parseColor("#ECF6ED")
        style = Paint.Style.FILL
    }

    private val padding = 2f.px

    private var paddingStart = 30f.px
    private var paddingBottom = 20f.px

    private val outPath = Path()
    private val inPath = Path()

    override fun draw(canvas: Canvas) {

        bounds.apply {

            //外边框
            pathPaint.color = Color.parseColor("#2BDEA8")
            canvas.drawRoundRect(
                left.toFloat(),
                top.toFloat(),
                right.toFloat(),
                bottom.toFloat() - paddingBottom,
                5f.px,
                5f.px,
                pathPaint
            )

            //内边框
            pathPaint.color = Color.parseColor("#ECF6ED")
            canvas.drawRoundRect(
                left.toFloat() + padding,
                top.toFloat() + padding,
                right.toFloat() - padding,
                bottom.toFloat() - paddingBottom - padding,
                5f.px,
                5f.px,
                pathPaint
            )

            outPath.reset()
            outPath.moveTo(bounds.left + paddingStart, bottom - paddingBottom - padding)
            outPath.lineTo(bounds.left + paddingStart + 10f.px, bottom - paddingBottom + 13f.px)
            outPath.lineTo(bounds.left + paddingStart + 20f.px, bottom - paddingBottom - padding)
            outPath.close()
            pathPaint.color = Color.parseColor("#2BDEA8")
            canvas.drawPath(outPath, pathPaint)

            inPath.reset()
            inPath.moveTo(
                bounds.left + paddingStart + padding,
                bottom - paddingBottom - padding * 2
            )
            inPath.lineTo(
                bounds.left + paddingStart + 10f.px,
                bottom - paddingBottom + 13f.px - padding * 2
            )
            inPath.lineTo(
                bounds.left + paddingStart - padding + 20f.px,
                bottom - paddingBottom - padding * 2
            )
            inPath.close()
            pathPaint.color = Color.parseColor("#ECF6ED")
            canvas.drawPath(inPath, pathPaint)
        }

    }

    override fun setAlpha(alpha: Int) {
        pathPaint.alpha = alpha
    }

    override fun setColorFilter(colorFilter: ColorFilter?) {
        pathPaint.colorFilter = colorFilter
    }

    override fun getOpacity(): Int {
        return when (pathPaint.alpha) {
            0xff -> PixelFormat.OPAQUE
            0x00 -> PixelFormat.TRANSPARENT
            else -> PixelFormat.TRANSLUCENT
        }
    }

}
package com.drawable.learning.fragment.custom.stroke

import android.animation.ValueAnimator
import android.content.Context
import android.graphics.*
import android.util.AttributeSet
import android.util.Log
import android.view.View
import android.view.animation.LinearInterpolator
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
import com.drawable.learning.tools.px

class HighlightAnimView(context: Context, attributeSet: AttributeSet?, defStyleAttr: Int) :
    View(context, attributeSet, defStyleAttr), DefaultLifecycleObserver {
    private val TAG = "HighlightAnimView"

    constructor(context: Context) : this(context, null, 0)
    constructor(context: Context, attributeSet: AttributeSet?) : this(context, attributeSet, 0)

    private var percent = 0f

    private var realValueAnimator: ValueAnimator =
        ValueAnimator.ofFloat(0f, 1f).apply {
            interpolator = LinearInterpolator()
            repeatCount = ValueAnimator.INFINITE
            repeatMode = ValueAnimator.RESTART
            duration = 600
            addUpdateListener {
                it.animatedValue.toString().toFloat().let { valuePercent ->
                    percent = valuePercent
//                    Log.e(TAG, "percent = $percent: ")
                }
                postInvalidate()
            }
        }

    private fun startAnim() {
        Log.e(TAG, "startAnim: realValueAnimator.isRunning = ${realValueAnimator.isRunning}")
        if (!realValueAnimator.isRunning) {
            realValueAnimator.start()
        }
    }

    private fun stopAnim() {
        Log.e(TAG, "stopAnim: realValueAnimator.isRunning = ${realValueAnimator.isRunning}")
        if (realValueAnimator.isRunning) {
            realValueAnimator.cancel()
        }

    }

    override fun onResume(owner: LifecycleOwner) {
        super.onResume(owner)
        Log.e(TAG, "onResume: ")
        startAnim()
    }

    override fun onPause(owner: LifecycleOwner) {
        super.onPause(owner)
        Log.e(TAG, "onPause: ")
        stopAnim()
    }

    override fun onStop(owner: LifecycleOwner) {
        super.onStop(owner)
        Log.e(TAG, "onStop: ")
        stopAnim()
    }


    private val dashPathEffect by lazy {
        DashPathEffect(floatArrayOf(5f.px, 5f.px, 5f.px, 5f.px), 1f)
    }

    private val linePaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
        color = Color.parseColor("#FFE524")
        style = Paint.Style.STROKE
        strokeWidth = 5f.px
        pathEffect = dashPathEffect
    }

    private val topPath = Path()
    private val rightPath = Path()
    private val bottomPath = Path()
    private val leftPath = Path()

    private val distance = 10f.px

    override fun onDraw(canvas: Canvas?) {
        super.onDraw(canvas)

        topPath.reset()
        topPath.moveTo(distance * percent, 0f)
        topPath.lineTo(width.toFloat(), 0f)
        canvas?.drawPath(topPath, linePaint)

        rightPath.reset()
        rightPath.moveTo(width.toFloat(), distance * percent)
        rightPath.lineTo(width.toFloat(), height.toFloat())
        canvas?.drawPath(rightPath, linePaint)

        bottomPath.reset()
        bottomPath.moveTo(width.toFloat() - distance * percent, height.toFloat())
        bottomPath.lineTo(0f, height.toFloat())
        canvas?.drawPath(bottomPath, linePaint)

        leftPath.reset()
        leftPath.moveTo(0f, height.toFloat() - distance * percent)
        leftPath.lineTo(0f, 0f)
        canvas?.drawPath(leftPath, linePaint)
    }

}
package com.drawable.learning.fragment.custom.stroke

import com.drawable.learning.databinding.FragmentHighlightAnimBinding
import com.drawable.learning.fragment.BaseFragment
import com.drawable.learning.tools.px

class HighlightAnimFragment : BaseFragment<FragmentHighlightAnimBinding>() {


    override fun initView() {
        val bubbleChatRectDrawable = BubbleChatRectDrawable()

        val intArray = IntArray(2)
        binding.testIv.getLocationOnScreen(intArray)

        bubbleChatRectDrawable.setBounds(
            intArray[0],
            intArray[1],
            (intArray[0] + 200f.px).toInt(),
            (intArray[1] + 100f.px).toInt()
        )
        binding.testIv.background = bubbleChatRectDrawable

        lifecycle.addObserver(binding.noteSav)
        lifecycle.addObserver(binding.sav)

    }

}
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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">

    <TextView
        android:id="@+id/noteTv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="20dp"
        android:padding="10dp"
        android:text="@string/please_note"
        android:textSize="16sp"
        android:textStyle="bold"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <com.drawable.learning.fragment.custom.stroke.HighlightAnimView
        android:id="@+id/noteSav"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:background="@android:color/transparent"
        app:layout_constraintBottom_toBottomOf="@id/noteTv"
        app:layout_constraintEnd_toEndOf="@id/noteTv"
        app:layout_constraintStart_toStartOf="@id/noteTv"
        app:layout_constraintTop_toTopOf="@id/noteTv" />

    <ImageView
        android:id="@+id/noteNickIv"
        android:layout_width="160dp"
        android:layout_height="100dp"
        android:scaleType="fitXY"
        android:src="@drawable/nick"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.3" />

    <com.drawable.learning.fragment.custom.stroke.HighlightAnimView
        android:id="@+id/sav"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout_margin="-20dp"
        android:background="@android:color/transparent"
        app:layout_constraintBottom_toBottomOf="@id/noteNickIv"
        app:layout_constraintEnd_toEndOf="@id/noteNickIv"
        app:layout_constraintStart_toStartOf="@id/noteNickIv"
        app:layout_constraintTop_toTopOf="@id/noteNickIv" />

    <ImageView
        android:id="@+id/testIv"
        android:layout_width="200dp"
        android:layout_height="100dp"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        android:layout_marginStart="100dp"
        android:layout_marginBottom="50dp"/>

</androidx.constraintlayout.widget.ConstraintLayout>

13.其他:

abstract class BaseFragment<B : ViewBinding> : Fragment() {

    private var _binding: B? = null
    val binding
        get() = _binding!!

    val TAG = this.javaClass.simpleName

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        val type = (javaClass.genericSuperclass as ParameterizedType).actualTypeArguments[0] as Class<B>
        val method = type.getDeclaredMethod(
            "inflate",
            LayoutInflater::class.java,
            ViewGroup::class.java,
            Boolean::class.java
        )
        _binding = method.invoke(null, layoutInflater, container, false) as B
        return binding.root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        initView()
    }

    abstract fun initView()

}

https://github.com/JereChen11/Drawable_Learning

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,125评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,293评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,054评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,077评论 1 291
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,096评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,062评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,988评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,817评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,266评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,486评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,646评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,375评论 5 342
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,974评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,621评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,796评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,642评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,538评论 2 352

推荐阅读更多精彩内容