当前位置: 首页 > news >正文

详解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。

关于 <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

效果图如下所示:

LayerDrawable

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

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

介绍一下其中的属性:

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

效果图如下所示:

StateListDrawable

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

介绍一下其中的属性:

<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 = sldsetOnClickListener {Log.e(TAG, "stateListDrawableTv2: isPressed = $isPressed")}
}

LevelListDrawable

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

介绍一下其中的属性:

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 = levelListDrawabletv2.setText(R.string.level_list_drawable)tv2.background = lld}binding.seekBar.apply {//init levellevelListDrawable?.level = progresslld.level = progress//add listenersetOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {override fun onProgressChanged(seekBar: SeekBar?,progress: Int,fromUser: Boolean) {levelListDrawable?.level = progresslld.level = progressLog.e(TAG, "onProgressChanged: progreess = $progress")}override fun onStartTrackingTouch(seekBar: SeekBar?) {}override fun onStopTrackingTouch(seekBar: SeekBar?) {}})}}}

效果图如下所示:

TransitionDrawable

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

介绍一下其中的属性:

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 = falseprivate lateinit var manualDrawable: TransitionDrawableoverride 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}}

效果图如下所示:

InsetDrawable

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

介绍一下其属性:

<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

效果图如下所示:

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 = clipDrawabletv2.setText(R.string.clip_drawable)tv2.background = manualClipDrawable}//level 默认级别为 0,即完全裁剪,使图像不可见。当级别为 10,000 时,图像不会裁剪,而是完全可见。binding.seekBar.apply {//init levelclipDrawable?.level = progressmanualClipDrawable.level = progress//add listenersetOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {override fun onProgressChanged(seekBar: SeekBar?,progress: Int,fromUser: Boolean) {clipDrawable?.level = progressmanualClipDrawable.level = progress}override fun onStartTrackingTouch(seekBar: SeekBar?) {}override fun onStopTrackingTouch(seekBar: SeekBar?) {}})}}}

效果图如下所示:

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 = scaleDrawablebinding.seekBar.apply {//init leveltv1.background.level = progressscaleDrawable.level = progress//add listenersetOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {override fun onProgressChanged(seekBar: SeekBar?,progress: Int,fromUser: Boolean) {tv1.background.level = progressscaleDrawable.level = progressLog.e(TAG, "onProgressChanged: progreess = $progress")}override fun onStartTrackingTouch(seekBar: SeekBar?) {}override fun onStopTrackingTouch(seekBar: SeekBar?) {}})
}

效果图如下所示:

ShapeDrawable

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

介绍一下其属性:

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.FILLcolor = Color.parseColor("#4169E1")}private val strokePaint = Paint().apply {style = Paint.Style.STROKEcolor = Color.parseColor("#FFBB86FC")strokeMiter = 10fstrokeWidth = 5f.pxpathEffect = 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)}}}

效果图如下所示:

GradientDrawable

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

介绍一下其属性:

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.OVALgradientType = GradientDrawable.RADIAL_GRADIENTcolors = intArrayOf(Color.parseColor("#00F5FF"), Color.parseColor("#BBFFFF"))gradientRadius = 100f.px
}
binding.tv2.background = gradientDrawable

效果图如下所示:

AnimationDrawable

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

<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"><itemandroid:drawable="@drawable/nick"android:duration="1000" /><itemandroid: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()

效果图如下所示:

自定义 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所影响。

接下来为大家举个例子。

举例:滚动篮球

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

如下图所示:

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

1. 绘制一个篮球。

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

绘制篮球

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

如下所示:

