Android自定義view流程
阿新 • • 發佈:2021-12-14
Android自定義view流程,主要目的是總結實現過程中的思路以及一些需要注意的地方。
首先,我們先來看一張效果圖:
實現邏輯
- 重新指定View寬高
- 繪製外圓圓弧背景及進度
- 繪製中圓圓弧背景及進度
- 繪製內圓圓弧背景及進度
知識點
onMeasure
- 用於測量View的大小。建立時View無需測量,當將這個View放入一個容器(父控制元件)時候才需要測量,而測量方法由父控制元件呼叫。當控制元件的父控制元件要放置該控制元件的時候,父控制元件會呼叫子控制元件的onMeasure方法確定子控制元件需要的空間大小,然後傳入widthMeasureSpec和heightMeasureSpec來告訴子控制元件可獲得的空間大小,子控制元件通過這兩個引數就可以測量自身的寬高了。
setMeasuredDimension
- 用於重新設定View寬高
Canvas#drawArc(RectF oval, float startAngle, float sweepAngle, boolean useCenter, Paint paint)
- 繪製以oval為邊界的圓弧
onDraw
- 用來確定View長什麼樣。onDraw繪製過程如下:
- Draw the background(繪製背景)
- If necessary, save the canvas’ layers to prepare for fading(如果需要,為儲存這層為邊緣的滑動效果作準備)
- Draw view’s content(繪製內容)
- Draw children(繪製子View)
- If necessary, draw the fading edges and restore layers(如果需要,繪製邊緣效果並且儲存圖層)
- Draw decorations (scrollbars for instance)(繪製邊框,比如scrollbars,TextView)
主要程式碼
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// 根據父控制元件傳遞的widthMeasureSpec和heightMeasureSpec呼叫MeasureSpec.getSize測量自身寬高
int measureWidth = MeasureSpec.getSize(widthMeasureSpec);
int measureHeight = MeasureSpec.getSize(heightMeasureSpec);
int finalWidth = measureWidth;
int finalHeight = measureHeight;
// 根據自身寬高重新計算新的寬高,使新的寬高比為2:1
if (measureWidth >= measureHeight * 2) {
finalWidth = measureHeight * 2;
} else {
finalHeight = measureWidth / 2;
}
// 設定View新的寬高
setMeasuredDimension(finalWidth, finalHeight);
}
/**
* 繪製圓弧
* @param canvas
* @param progress 進度
* @param color 進度顏色
* @param radius 圓弧半徑
*/
private void drawArc(Canvas canvas, float progress, int color, float radius){
// 圓心
mXCenter = getWidth() / 2;
mYCenter = getHeight() ;
mPaint.setColor(mBackgroundArcColor);
// 構造邊界矩形
RectF oval = new RectF();
oval.left = (mXCenter - radius);
oval.top = (mYCenter - radius);
oval.right = mXCenter + radius;
oval.bottom = radius * 2 + (mYCenter - radius);
//繪製圓弧背景
canvas.drawArc(oval, -180, 180, false, mPaint);
//繪製圓弧進度
float showDegree = progress / 100 * 180;
mPaint.setColor(color);
canvas.drawArc(oval, -180, showDegree, false, mPaint);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 初始半徑
float originalRadius = getWidth() * .5f;
// 畫筆半寬
float halfArcStokeWidth = mArcStrokeWidth * .5f;
// 外圓環半徑=初始半徑-畫筆半寬
float outSideArcRadius = originalRadius - halfArcStokeWidth;
drawArc(canvas, mOutsideProgress, mOutsideArcColor, outSideArcRadius);
// 中圓環半徑=外圓的半徑-圓環偏移值-畫筆半寬
float middleArcRadius = outSideArcRadius - mArcOffset - halfArcStokeWidth;
drawArc(canvas, mMiddleProgress, mMiddleArcColor, middleArcRadius);
// 內圓環半徑=中圓的半徑-圓環偏移值-畫筆半寬
float insideArcRadius = middleArcRadius - mArcOffset - halfArcStokeWidth;
drawArc(canvas, mInsideProgress, mInsideArcColor, insideArcRadius);
}
全部程式碼
ThreeArcView.java
public class ThreeArcView extends View {
//圓弧畫筆
private Paint mPaint;
//背景圓環顏色
private int mBackgroundArcColor;
//外圓環顏色
private int mOutsideArcColor;
//中圓環顏色
private int mMiddleArcColor;
//內圓環顏色
private int mInsideArcColor;
//外圓展示弧度
private float mOutsideProgress;
//中圓展示弧度
private float mMiddleProgress;
//內圓展示弧度
private float mInsideProgress;
//圓弧寬度
private float mArcStrokeWidth;
//圓偏移值
private float mArcOffset;
// 圓心x座標
private int mXCenter;
// 圓心y座標
private int mYCenter;
public ThreeArcView(Context context) {
this(context, null);
}
public ThreeArcView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public ThreeArcView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initAttrs(context, attrs);
initVariable();
}
private void initAttrs(Context context, AttributeSet attrs) {
TypedArray typeArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.ThreeArcView, 0, 0);
mArcStrokeWidth = typeArray.getDimension(R.styleable.ThreeArcView_ts_strokeWidth, dp2px(context, 20));
// 圓環背景顏色
mBackgroundArcColor = typeArray.getColor(R.styleable.ThreeArcView_ts_bgArcColor, 0xFFFFFFFF);
// 圓環顏色
mOutsideArcColor = typeArray.getColor(R.styleable.ThreeArcView_ts_outsideBgColor, 0xFFFFFFFF);
mMiddleArcColor = typeArray.getColor(R.styleable.ThreeArcView_ts_middleBgColor, 0xFFFFFFFF);
mInsideArcColor = typeArray.getColor(R.styleable.ThreeArcView_ts_insideBgColor, 0xFFFFFFFF);
// 圓進度
mOutsideProgress = typeArray.getFloat(R.styleable.ThreeArcView_ts_outsideProgress, 0f);
mMiddleProgress = typeArray.getFloat(R.styleable.ThreeArcView_ts_middleProgress, 0f);
mInsideProgress = typeArray.getFloat(R.styleable.ThreeArcView_ts_insideProgress, 0f);
// 圓環偏移值
mArcOffset = typeArray.getDimension(R.styleable.ThreeArcView_ts_radiusOffset, dp2px(context, 20));
typeArray.recycle();
// 偏移值不能小於畫筆寬度的一半,否則會發生覆蓋
if (mArcOffset < mArcStrokeWidth / 2){
mArcOffset = mArcStrokeWidth / 2;
}
}
private void initVariable() {
//背景圓弧畫筆設定
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(mArcStrokeWidth);
mPaint.setStrokeCap(Paint.Cap.ROUND);//開啟顯示邊緣為圓形
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// 分別獲取期望的寬度和高度,並取其中較小的尺寸作為該控制元件的寬和高
int measureWidth = MeasureSpec.getSize(widthMeasureSpec);
int measureHeight = MeasureSpec.getSize(heightMeasureSpec);
//裁剪出一個 (寬:高) = (2:1) 的矩形
int finalWidth = measureWidth;
int finalHeight = measureHeight;
if (measureWidth >= measureHeight * 2) {
finalWidth = measureHeight * 2;
} else {
finalHeight = measureWidth / 2;
}
setMeasuredDimension(finalWidth, finalHeight);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 初始半徑
float originalRadius = getWidth() * .5f;
// 畫筆半寬
float halfArcStokeWidth = mArcStrokeWidth * .5f;
// 外圓環半徑=初始半徑-畫筆半寬
float outSideArcRadius = originalRadius - halfArcStokeWidth;
drawArc(canvas, mOutsideProgress, mOutsideArcColor, outSideArcRadius);
// 中圓環半徑=外圓的半徑-圓環偏移值-畫筆半寬
float middleArcRadius = outSideArcRadius - mArcOffset - halfArcStokeWidth;
drawArc(canvas, mMiddleProgress, mMiddleArcColor, middleArcRadius);
// 內圓環半徑=中圓的半徑-圓環偏移值-畫筆半寬
float insideArcRadius = middleArcRadius - mArcOffset - halfArcStokeWidth;
drawArc(canvas, mInsideProgress, mInsideArcColor, insideArcRadius);
}
/**
* 繪製圓弧
* @param canvas
* @param progress 進度
* @param color 進度顏色
* @param radius 圓弧半徑
*/
private void drawArc(Canvas canvas, float progress, int color, float radius){
// 圓心
mXCenter = getWidth() / 2;
mYCenter = getHeight() ;
mPaint.setColor(mBackgroundArcColor);
// 構造邊界矩形
RectF oval = new RectF();
oval.left = (mXCenter - radius);
oval.top = (mYCenter - radius);
oval.right = mXCenter + radius;
oval.bottom = radius * 2 + (mYCenter - radius);
//繪製圓弧背景
canvas.drawArc(oval, -180, 180, false, mPaint);
//繪製圓弧進度
float showDegree = progress / 100 * 180;
mPaint.setColor(color);
canvas.drawArc(oval, -180, showDegree, false, mPaint);
}
private void setOutSideProgress(float progress){
this.mOutsideProgress = progress;
postInvalidate();
}
private void setMiddleProgress(float progress){
this.mMiddleProgress = progress;
postInvalidate();
}
private void setInsideProgress(float progress){
this.mInsideProgress = progress;
postInvalidate();
}
public void setProgress(float outSideProgress, float middleProgress, float insideProgress) {
mOutsideProgress = outSideProgress;
mMiddleProgress = middleProgress;
mInsideProgress = insideProgress;
postInvalidate();
}
public int dp2px(Context context, float dipValue) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (dipValue * scale + 0.5f);
}
public int sp2px(Context context, float spValue) {
final float fontScale = context.getResources().getDisplayMetrics().scaledDensity;
return (int) (spValue * fontScale + 0.5f);
}
public int px2sp(Context context, float pxValue) {
final float fontScale = context.getResources().getDisplayMetrics().scaledDensity;
return (int) (pxValue / fontScale + 0.5f);
}
}
styes.xml
<declare-styleable name="ThreeArcView">
<!-- 畫筆寬度 -->
<attr name="ts_strokeWidth" format="dimension" />
<!-- 圓弧背景色 -->
<attr name="ts_bgArcColor" format="color" />
<!-- 外圓進度顏色 -->
<attr name="ts_outsideBgColor" format="color" />
<!-- 中圓進度顏色 -->
<attr name="ts_middleBgColor" format="color" />
<!-- 內圓進度顏色 -->
<attr name="ts_insideBgColor" format="color" />
<!-- 外圓進度 -->
<attr name="ts_outsideProgress" format="float" />
<!-- 中圓進度 -->
<attr name="ts_middleProgress" format="float" />
<!-- 內圓進度 -->
<attr name="ts_insideProgress" format="float" />
<!-- 圓偏移值 -->
<attr name="ts_radiusOffset" format="dimension" />
</declare-styleable>
OK,本文到此結束,若發現問題,歡迎一起留言一起探討,感謝~
相關教程
Android基礎系列教程:
Android基礎課程U-小結_嗶哩嗶哩_bilibili
Android基礎課程UI-佈局_嗶哩嗶哩_bilibili
Android基礎課程UI-控制元件_嗶哩嗶哩_bilibili
Android基礎課程UI-動畫_嗶哩嗶哩_bilibili
Android基礎課程-activity的使用_嗶哩嗶哩_bilibili
Android基礎課程-Fragment使用方法_嗶哩嗶哩_bilibili
Android基礎課程-熱修復/熱更新技術原理_嗶哩嗶哩_bilibili
本文轉自https://juejin.cn/post/6847902216959819790,如有侵權,請聯絡刪除。