大家好,我是皇叔,最近開了一個安卓進階漲薪訓練營,可以幫助大家突破技術&職場瓶頸,從而度過難關,進入心儀的公司。
詳情見文章:沒錯!皇叔開了個訓練營
作者:Jere_Chenhttps://juejin.cn/post/7064364261685854245
關於自定義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。
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=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中實現,我們同樣可以通過代碼來實現上面同樣的效果。
classLevelListDrawableFragment:BaseFragment<FragmentLevelListDrawableBinding>(){privatevallldbylazy{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}privatevallevelListDrawablebylazy{ContextCompat.getDrawable(requireContext(),R.drawable.level_list_drawable)}overridefuninitView(){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{//initlevellevelListDrawable?.level=progresslld.level=progress//addlistenersetOnSeekBarChangeListener(object:SeekBar.OnSeekBarChangeListener{overridefunonProgressChanged(seekBar:SeekBar?,progress:Int,fromUser:Boolean){levelListDrawable?.level=progresslld.level=progressLog.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=falseprivatelateinitvarmanualDrawable:TransitionDrawableoverridefuninitView(){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中實現,我們同樣可以通過代碼來實現上面同樣的效果。
valinsetDrawable=InsetDrawable(ContextCompat.getDrawable(requireContext(),R.drawable.nick),0f,0f,0.5f,0.25f)binding.tv2.background=insetDrawable效果圖如下所示:

ClipDrawable
裁剪可繪製對象(ClipDrawable):根據level等級對可繪製對象進行裁剪,可以根據level與gravity來控制子可繪製對象的寬度與高度。
<?xmlversion="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>(){privatevalclipDrawablebylazy{ContextCompat.getDrawable(requireContext(),R.drawable.clip_drawable)}privatevalmanualClipDrawablebylazy{ClipDrawable(ContextCompat.getDrawable(requireContext(),R.drawable.nick),Gravity.CENTER,ClipDrawable.VERTICAL)}overridefuninitView(){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{//initlevelclipDrawable?.level=progressmanualClipDrawable.level=progress//addlistenersetOnSeekBarChangeListener(object:SeekBar.OnSeekBarChangeListener{overridefunonProgressChanged(seekBar:SeekBar?,progress:Int,fromUser:Boolean){clipDrawable?.level=progressmanualClipDrawable.level=progress}overridefunonStartTrackingTouch(seekBar:SeekBar?){}overridefunonStopTrackingTouch(seekBar:SeekBar?){}})}}}效果圖如下所示:

ScaleDrawable
縮放可繪製對象(ScaleDrawable):根據level等級來更改其可繪製對象大小。
<?xmlversion="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=scaleDrawablebinding.seekBar.apply{//initleveltv1.background.level=progressscaleDrawable.level=progress//addlistenersetOnSeekBarChangeListener(object:SeekBar.OnSeekBarChangeListener{overridefunonProgressChanged(seekBar:SeekBar?,progress:Int,fromUser:Boolean){tv1.background.level=progressscaleDrawable.level=progressLog.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.FILLcolor=Color.parseColor("#4169E1")}privatevalstrokePaint=Paint().apply{style=Paint.Style.STROKEcolor=Color.parseColor("#FFBB86FC")strokeMiter=10fstrokeWidth=5f.pxpathEffect=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.OVALgradientType=GradientDrawable.RADIAL_GRADIENTcolors=intArrayOf(Color.parseColor("#00F5FF"),Color.parseColor("#BBFFFF"))gradientRadius=100f.px}binding.tv2.background=gradientDrawable效果圖如下所示:

AnimationDrawable
動畫可繪製對象(AnimationDrawable):用於創建逐幀動畫的可繪製對象。
<?xmlversion="1.0"encoding="utf-8"?><animation-listxmlns: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中實現,我們同樣可以通過代碼來實現上面同樣的效果。
valanimationDrawable=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=animationDrawableanimationDrawable.start()效果圖如下所示:

介紹完了幾種常見的可繪製對象資源,接下來我們進一步學習一下,如果進行自定義Drawable。classJcTestDrawable:Drawable(){overridefundraw(p0:Canvas){TODO("Notyetimplemented")}overridefunsetAlpha(p0:Int){TODO("Notyetimplemented")}overridefunsetColorFilter(p0:ColorFilter?){TODO("Notyetimplemented")}overridefungetOpacity():Int{TODO("Notyetimplemented")}}
從上述代碼可以看出,我們需要繼承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.FILLcolor=Color.parseColor("#D2691E")}privatevallinePaint=Paint(Paint.ANTI_ALIAS_FLAG).apply{style=Paint.Style.STROKEstrokeWidth=1f.pxcolor=Color.BLACK}overridefundraw(canvas:Canvas){valradius=bounds.width().toFloat()/2canvas.drawCircle(bounds.width().toFloat()/2,bounds.height().toFloat()/2,radius,paint)//theverticallineoftheballcanvas.drawLine(bounds.width().toFloat()/2,0f,bounds.width().toFloat()/2,bounds.height().toFloat(),linePaint)//thetransverselineoftheballcanvas.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()//leftcurvepath.moveTo(radius-sinValue*radius,radius-sinValue*radius)path.cubicTo(radius-sinValue*radius,radius-sinValue*radius,radius,radius,radius-sinValue*radius,radius+sinValue*radius)//rightcurvepath.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.OPAQUE0x00->PixelFormat.TRANSPARENTelse->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:ImageViewprivatevalballDrawable=BallDrawable()privatevalradius=50privatevarrippleAlpha=0privatevarrippleRadius=10fprivatevarrawTouchEventX=0fprivatevarrawTouchEventY=0fprivatevartouchEventX=0fprivatevartouchEventY=0fprivatevarlastTouchEventX=0fprivatevarlastTouchEventY=0fprivatevalripplePaint=Paint(Paint.ANTI_ALIAS_FLAG).apply{isDither=truecolor=Color.REDstyle=Paint.Style.STROKEstrokeWidth=2f.pxalpha=rippleAlpha}init{initView(context,attributeSet)}privatefuninitView(context:Context,attributeSet:AttributeSet?){//generateaballbydynamicballContainerIv=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=touchEventXlastTouchEventY=touchEventYevent?.let{rawTouchEventX=it.xrawTouchEventY=it.ytouchEventX=it.x-radiustouchEventY=it.y-radius}ObjectAnimator.ofFloat(this,"rippleValue",0f,1f).apply{duration=1000start()}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=1000playTogether(oaMoving,oaRotating)start()}returnsuper.onTouchEvent(event)}funsetRippleValue(currentValue:Float){rippleRadius=currentValue*radiusrippleAlpha=((1-currentValue)*255).toInt()invalidate()}overridefunonDraw(canvas:Canvas?){super.onDraw(canvas)ripplePaint.alpha=rippleAlpha//drawrippleforclickeventcanvas?.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
參考文檔:
可繪製對象資源
https://developer.android.com/guide/topics/resources/drawable-resource
其實分享文章的最大目的正是等待着有人指出我的錯誤,如果你發現哪裡有錯誤,請毫無保留的指出即可,虛心請教。
另外,如果你覺得文章不錯,對你有所幫助,請幫我點個讚,謝謝!你的每一次點讚就是對我說:加油!陌生人。這會帶給我持續不斷的動力,再次感謝!Peace~
微信改了推送機制,真愛請星標本公號👇