編輯:關於Android編程
今天我們要實現的每一交互性的view,所以就繼承子view。
自定義view的套路,套路很深
這麼看來,自定義view的套路很清晰嘛。
我們看下今天的效果圖
我們按照套路來。
看下效果圖我們就知道因該需要哪些屬性。就不說了。
然後就是獲取我們的這些屬性,就是用TypedArray來獲取。當然是在構造中獲取,一般我們會復寫構造方法,少參數調用參數多的,然後走到參數最多的那個。
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是這個控件的默認樣式。
我們重寫這個方法主要是更具父看見的寬和高來設置自己的寬和高。
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { //計算寬和高 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; //計算半徑,減去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); }
首先該控件的寬和高肯定是一樣的,因為是個圓嘛。其實是寬和高與半徑和內邊距有關,這裡的內邊距,我們取上下左右最小的一個。寬和高也選擇取最小的。
this.width = this.height = min; 包含左右邊距。
resolveSize這個方法很好的為我們實現了我們想要的寬和高我慢看下源碼。
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); }
如果我們自己寫也是這樣寫。
最後通過setMeasuredDimension設置寬和高。
關於繪制有很多android 提供了很多API,這裡就不多說了。
繪制首先就是一些畫筆的初始化。
需要提一下繪制path路徑的畫筆設置為PorterDuff.Mode.SRC_IN模式,這個模式只顯示重疊的部分。
pathPaint = new Paint(Paint.ANTI_ALIAS_FLAG); pathPaint.setColor(progressColor); pathPaint.setDither(true); pathPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
我們要將所有的繪制 繪制到一個透明的bitmap上,然後將這個bitmap繪制到canvas上。
if (bitmap == null) { bitmap = Bitmap.createBitmap(this.width, this.height, Bitmap.Config.ARGB_8888); bitmapCanvas = new Canvas(bitmap); }
為了方便計算和繪制,我將坐標系平移padding的距離
bitmapCanvas.save(); //移動坐標系 bitmapCanvas.translate(minPadding, minPadding); // .... some thing bitmapCanvas.restore();
bitmapCanvas.drawCircle(radius, radius, radius, circlePaint);
3.2繪制PATH 路徑.
一是要實現波紋的左右飄,和上下的振幅慢慢的減小
繪制這個之前我們需要知道二階貝塞爾曲線的大致原理。
簡單的說就是知道:P1起始點,P2是終點,P1是控制點.利用塞爾曲線的公式就可以得道沿途的一些點,最後把點連起來就是喽。
下面這個圖片來於網絡:
在android-sdk裡提供了繪制貝塞爾曲線的函數rQuadTo方法
public void rQuadTo(float dx1, float dy1, float dx2, float dy2)
dx1:控制點X坐標,表示相對上一個終點X坐標的位移坐標,可為負值,正值表示相加,負值表示相減; dy1:控制點Y坐標,相對上一個終點Y坐標的位移坐標。同樣可為負值,正值表示相加,負值表示相減; dx2:終點X坐標,同樣是一個相對坐標,相對上一個終點X坐標的位移值,可為負值,正值表示相加,負值表示相減; dy2:終點Y坐標,同樣是一個相對,相對上一個終點Y坐標的位移值。可為負值,正值表示相加,負值表示相減;
這四個參數都是傳遞的都是相對值,相對上一個終點的位移值。
要實現振幅慢慢的減小我們可以調節控制點的y坐標即可,即:
float percent=progress * 1.0f / maxProgress;
就可以得到[0,1]的
一個閉區間,[0,1]這貨好啊,我喜歡,可以來做很多事情。
這樣我們就可以根據percent來調節控制點的y坐標了。
//根據直徑計算繪制貝賽爾曲線的次數
int count = radius * 4 / 60;
//控制-控制點y的坐標
float point = (1 - percent) * 15;
for (int i = 0; i < count; i++) {
path.rQuadTo(15, -point, 30, 0);
path.rQuadTo(15, point, 30, 0);
}
要實現左右波紋只需要控制閉合路徑的左上角的x坐標即可,當然也是根據percent喽。
大家可以結合下面這個圖來理解下上面的話。
path繪制的完整代碼片段。
//繪制PATH
//重置繪制路線
path.reset();
float percent=progress * 1.0f / maxProgress;
float y = (1 - percent) * radius * 2;
//移動到右上邊
path.moveTo(radius * 2, y);
//移動到最右下方
path.lineTo(radius * 2, radius * 2);
//移動到最左下邊
path.lineTo(0, radius * 2);
//移動到左上邊
// path.lineTo(0, y);
//實現左右波動,根據progress來平移
path.lineTo(-(1 -percent) * radius*2, y);
if (progress != 0.0f) {
//根據直徑計算繪制貝賽爾曲線的次數
int count = radius * 4 / 60;
//控制-控制點y的坐標
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繪制進度的文字
這個就比較簡單了,繪制在控件的中間即可。關於文字的坐標計算還是很好理解的。
//繪制文字
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);
哦,最後是實用方法,這裡我們不用thread+handler,我們用屬性動畫。
你懂的!!!,like
ObjectAnimator objectAnimator0 = ObjectAnimator.ofFloat(waveProgressView_0, "progress", 0f, 100f);
objectAnimator0.setDuration(3300);
objectAnimator0.setInterpolator(new LinearInterpolator());
objectAnimator0.start();
至此,也就實現了我們的效果。
最後給出源碼的下載地址:
https://github.com/ta893115871/WaveProgressView
XML初步今天我們來學習另一種非常重要的數據交換格式-XML。XML(Extensible Markup Language的縮寫,意為可擴展的標記語言),它是一種元標記
Android Studio 2.1中使用 Android SDK 6.0(API 23),加載融雲Demo時,報錯:解決辦法:Android 6.0(api 23)已經
Intent是一個消息傳遞對象,您可以使用它從其他應用組件請求操作。盡管 Intent 可以通過多種方式促進組件之間的通信,但其基本用例主要包括以下三個:1.啟動 Act
關於下拉刷新的實現原理我在上篇文章Android自定義控件之仿美團下拉刷新中已經詳細介紹過了,這篇文章主要介紹表盤的動畫實現原理汽車之家的下拉刷新分為三個狀態:第一個狀態