Android自定義view實(shí)現(xiàn)水波紋進(jìn)度球效果
今天我們要實(shí)現(xiàn)的這個(gè)view沒有太多交互性的view,所以就繼承view。
自定義view的套路,套路很深
1、獲取我們自定義屬性attrs(可省略)
2、重寫onMeasure方法,計(jì)算控件的寬和高
3、重寫onDraw方法,繪制我們的控件
這么看來(lái),自定義view的套路很清晰嘛。
我們看下今天的效果圖,其中一個(gè)是放慢的效果(時(shí)間調(diào)的長(zhǎng))
我們按照套路來(lái)。
一.自定義屬性
<declare-styleable name="WaveProgressView"> <attr name="radius" format="dimension|reference" /> <attr name="radius_color" format="color|reference" /> <attr name="progress_text_color" format="color|reference" /> <attr name="progress_text_size" format="dimension|reference" /> <attr name="progress_color" format="color|reference" /> <attr name="progress" format="float" /> <attr name="maxProgress" format="float" /> </declare-styleable>
看下效果圖我們就知道因該需要哪些屬性。就不說(shuō)了。
然后就是獲取我們的這些屬性,就是用TypedArray
來(lái)獲取。當(dāng)然是在構(gòu)造中獲取,一般我們會(huì)復(fù)寫構(gòu)造方法,少參數(shù)調(diào)用參數(shù)多的,然后走到參數(shù)最多的那個(gè)。
TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.WaveProgressView, defStyleAttr, R.style.WaveProgressViewDefault); radius = (int) a.getDimension(R.styleable.WaveProgressView_radius, radius); textColor = a.getColor(R.styleable.WaveProgressView_progress_text_color, 0); textSize = a.getDimensionPixelSize(R.styleable.WaveProgressView_progress_text_size, 0); progressColor = a.getColor(R.styleable.WaveProgressView_progress_color, 0); radiusColor = a.getColor(R.styleable.WaveProgressView_radius_color, 0); progress = a.getFloat(R.styleable.WaveProgressView_progress, 0); maxProgress = a.getFloat(R.styleable.WaveProgressView_maxProgress, 100); a.recycle();
注: R.style.WaveProgressViewDefault是這個(gè)控件的默認(rèn)樣式。
二.onMeasure測(cè)量
我們重寫這個(gè)方法主要是更具父看見的寬和高來(lái)設(shè)置自己的寬和高。
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { //計(jì)算寬和高 int exceptW = getPaddingLeft() + getPaddingRight() + 2 * radius; int exceptH = getPaddingTop() + getPaddingBottom() + 2 * radius; int width = resolveSize(exceptW, widthMeasureSpec); int height = resolveSize(exceptH, heightMeasureSpec); int min = Math.min(width, height); this.width = this.height = min; //計(jì)算半徑,減去padding的最小值 int minLR = Math.min(getPaddingLeft(), getPaddingRight()); int minTB = Math.min(getPaddingTop(), getPaddingBottom()); minPadding = Math.min(minLR, minTB); radius = (min - minPadding * 2) / 2; setMeasuredDimension(min, min); }
首先該控件的寬和高肯定是一樣的,因?yàn)槭莻€(gè)圓嘛。其實(shí)是寬和高與半徑和內(nèi)邊距有關(guān),這里的內(nèi)邊距,我們?nèi)∩舷伦笥易钚〉囊粋€(gè)。寬和高也選擇取最小的。
this.width = this.height = min;
包含左右邊距。
resolveSize
這個(gè)方法很好的為我們實(shí)現(xiàn)了我們想要的寬和高我慢看下源碼。
public static int resolveSizeAndState(int size, int measureSpec, int childMeasuredState) { final int specMode = MeasureSpec.getMode(measureSpec); final int specSize = MeasureSpec.getSize(measureSpec); final int result; switch (specMode) { case MeasureSpec.AT_MOST: if (specSize < size) { result = specSize | MEASURED_STATE_TOO_SMALL; } else { result = size; } break; case MeasureSpec.EXACTLY: result = specSize; break; case MeasureSpec.UNSPECIFIED: default: result = size; } return result | (childMeasuredState & MEASURED_STATE_MASK); }
如果我們自己寫也是這樣寫。
最后通過(guò)setMeasuredDimension
設(shè)置寬和高。
三.onDraw繪制
關(guān)于繪制有很多android 提供了很多API,這里就不多說(shuō)了。
繪制首先就是一些畫筆的初始化。
需要提一下繪制path路徑的畫筆設(shè)置為PorterDuff.Mode.SRC_IN
模式,這個(gè)模式只顯示重疊的部分。
pathPaint = new Paint(Paint.ANTI_ALIAS_FLAG); pathPaint.setColor(progressColor); pathPaint.setDither(true); pathPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
我們要將所有的繪制 繪制到一個(gè)透明的bitmap
上,然后將這個(gè)bitmap
繪制到canvas上。
if (bitmap == null) { bitmap = Bitmap.createBitmap(this.width, this.height, Bitmap.Config.ARGB_8888); bitmapCanvas = new Canvas(bitmap); }
為了方便計(jì)算和繪制,我將坐標(biāo)系平移padding
的距離
bitmapCanvas.save(); //移動(dòng)坐標(biāo)系 bitmapCanvas.translate(minPadding, minPadding); // .... some thing bitmapCanvas.restore();
3.1繪制圓
bitmapCanvas.drawCircle(radius, radius, radius, circlePaint);
3.2繪制PATH 路徑.
一是要實(shí)現(xiàn)波紋的左右飄,和上下的振幅慢慢的減小
繪制這個(gè)之前我們需要知道二階貝塞爾曲線的大致原理。
簡(jiǎn)單的說(shuō)就是知道:P1起始點(diǎn),P2是終點(diǎn),P1是控制點(diǎn).利用塞爾曲線的公式就可以得道沿途的一些點(diǎn),最后把點(diǎn)連起來(lái)就是嘍。
下面這個(gè)圖片來(lái)于網(wǎng)絡(luò):
二階貝塞爾曲線
在android-sdk里提供了繪制貝塞爾曲線的函數(shù)rQuadTo
方法
public void rQuadTo(float dx1, float dy1, float dx2, float dy2)
dx1:控制點(diǎn)X坐標(biāo),表示相對(duì)上一個(gè)終點(diǎn)X坐標(biāo)的位移坐標(biāo),可為負(fù)值,正值表示相加,負(fù)值表示相減;
dy1:控制點(diǎn)Y坐標(biāo),相對(duì)上一個(gè)終點(diǎn)Y坐標(biāo)的位移坐標(biāo)。同樣可為負(fù)值,正值表示相加,負(fù)值表示相減;
dx2:終點(diǎn)X坐標(biāo),同樣是一個(gè)相對(duì)坐標(biāo),相對(duì)上一個(gè)終點(diǎn)X坐標(biāo)的位移值,可為負(fù)值,正值表示相加,負(fù)值表示相減;
dy2:終點(diǎn)Y坐標(biāo),同樣是一個(gè)相對(duì),相對(duì)上一個(gè)終點(diǎn)Y坐標(biāo)的位移值??蔀樨?fù)值,正值表示相加,負(fù)值表示相減;
這四個(gè)參數(shù)都是傳遞的都是相對(duì)值,相對(duì)上一個(gè)終點(diǎn)的位移值。
要實(shí)現(xiàn)振幅慢慢的減小我們可以調(diào)節(jié)控制點(diǎn)的y坐標(biāo)即可,即:
float percent=progress * 1.0f / maxProgress;
就可以得到[0,1]的
一個(gè)閉區(qū)間,[0,1]這貨好啊,我喜歡,可以來(lái)做很多事情。
這樣我們就可以根據(jù)percent
來(lái)調(diào)節(jié)控制點(diǎn)的y坐標(biāo)了。
//根據(jù)直徑計(jì)算繪制貝賽爾曲線的次數(shù) int count = radius * 4 / 60; //控制-控制點(diǎn)y的坐標(biāo) float point = (1 - percent) * 15; for (int i = 0; i < count; i++) { path.rQuadTo(15, -point, 30, 0); path.rQuadTo(15, point, 30, 0); }
要實(shí)現(xiàn)左右波紋只需要控制閉合路徑的左上角的x坐標(biāo)即可,當(dāng)然也是根據(jù)percent
嘍。
大家可以結(jié)合下面這個(gè)圖來(lái)理解下上面的話。
path繪制的完整代碼片段。
//繪制PATH //重置繪制路線 path.reset(); float percent=progress * 1.0f / maxProgress; float y = (1 - percent) * radius * 2; //移動(dòng)到右上邊 path.moveTo(radius * 2, y); //移動(dòng)到最右下方 path.lineTo(radius * 2, radius * 2); //移動(dòng)到最左下邊 path.lineTo(0, radius * 2); //移動(dòng)到左上邊 // path.lineTo(0, y); //實(shí)現(xiàn)左右波動(dòng),根據(jù)progress來(lái)平移 path.lineTo(-(1 -percent) * radius*2, y); if (progress != 0.0f) { //根據(jù)直徑計(jì)算繪制貝賽爾曲線的次數(shù) int count = radius * 4 / 60; //控制-控制點(diǎn)y的坐標(biāo) float point = (1 - percent) * 15; for (int i = 0; i < count; i++) { path.rQuadTo(15, -point, 30, 0); path.rQuadTo(15, point, 30, 0); } } //閉合 path.close(); bitmapCanvas.drawPath(path, pathPaint);
3.3繪制進(jìn)度的文字
這個(gè)就比較簡(jiǎn)單了,繪制在控件的中間即可。關(guān)于文字的坐標(biāo)計(jì)算還是很好理解的。
//繪制文字 String text = progress + "%"; float textW = textPaint.measureText(text); Paint.FontMetrics fontMetrics = textPaint.getFontMetrics(); float baseLine = radius - (fontMetrics.ascent + fontMetrics.descent) / 2; bitmapCanvas.drawText(text, radius - textW / 2, baseLine, textPaint);
最后別忘了把我們的bitmap
繪制到canvas上。
canvas.drawBitmap(bitmap, 0, 0, null);
哦,最后是實(shí)用方法,這里我們不用thread+handler,我們用屬性動(dòng)畫。
你懂的?。?!,like
ObjectAnimator objectAnimator0 = ObjectAnimator.ofFloat(waveProgressView_0, "progress", 0f, 100f); objectAnimator0.setDuration(3300); objectAnimator0.setInterpolator(new LinearInterpolator()); objectAnimator0.start();
結(jié)束語(yǔ)
至此,也就實(shí)現(xiàn)了我們的效果。以上就是本文的全部?jī)?nèi)容,希望本文的內(nèi)容對(duì)大家開發(fā)Android能有所幫助。
- Android水波紋載入控件CircleWaterWaveView使用詳解
- android自定義WaveView水波紋控件
- Android自定義View控件實(shí)現(xiàn)多種水波紋漣漪擴(kuò)散效果
- Android自定義WaveProgressView實(shí)現(xiàn)水波紋加載需求
- Android自定義View實(shí)現(xiàn)水波紋效果
- Android自定義View實(shí)現(xiàn)水波紋引導(dǎo)動(dòng)畫
- Android 自定義view實(shí)現(xiàn)水波紋動(dòng)畫效果
- Android自定義View 實(shí)現(xiàn)水波紋動(dòng)畫引導(dǎo)效果
- Android項(xiàng)目實(shí)戰(zhàn)手把手教你畫圓形水波紋loadingview
- Android自定義View實(shí)現(xiàn)簡(jiǎn)單水波紋效果
相關(guān)文章
Android手勢(shì)操作示例(上/下/左/右的判斷)
這篇文章主要介紹了Android手勢(shì)操作方法,包含了針對(duì)上、下、左、右等方向的判斷,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2016-06-06Android開發(fā)導(dǎo)入項(xiàng)目報(bào)錯(cuò)Ignoring InnerClasses attribute for an anonym
今天小編就為大家分享一篇關(guān)于Android開發(fā)導(dǎo)入項(xiàng)目報(bào)錯(cuò)Ignoring InnerClasses attribute for an anonymous inner class的解決辦法,小編覺得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧2018-12-12Android如何禁止向EditText控件中輸入內(nèi)容詳解
EditText是接受用戶輸入信息的最重要控件。下面這篇文章主要給大家介紹了關(guān)于Android如何禁止向EditText控件中輸入內(nèi)容的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友可以參考借鑒,下面來(lái)一起看看吧。2017-09-09

Android 中 Tweened animation的實(shí)例詳解

Android HTTP網(wǎng)絡(luò)請(qǐng)求的異步實(shí)現(xiàn)