详解Android 13种 Drawable的使用方法

这篇具有很好参考价值的文章主要介绍了详解Android 13种 Drawable的使用方法。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

前言

关于自定义View,相信大家都已经很熟悉了。今天,我想分享一下关于自定义View中的一部分,就是自定义Drawable。

Drawable 是可绘制对象的一个抽象类,相对比View来说,它更加的纯粹,只用来处理绘制的相关工作而不处理与用户的交互事件,所以适合用来处理背景的绘制。

在介绍自定义Drawable前,我们先来学习一下几种常见的Drawable。

可绘制对象资源介绍

可绘制对象是指可在屏幕上绘制的图形,可以通过getDrawable(int)等方法来获取,然后应用到 android:drawable 和 android:icon 等属性方法中。

下面介绍几种常见的可绘制对象,我会分三个步骤来介绍:

1. 介绍一下在XML中的使用方法(会举例说明)。

2. 然后介绍一下其属性方法。

3. 再以代码的形式来动态实现XML中的同样效果(会举例说明)。

BitmapDrawable

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

XML位图:通过XML文件来定义,指向位图文件,文件位于res/drawable/filename.xml,其文件名就是作为引用的资源 ID,如:R.drawable.filename。

详解Android 13种 Drawable的使用方法

关于 <bitmap> 属性:

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

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

定义平铺属性的值必须是以下值之一:

disabled:不平铺位图,默认值。

clamp:当着色器绘制范围超出其原边界时复制边缘颜色。

repeat:水平和垂直重复着色器的图像。

mirror:水平和垂直重复着色器的图像,交替镜像图像以使相邻图像始终相接。

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

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

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

bottom:将对象放在其容器底部,不改变其大小。

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

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

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

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

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

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

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

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

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

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

除了在 XML 文件中定义位图,我们也可以直接通过代码来实现,即BitmapDrawable。

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

效果图如下所示:

详解Android 13种 Drawable的使用方法

LayerDrawable

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

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

详解Android 13种 Drawable的使用方法

介绍一下其中的属性:

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

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

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

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

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

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

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

除了通过在XML中实现,我们同样可以通过代码来实现上面同样的效果。

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)
}
binding.tv2.background = ld

效果图如下所示:

详解Android 13种 Drawable的使用方法

StateListDrawable

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

详解Android 13种 Drawable的使用方法
详解Android 13种 Drawable的使用方法

介绍一下其中的属性:

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

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

其属性:

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

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

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

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

除了通过在XML中实现,我们同样可以通过代码来实现上面同样的效果。

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")
    }
}

LevelListDrawable

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

详解Android 13种 Drawable的使用方法

介绍一下其中的属性:

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

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

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

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

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

在将该 Drawable 应用到 View 后,就可以通过 setLevel() 或 setImageLevel() 更改级别。

除了通过在XML中实现,我们同样可以通过代码来实现上面同样的效果。

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?) {

                }
            })
        }

    }


}

效果图如下所示:

详解Android 13种 Drawable的使用方法

TransitionDrawable

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

详解Android 13种 Drawable的使用方法

介绍一下其中的属性:

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

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

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

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

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

除了通过在XML中实现,我们同样可以通过代码来实现上面同样的效果。

class TransitionDrawableFragment : BaseFragment<FragmentTransitionDrawableBinding>() {

    private var isShow = false
    private lateinit var manualDrawable: TransitionDrawable

    override fun initView() {
        binding.transitionDrawableInclude.apply {
            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) {
            manualDrawable.reverseTransition(3000)
        } else {
            manualDrawable.startTransition(3000)
        }
    }

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

}

效果图如下所示:

详解Android 13种 Drawable的使用方法

InsetDrawable

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

详解Android 13种 Drawable的使用方法

介绍一下其属性:

<inset>:必备。根元素。

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

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

除了通过在XML中实现,我们同样可以通过代码来实现上面同样的效果。

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

效果图如下所示:

详解Android 13种 Drawable的使用方法

ClipDrawable

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

<?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>

介绍一下其属性:

<clip>:必备。根元素。

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

android:clipOrientation:裁剪方向。

horizontal:水平裁剪。

vertical:垂直裁剪。

android:gravity:重力属性。

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

除了通过在XML中实现,我们同样可以通过代码来实现上面同样的效果。

class ClipDrawableFragment : BaseFragment<FragmentClipDrawableBinding>() {

    private val clipDrawable by lazy {
        ContextCompat.getDrawable(requireContext(), R.drawable.clip_drawable)
    }
    private val manualClipDrawable 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?) {

                }

            })
        }
    }

}

效果图如下所示:

详解Android 13种 Drawable的使用方法

ScaleDrawable

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

<?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% 等。

除了通过在XML中实现,我们同样可以通过代码来实现上面同样的效果。