class BallDrawable : Drawable() {private val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {style = Paint.Style.FILLcolor = Color.parseColor("#D2691E")}private val linePaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {style = Paint.Style.STROKEstrokeWidth = 1f.pxcolor = Color.BLACK}override fun draw(canvas: Canvas) {val radius = bounds.width().toFloat() / 2canvas.drawCircle(bounds.width().toFloat() / 2,bounds.height().toFloat() / 2,radius,paint)//the vertical line of the ballcanvas.drawLine(bounds.width().toFloat() / 2,0f,bounds.width().toFloat() / 2,bounds.height().toFloat(),linePaint)//the transverse line of the ballcanvas.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 curvepath.moveTo(radius - sinValue * radius,radius - sinValue * radius)path.cubicTo(radius - sinValue * radius,radius - sinValue * radius,radius,radius,radius - sinValue * radius,radius + sinValue * radius)//right curvepath.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.OPAQUE0x00 -> PixelFormat.TRANSPARENTelse -> 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: ImageViewprivate val ballDrawable = BallDrawable()private val radius = 50private var rippleAlpha = 0private var rippleRadius = 10fprivate var rawTouchEventX = 0fprivate var rawTouchEventY = 0fprivate var touchEventX = 0fprivate var touchEventY = 0fprivate var lastTouchEventX = 0fprivate var lastTouchEventY = 0fprivate val ripplePaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {isDither = truecolor = Color.REDstyle = Paint.Style.STROKEstrokeWidth = 2f.pxalpha = rippleAlpha}init {initView(context, attributeSet)}private fun initView(context: Context, attributeSet: AttributeSet?) {//generate a ball by dynamicballContainerIv = 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 = touchEventXlastTouchEventY = touchEventYevent?.let {rawTouchEventX = it.xrawTouchEventY = it.ytouchEventX = it.x - radiustouchEventY = it.y - radius}ObjectAnimator.ofFloat(this, "rippleValue", 0f, 1f).apply {duration = 1000start()}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 = 1000playTogether(oaMoving, oaRotating)start()}return super.onTouchEvent(event)}fun setRippleValue(currentValue: Float) {rippleRadius = currentValue * radiusrippleAlpha = ((1 - currentValue) * 255).toInt()invalidate()}override fun onDraw(canvas: Canvas?) {super.onDraw(canvas)ripplePaint.alpha = rippleAlpha//draw ripple for click eventcanvas?.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://github.com/JereChen11/Drawable_Learning

相关文章:

详解Android 13种 Drawable的使用方法

前言关于自定义View&#xff0c;相信大家都已经很熟悉了。今天&#xff0c;我想分享一下关于自定义View中的一部分&#xff0c;就是自定义Drawable。Drawable 是可绘制对象的一个抽象类&#xff0c;相对比View来说&#xff0c;它更加的纯粹&#xff0c;只用来处理绘制的相关工作…...

MakeFile教程

前言 当我们需要编译一个比较大的项目时&#xff0c;编译命令会变得越来越复杂&#xff0c;需要编译的文件越来越多。其 次就是项目中并不是每一次编译都需要把所有文件都重新编译&#xff0c;比如没有被修改过的文件则不需要重 新编译。工程管理器就帮助我们来优化这两个问题…...

Spring使用mongoDB步骤

1. 在Linux系统使用docker安装mongoDB 1.1. 安装 在docker运行的情况下&#xff0c;执行下述命令。 docker run \ -itd \ --name mongoDB \ -v mongoDB_db:/data/db \ -p 27017:27017 \ mongo:4.4 \ --auth执行docker ps后&#xff0c;出现下列行&#xff0c;即表示mongoDB安…...

【蓝牙mesh】access层(接入层)协议介绍

【蓝牙mesh】access层&#xff08;接入层&#xff09;协议介绍 Access层简介 Access层定义了应用层如何使用upper协议层的接口&#xff0c;它不仅定义了应用层的格式&#xff0c;还定义了应用数据在upper层的加密和解密。当收到下层的数据包时&#xff0c;它会检查数据的netke…...

【一天一门编程语言】JavaScript 语言程序设计极简教程

JavaScript 语言程序设计极简教程 用 markdown 格式输出答案。 不少于3000字。细分到2级目录。 一、JavaScript 简介 1.1 什么是 JavaScript JavaScript 是一种由Netscape的LiveScript发展而来的脚本语言&#xff0c;是一种动态类型、弱类型、基于原型的语言&#xff0c;内…...

CMake调试器出炉:调试你的CMake脚本

Visual Studio 开发团队一直和 Kitware 紧密合作&#xff0c;致力于开发一个用于调试 CMake 脚本的调试器。 我们将继续这个工作&#xff0c;以便开发人员社区可以通过添加新功能和对其他 DAP 功能的支持来共同改进它。 我们很高兴地宣布&#xff0c;CMake 调试器的预览版现在…...

题解 # 二维矩阵最大矩形问题#

题目&#xff1a; 小明有一张N*M的方格纸&#xff0c;且部分小方格中涂了颜色&#xff0c;部分小方格还是空白。 给出N (2<Ns30)和M(2sMs30)的值&#xff0c;及每个小方格的状态(&#xff08;被涂了颜色小方格用数字1表示&#xff0c;空白小方格用数字0表示)&#xff1b; 请…...

奔四的路上,依旧倔强的相信未来

本文首发于2022年12月31日 原标题: 奔四的路上,依旧倔强的相信未来!–我的2022年终总结 读大学那几年,一直保持着写日记和做计划的习惯,还记得大学毕业刚开始打工的时候,我的床头的墙上一定会画一张表,写上一个月的计划和一周的计划 计划也会有完不成的时候,但加深了…...

61 k8s + rancher + karmada容器化部署

文章目录 一、什么是rancher二、为什么使用rancher三、rancher安装1、细部介绍四、图形化操作1、执行2、补充五、 karmada1、官网2、细部介绍一、什么是rancher 1、Rancher 是一个 全栈式 的 Kubernetes 容器管理平台,为你提供在任何地方都能成功运行 Kubernetes 的工具。 二…...

Vue3的新特性变化,上手指南!

文章目录一、Vue3相比Vue2&#xff0c;更新了什么变化&#xff1f;二、Proxy 代理响应式原理三、组合式 API (Composition API)setup()函数:ref()函数reactive()函数组合式 setup 中使用 Props 父向子传递参数计算属性watch&#xff08;数据监视&#xff09;watchEffect&#x…...

OllyDbg

本文通过吾爱破解论坛上提供的OllyDbg版本为例&#xff0c;讲解该软件的使用方法 F2对鼠标所处的位置打下断点&#xff0c;一般表现为鼠标所属地址位置背景变红F3加载一个可执行程序&#xff0c;进行调试分析&#xff0c;表现为弹出打开文件框F4执行程序到光标处F5缩小还原当前…...

记一次键盘维修,最终修复

我的笔记本是华硕的K45VD&#xff0c;是我亲人在高二那年买的&#xff0c;之后就一直给我用&#xff0c;距今2023年已经差不多13年&#xff0c;它承载了太多记忆。在大学期间也给它升级&#xff0c;重要的零部件基本没问题。只在大学时加了8G内存和一个240G固态&#xff0c;换了…...

LeetCode 155.最小栈

设计一个支持 push &#xff0c;pop &#xff0c;top 操作&#xff0c;并能在常数时间内检索到最小元素的栈。实现 MinStack 类:MinStack() 初始化堆栈对象。void push(int val) 将元素val推入堆栈。void pop() 删除堆栈顶部的元素。int top() 获取堆栈顶部的元素。int getMin(…...

C++学习笔记-重载运算符和重载函数

重载的运算符是带有特殊名称的函数&#xff0c;函数名是由关键字 operator 和其后要重载的运算符符号构成的。与其他函数一样&#xff0c;重载运算符有一个返回类型和一个参数列表。 C 允许在同一作用域中的某个函数和运算符指定多个定义&#xff0c;分别称为函数重载和运算符重…...

Java —— JDBC

引入mysql链接 创建表格 Navicat查看建表代码双击要打开的表&#xff0c;右侧顶端点击ddl小方框 CREATE TABLE s (id int(6) NOT NULL,name varchar(20) COLLATE utf8_bin DEFAULT NULL,age int(11) DEFAULT NULL,gender varchar(2) COLLATE utf8_bin DEFAULT NULL,dept var…...

备战金三银四,熬夜半个月汇集大厂 Java 岗 1600 页面试真题

如果你不停地加班。却很少冒险&#xff0c;也很少学习&#xff0c;那你极大可能会陷入到内卷中。 为什么这么说呢&#xff1f;我们先来捋清楚「内卷」的概念&#xff1a; 「内卷化」简而言之就是&#xff1a;日复一日&#xff0c;越混越掉坑里。 所谓内卷化&#xff0c;指一种…...

9、面向对象、泛型与反射

目录一、构造函数二、继承与重写三、泛型四、反射1 - 反射的基本概念2 - 反射的基础数据类型3 - 反射APIa - 获取Type类型b - 获取struct成员变量的信息c - 获取struct成员方法的信息d - 获取函数的信息e - 判断类型是否实现了某接口五、reflect.Valuea - 空value判断b - 获取V…...

Python使用百度通用API进行翻译

想汉化StarUML这个软件&#xff0c;感觉工作量太大&#xff0c;想要用Python自动翻译。 结果网上找的一个个用不了&#xff0c;或者用一会儿就断。 于是自己手写了一个简单的&#xff0c;只有两个类&#xff1a;APIConfig和Translater 使用 demo my_api_config APIConfig(…...

JavaScript 弹窗

文章目录JavaScript 弹窗警告框确认框提示框换行JavaScript 弹窗 可以在 JavaScript 中创建三种消息框&#xff1a;警告框、确认框、提示框。 警告框 警告框经常用于确保用户可以得到某些信息。 当警告框出现后&#xff0c;用户需要点击确定按钮才能继续进行操作。 语法 wi…...

408复试day1

文章目录数据结构计算机组成原理操作系统计算机网络数据结构 深度优先遍历DFS&#xff1a; 首先访问图中起始顶点v&#xff0c;然后由v出发&#xff0c;访问与v邻接且未被访问的顶点v1&#xff0c;再访问与v1相邻的且未被访问的顶点v2……重复上述过程。当不能再继续向下访问时…...

gdb openocd jlink arm-a9调试

连接关系是这样的&#xff1a;gdb —> openocd —>&#xff08;这里需要两个xx.cfg配置文件&#xff09; jlink —> arm-a9板子 具体流程是这样的&#xff1a; 给jlink&#xff08;硬件调试器&#xff09;安装驱动&#xff0c;用USB Driver Tool这个软件&#xff0c;…...

Leetcode Solutions - Part 2

1. Two Sum 给定一个整数数组 nums 和一个整数目标值 target&#xff0c;请你在该数组中找出 和为目标值 target 的那 两个 整数&#xff0c;并返回它们的数组下标。 你可以假设每种输入只会对应一个答案。但是&#xff0c;数组中同一个元素在答案里不能重复出现。 你可以按…...

外盘国际期货:围观那些奇葩的国际节日?

围观那些奇葩的国际节日&#xff1f; 2月24日&#xff1a;世界讨厌香菜日&#xff0c;号召全世界所以讨厌香菜的人一起抵制香菜&#xff0c;2016年世界反香菜联盟 3月21日&#xff1a;世界睡眠日&#xff0c;唤起全民对睡眠重要性的认识&#xff0c;2001年国际精神卫生组织 …...

Kubernetes之服务的基本管理

svc是kubernetes最核心的概念&#xff0c;通过创建Service&#xff0c;可以为一组具有相同功能的容器应用提供一个统一的入口地址&#xff0c;并将请求进行负载分发到后端的各个容器应用上。pod生命周期短不稳定&#xff0c;pod异常后新生成的pod的IP会发生变化&#xff0c;通过…...

TimeWheel时间轮算法原理及实现(附源码)

时间轮算法原理及实现前言1.时间轮核心2.简单定时器3.任务队列4.优化任务队列5.简单时间轮6.多层时间轮前言 在各种业务场景中,我们总是会需要一些定时进行一些操作,这些操作可能是需要在指定的某个时间点操作,也可能是每过一个固定的时间间隔后进行操作,这就要求我们需要有一个…...

【蓝牙mesh】Upper协议层介绍

【蓝牙mesh】Upper协议层介绍 Upper层简介 Upper协议层用于处理网络层以上的功能&#xff0c;包括设备的应用层数据、安全、群组等信息&#xff0c;是实现蓝牙mesh应用功能的关键协议之一。Upper层接收来自Access层的数据或者是Upper层自己生成的Control数据&#xff0c;并且将…...

NEXUS 6P刷机安装Edxposed

刷机 abd等工具下载&#xff1a; https://developer.android.com/studio/releases/platform-tools?hlzh-cn 下载后配置环境变量 镜像下载&#xff1a; https://developers.google.com/android/images?hlzh-cn#angler Magisk下载 GitHub - topjohnwu/Magisk: The Magic M…...

web、ES、vue等知识总结

1&#xff1a;Vue2和vue3的区别: 一个、两个2&#xff1a;Vue3.x为什么要用Proxy来代替Object.defineProperty?3&#xff1a;Proxy4&#xff1a;一些知识点的原理全面5&#xff1a;vue3中加入eslint和prettier6&#xff1a;详解vue中的diff算法7:响应式布局的常用解决方案对比…...

数据库第一章(王珊课后习题)

文章目录1.试述数据、数据库、数据库管理系统、数据库系统的概念2.使用数据库系统有什么好处&#xff1f;3.试述文件系统与数据库系统的区别和联系。4.试述数据库系统的特点5.数据库管理系统的主要功能有哪些&#xff1f;6.什么是概念模型?试叙述概念模型的作用7.解释实体、实…...

设计模式(十一)----结构型模式之装饰者模式

1、概述 我们先来看一个快餐店的例子。 快餐店有炒面、炒饭这些快餐&#xff0c;可以额外附加鸡蛋、火腿、培根这些配菜&#xff0c;当然加配菜需要额外加钱&#xff0c;每个配菜的价钱通常不太一样&#xff0c;那么计算总价就会显得比较麻烦。 使用继承的方式存在的问题&…...