评论

Android 13种 Drawable,全面掌握!

原标题:Android 13种 Drawable,全面掌握!

本文作者

作者: Jere_Chen

链接:

1

前言

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

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

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

2

可绘制对象资源介绍

可绘制对象是指可在屏幕上绘制的图形,可以通过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

valbitmap = BitmapFactory.decodeResource(resources, R.drawable.nick)

valbitmapShape = 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中实现,我们同样可以通过代码来实现上面同样的效果。

valitemLeft = GradientDrawable.apply {

setColor(ContextCompat.getColor(requireContext, R.color.royal_blue))

setSize( 50.px, 50.px)

shape = GradientDrawable.OVAL

}

valitemCenter = GradientDrawable.apply {

setColor(ContextCompat.getColor(requireContext, R.color.indian_red))

shape = GradientDrawable.OVAL

}

valitemRight = GradientDrawable.apply {

setColor(ContextCompat.getColor(requireContext, R.color.yellow))

shape = GradientDrawable.OVAL

}

valarr = arrayOf(

ContextCompat.getDrawable(requireContext, R.drawable.nick)!!,

itemLeft,

itemCenter,

itemRight

)

valld = 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):会根据对象状态,使用多个不同的图像来表示同一个图形。

android:state_pressed="true" android:state_pressed="false"

介绍一下其中的属性:

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

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

其属性:

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

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

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

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

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

valsld = 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值大于或等于传递至方法的值的可绘制对象资源。

介绍一下其中的属性:

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

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

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

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

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

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

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

classLevelListDrawableFragment: BaseFragment< FragmentLevelListDrawableBinding> {

privatevallld bylazy {

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

}

}

privatefungetDrawable(id: Int) : Drawable {

return(ContextCompat.getDrawable(requireContext, id)

?: ContextCompat.getDrawable(requireContext, R.drawable.nick)) asDrawable

}

privatevallevelListDrawable bylazy {

ContextCompat.getDrawable(requireContext, R.drawable.level_list_drawable)

}

overridefuninitView{

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 {

overridefunonProgressChanged(

seekBar: SeekBar?,

progress: Int,

fromUser: Boolean

) {

levelListDrawable?.level = progress

lld.level = progress

Log.e(TAG, "onProgressChanged: progreess = $progress" )

}

overridefunonStartTrackingTouch(seekBar: SeekBar?) {

}

overridefunonStopTrackingTouch(seekBar: SeekBar?) {

}

})

}

}

}

效果图如下所示:

TransitionDrawable

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

介绍一下其中的属性:

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

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

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

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

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

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

classTransitionDrawableFragment: BaseFragment< FragmentTransitionDrawableBinding> {

privatevarisShow = false

privatelateinitvarmanualDrawable: TransitionDrawable

overridefuninitView{

binding.transitionDrawableInclude.apply {

valdrawableArray = arrayOf(

ContextCompat.getDrawable(requireContext, R.drawable.nick),

ContextCompat.getDrawable(requireContext, R.drawable.basketball)

)

manualDrawable = TransitionDrawable(drawableArray)

tv2.background = manualDrawable

}

}

privatefunsetTransition{

if(isShow) {

manualDrawable.reverseTransition( 3000)

} else{

manualDrawable.startTransition( 3000)

}

}

overridefunonResume{

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

< clipxmlns: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中实现,我们同样可以通过代码来实现上面同样的效果。

classClipDrawableFragment: BaseFragment< FragmentClipDrawableBinding> {

privatevalclipDrawable bylazy {

ContextCompat.getDrawable(requireContext, R.drawable.clip_drawable)

}

privatevalmanualClipDrawable bylazy {

ClipDrawable(

ContextCompat.getDrawable(requireContext, R.drawable.nick),

Gravity.CENTER,

ClipDrawable.VERTICAL

)

}

overridefuninitView{

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 {

overridefunonProgressChanged(

seekBar: SeekBar?,

progress: Int,

fromUser: Boolean

) {

clipDrawable?.level = progress

manualClipDrawable.level = progress

}

overridefunonStartTrackingTouch(seekBar: SeekBar?) {

}

overridefunonStopTrackingTouch(seekBar: SeekBar?) {

}

})

}

}

}

效果图如下所示:

ScaleDrawable

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

<?xml version="1.0" encoding="utf-8"?>

< scalexmlns: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中实现,我们同样可以通过代码来实现上面同样的效果。