val scaleDrawable = ScaleDrawable(
    ContextCompat.getDrawable(requireContext(), R.drawable.nick),
    Gravity.CENTER,
    1f,
    1f
)
binding.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?) {

        }

    })
}

效果图如下所示:

详解Android 13种 Drawable的使用方法

ShapeDrawable

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

详解Android 13种 Drawable的使用方法

介绍一下其属性:

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

2. android:shape:定义形状的类型。

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

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

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

ring:环形。

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

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

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

android:radius:尺寸。所有角的半径。如果想要设置单独某个角,可以使用android:topLeftRadius、android:topRightRadius、android:bottomLeftRadius、android:bottomRightRadius。

4. <padding>:设置内边距。

android:left:尺寸。设置左内边距。同样还有android:right、android:top、android:bottom供选择。

5. <size>:形状的大小。

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

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

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

android:color:颜色。

7. <stroke>:形状的笔画

android:width:尺寸。线宽。

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

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

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

除了通过在XML中实现,我们同样可以通过代码来实现上面同样的效果。

class ShapeDrawableFragment : BaseFragment<FragmentShapeDrawableBinding>() {

    override fun initView() {
        val roundRectShape =
            RoundRectShape(
                floatArrayOf(20f.px, 20f.px, 20f.px, 20f.px, 0f, 0f, 0f, 0f),
                null,
                null
            )
        binding.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")
            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)
        }
    }

}

效果图如下所示:

详解Android 13种 Drawable的使用方法

GradientDrawable

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

详解Android 13种 Drawable的使用方法

介绍一下其属性:

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:流线型渐变。

除了通过在XML中实现,我们同样可以通过代码来实现上面同样的效果。

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

效果图如下所示:

详解Android 13种 Drawable的使用方法

AnimationDrawable

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

<?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。

除了通过在XML中实现,我们同样可以通过代码来实现上面同样的效果。

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

效果图如下所示:

详解Android 13种 Drawable的使用方法

自定义 Drawable

介绍完了几种常见的可绘制对象资源,接下来我们进一步学习一下,如果进行自定义Drawable。

class JcTestDrawable : Drawable() {

    override fun draw(p0: Canvas) {
        TODO("Not yet implemented")
    }

    override fun setAlpha(p0: Int) {
        TODO("Not yet implemented")
    }

    override fun setColorFilter(p0: ColorFilter?) {
        TODO("Not yet implemented")
    }

    override fun getOpacity(): Int {
        TODO("Not yet implemented")
    }

}

从上述代码可以看出,我们需要继承Drawable(),然后实现4个方法,分别是:

1. setAlpha:为Drawable指定一个alpha值,0 表示完全透明,255 表示完全不透明。

2. setColorFilter:为Drawable指定可选的颜色过滤器。Drawable的draw绘图内容的每个输出像素在混合到 Canvas 的渲染目标之前将被颜色过滤器修改。传递 null 会删除任何现有的颜色过滤器。

3. getOpacity:返回Drawable的透明度,如下所示:

PixelFormat.TRANSLUCENT:半透明的。

PixelFormat.TRANSPARENT:透明的。

PixelFormat.OPAQUE:不透明的。

PixelFormat.UNKNOWN:未知。

4. draw:在边界内进行绘制(通过setBounds()),受alpha与colorFilter所影响。

接下来为大家举个例子。

举例:滚动篮球

功能介绍:当我们点击屏幕,篮球会滚向该坐标。

如下图所示:

详解Android 13种 Drawable的使用方法

实现步骤可以简单分为两步:

1. 绘制一个篮球。

2.获取到用户点击坐标,使用属性动画让篮球滚动到该位置。

绘制篮球

首先说绘制篮球这一步,这一步不需要与用户进行交互,所以我们采用自定义Drawable来进行绘制。

如下所示:

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
    }
}

滚动

绘制好篮球后,接着就是获取到用户的点击坐标,为了更好的举例,这里我放在自定义View中进行完成。

如下所示:

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)
    }
}

简单概括一下:首先我们会动态的生成一个View,将其背景设置为我们刚刚绘制的BallDrawable()来构成一个篮球。然后通过onTouchEvent()方法来获取到用户的点击坐标,再通过属性动画,让球滚动到该坐标。

更多额外代码请查看 Github Drawable_Leaning 之篮球滚动

https://github.com/JereChen11/Drawable_Learning/tree/main/app/src/main/java/com/drawable/learning/fragment/custom/ball

总结

通过这篇文章我们学习了几种常见的Drawable,也学习了自定义Drawable,我们知道Drawable只用来处理绘制的相关工作而不处理与用户的交互事件。所以,在我们复杂的自定义View中,我们可以将其进行拆分,像一些背景、装饰等完全就可以采取自定义Drawable来进行绘制。这样就能让我们复杂的自定义View变得图层更加层次清晰,代码可读性大大提升。