valscaleDrawable = 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 {

overridefunonProgressChanged(

seekBar: SeekBar?,

progress: Int,

fromUser: Boolean

) {

tv1.background.level = progress

scaleDrawable.level = progress

Log.e(TAG, "onProgressChanged: progreess = $progress" )

}

overridefunonStartTrackingTouch(seekBar: SeekBar?) {

}

overridefunonStopTrackingTouch(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中实现,我们同样可以通过代码来实现上面同样的效果。

classShapeDrawableFragment: BaseFragment< FragmentShapeDrawableBinding> {

overridefuninitView{

valroundRectShape =

RoundRectShape(

floatArrayOf( 20f.px, 20f.px, 20f.px, 20f.px, 0f, 0f, 0f, 0f),

null,

null

)

binding.tv2.background = MyShapeDrawable(roundRectShape)

}

/**

* TODO:使用 GradientDrawable 效果更好

*/

classMyShapeDrawable(shape: Shape) : ShapeDrawable(shape) {

privatevalfillPaint = Paint.apply {

style = Paint.Style.FILL

color = Color.parseColor( "#4169E1")

}

privatevalstrokePaint = Paint.apply {

style = Paint.Style.STROKE

color = Color.parseColor( "#FFBB86FC")

strokeMiter = 10f

strokeWidth = 5f.px

pathEffect = DashPathEffect(floatArrayOf( 10f.px, 5f.px), 0f)

}

overridefunonDraw(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中实现,我们同样可以通过代码来实现上面同样的效果。

valgradientDrawable = GradientDrawable.apply {

shape= GradientDrawable.OVAL

gradientType = GradientDrawable.RADIAL_GRADIENT

colors = intArrayOf(Color.parseColor( "#00F5FF"), Color.parseColor( "#BBFFFF"))

gradientRadius = 100f.px

}

binding.tv2.background = gradientDrawable

效果图如下所示:

AnimationDrawable

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

<?xml version="1.0" encoding="utf-8"?>

< animation-listxmlns: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

效果图如下所示:

3

自定义 Drawable

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

classJcTestDrawable: Drawable{

overridefundraw(p0: Canvas) {

TODO( "Not yet implemented")

}

overridefunsetAlpha(p0: Int) {

TODO( "Not yet implemented")

}

overridefunsetColorFilter(p0: ColorFilter?) {

TODO( "Not yet implemented")

}

overridefungetOpacity: 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来进行绘制。

如下所示:

classBallDrawable: Drawable{

privatevalpaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {

style = Paint.Style.FILL

color = Color.parseColor( "#D2691E")

}

privatevallinePaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {

style = Paint.Style.STROKE

strokeWidth = 1f.px

color = Color.BLACK

}

overridefundraw(canvas: Canvas) {

valradius = 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

)

valpath = Path

valsinValue = 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)

}

overridefunsetAlpha(alpha: Int) {

paint.alpha = alpha

}

overridefungetOpacity: Int{

returnwhen(paint.alpha) {

0xff-> PixelFormat.OPAQUE

0x00-> PixelFormat.TRANSPARENT

else-> PixelFormat.TRANSLUCENT

}

}

overridefunsetColorFilter(colorFilter: ColorFilter?) {

paint.colorFilter = colorFilter

}

}

滚动

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

如下所示:

classCustomBallMovingSiteView(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)

privatelateinitvarballContainerIv: ImageView

privatevalballDrawable = BallDrawable

privatevalradius = 50

privatevarrippleAlpha = 0

privatevarrippleRadius = 10f

privatevarrawTouchEventX = 0f

privatevarrawTouchEventY = 0f

privatevartouchEventX = 0f

privatevartouchEventY = 0f

privatevarlastTouchEventX = 0f

privatevarlastTouchEventY = 0f

privatevalripplePaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {

isDither = true

color = Color.RED

style = Paint.Style.STROKE

strokeWidth = 2f.px

alpha = rippleAlpha

}

init {

initView(context, attributeSet)

}

privatefuninitView(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)

}

overridefunonTouchEvent(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

}

valpath = Path.apply {

moveTo(lastTouchEventX, lastTouchEventY)

quadTo(

lastTouchEventX,

lastTouchEventY,

touchEventX,

touchEventY

)

}

valoaMoving = ObjectAnimator.ofFloat(ballContainerIv, "x", "y", path)

valoaRotating = ObjectAnimator.ofFloat(ballContainerIv, "rotation", 0f, 360f)

AnimatorSet.apply {

duration = 1000

playTogether(oaMoving, oaRotating)

start

}

returnsuper.onTouchEvent(event)

}

funsetRippleValue(currentValue: Float) {

rippleRadius = currentValue * radius

rippleAlpha = (( 1- currentValue) * 255).toInt

invalidate

}

overridefunonDraw(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

4

总结

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

如果你想参考文章中所有源码,可以点击Github Drawable_Learning 进行查看,欢迎你给我点个小星星。

https://github.com/JereChen11/Drawable_Learning

参考文档:

可绘制对象资源

https://developer.android.com/guide/topics/resources/drawable-resource

其实分享文章的最大目的正是等待着有人指出我的错误,如果你发现哪里有错误,请毫无保留的指出即可,虚心请教。

另外,如果你觉得文章不错,对你有所帮助,请帮我点个赞,谢谢!你的每一次点赞就是对我说:加油!陌生人。这会带给我持续不断的动力,再次感谢!Peace~

最后推荐一下我做的网站,玩Android: wanandroid.com,包含详尽的知识体系、好用的工具,还有本公众号文章合集,欢迎体验和收藏!

吹爆系列:大厂是如何干掉OOM的?

RecyclerView复杂适配器的“终极形态”?代码更解耦

新技术又又又又叒叒叒来了?

点击关注我的公众号

如果你想要跟大家分享你的文章,欢迎投稿~

┏(^0^)┛明天见!返回搜狐,查看更多

责任编辑:

平台声明:该文观点仅代表作者本人,搜狐号系信息发布平台,搜狐仅提供信息存储空间服务。
阅读 ()
大家都在看
推荐阅读