如果你想参考文章中所有源码,可以点击 Github Drawable_Learning 进行查看,欢迎你给我点个小星星。文章来源地址https://www.toymoban.com/news/detail-460549.html

https://github.com/JereChen11/Drawable_Learning

到了这里,关于详解Android 13种 Drawable的使用方法的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Optional 使用方法详解

    Optional的作用是什么?他都有哪些方法?阿里规范点名说尽量用Optional来避免空指针,那么什么场景用Optional?本篇文章围绕这三点来进行讲解。 到目前为止,臭名昭著的空指针异常是导致Java应用程序失败的最常见原因。以前,为了解决空指针异常,Google公司著名的Guava项目引

    2024年02月02日
    浏览(38)
  • Android TextView文字使用字体的3种方法

    Android给文字添加字体效果有3种方法: 1.直接在xml布局里面添加 先将字体文件复制到该位置  然后在xml中引用该文件 2.在activity里面设置(1)  先将字体文件复制到该位置 然后在activity里面设置该字体  3.在activity里面设置(2) 先将字体文件复制到该位置 然后在activity里面设

    2024年02月06日
    浏览(42)
  • foreach循环使用方法详解

    在前面的文章中, 千锋壹哥 给大家讲解了for、while、do-while三种循环结构,并讲解了如何跳出循环的几种方式,比如break、continue、return等。但是截止到目前,与循环相关的内容还没有完事,我们还有最后一哆嗦,今天 千锋壹哥 会给大家讲解另一种循环方式--增强for循环!这

    2023年04月24日
    浏览(45)
  • scanf函数使用方法详解

    1.首先我们需要知道, scanf函数是一种格式化输入函数(按照格式字符串的格式,从键盘上把数据输入到指定的变量之中) C语言中格式字符串 的一般形式为: %[标志][输出最小宽度][.精度][长度]类型 , 其中方括号[]中的项为可选项。 平时我们使用的最多的就是% + 类型。 s

    2024年02月13日
    浏览(35)
  • Android中保持屏幕常亮的三种使用方法

    目录 一、设置Flag 二、使用View类中的属性方法 1、Xml布局中设置: 2、功能代码中设置: 三、Wakelock 锁定机制 1、简介 2、作用 3、分类标志 4、使用方法 (1)在AndroidManifest.xml中设置权限 (2)通过PowerManager类获取WakeLock类的实例对象 (3)注意事项 (4)优化措施 (5)总结

    2024年02月07日
    浏览(44)
  • Android 签名方法---同时使用V1和V2签名

            V1签名(jar signature):         V1是Android7.0之前的签名方式,使用JDK中的jar signature方式对APK进行签名打包。签名完成后会生成一个META-INF文件夹,里面有三个文件:MANIFEST.MF,CERT.RSA,CERT.SF,是用来记录签名信息的。简单来说V1 签名是对压缩包中单个文件签名验证

    2024年02月03日
    浏览(28)
  • Android Jetpack Compose中使用字段验证的方法

    数据验证是创建健壮且用户友好的Android应用程序的关键部分。随着现代UI工具包Jetpack Compose的引入,处理字段验证变得更加高效和直观。在这篇文章中,我们将探讨如何在Android应用中使用Jetpack Compose进行字段验证。 字段验证是确保用户在各种输入字段中输入的数据符合特定

    2024年02月11日
    浏览(53)
  • GROUP BY 使用方法详解

    group by是开发中经常用到的SQL语句,从字面意思来看就是根据哪个字段或者哪几个字段对查询到的数据进行分组统计,既然是分组统计那如何分组呢?所以group by通常都是和聚合函数还有having一起使用。 select 聚合函数(字段1),字段2 from 表名 where 条件 group by 字段2,字段3 或者

    2024年02月13日
    浏览(35)
  • react map使用方法详解

    在React中, map() 方法是用于数组的常见方法之一,它可以用于处理数组并返回一个新的数组。在React中,经常使用 map() 方法来遍历数组,生成对应的组件列表或进行数据转换操作。 下面是 map() 方法在React中的使用方法详解: 假设有一个名为 data 的数组,我们要将其每个元素

    2024年02月15日
    浏览(39)
  • Unity ScriptableObject 使用方法详解

    ScriptableObject类 直接继承自Object类;它和 MonoBehaviour 是并列的,都继承自Object(但MonoBehaviour并不是直接继承自Object); ScriptableObject 就是一个数据容器,可以用来存储大量的数据,它是可序列化的,这个特点也正决定了它的主要用途;一个 主要用处就是通过将数据存储在Sc

    2024年02月08日
    浏览(40)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包