Android自定義View-漸變的溫度指示器
廢話少說,先上圖

ED9EF9312D01ADABDFADF481CF32A26C.jpg
1、自定義View的分類

image.png
2、自定義View要點
- View需要支援wrap_content
- View需要支援padding
- 儘量不要再View中使用Handler,View已經有post系列方法
- View如果有執行緒或者動畫,需要及時停止(onDetachedFromWindow會在View被remove時呼叫)——避免記憶體洩露
- View如果有滑動巢狀情形,需要處理好滑動衝突
3、直接繼承自View的實現步驟和方法
- 重寫onDraw,在onDraw中處理padding
- 重寫onMeasure,額外處理wrap_content的情況
- 設定自定義屬性attrs(屬性相關xml檔案,以及在onDraw中進行處理)
4、實現效果圖步驟
- 重寫onMeasure,額外處理wrap_content的情況
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec); int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec); int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec); int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec); mHeight = mDefaultTextSize + mDefaultTempHeight + mDefaultIndicatorHeight + textSpace; if (widthSpecMode == MeasureSpec.AT_MOST || heightSpecMode == MeasureSpec.AT_MOST) { setMeasuredDimension(mWidth, mHeight); } else if (widthSpecMode == MeasureSpec.AT_MOST) { setMeasuredDimension(mWidth, heightSpecSize); } else if (heightSpecMode == MeasureSpec.AT_MOST) { setMeasuredDimension(widthSpecSize, mHeight); } }
- 重寫onDraw,在onDraw中處理padding,並畫出溫度計及指標
@Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); final int paddingLeft = getPaddingLeft(); final int paddingTop = getPaddingTop(); final int paddingRight = getPaddingRight(); final int paddingBottom = getPaddingBottom(); //確定圓角矩形的範圍,在TmepView的最底部,top位置為總高度-圓角矩形的高度 rectProgressBg = new RectF(); rectProgressBg.left = 0 + paddingLeft; rectProgressBg.top = mHeight - mDefaultTempHeight; rectProgressBg.right = mWidth - paddingRight; rectProgressBg.bottom = mHeight - paddingBottom; shader = new LinearGradient(0, mHeight - mDefaultTempHeight, mWidth, mHeight, SECTION_COLORS, null, Shader.TileMode.MIRROR); mPaint.setShader(shader); //繪製圓角矩形 mDefaultTempHeight / 2確定圓角的圓心位置 canvas.drawRoundRect(rectProgressBg, mDefaultTempHeight / 2, mDefaultTempHeight / 2, mPaint); //當前位置佔比 selction = currentCount / maxCount; //繪製指標 指標的位置在當前溫度的位置 也就是三角形的頂點落在當前溫度的位置 //定義三角形的左邊點的座標 x= tempView的寬度*當前位置佔比-三角形的寬度/2y=tempView的高度-圓角矩形的高度 indexPath.moveTo(mWidth * selction - mDefaultIndicatorWidth / 2, mHeight - mDefaultTempHeight); //定義三角形的右邊點的座標 = tempView的寬度*當前位置佔比+三角形的寬度/2y=tempView的高度-圓角矩形的高度 indexPath.lineTo(mWidth * selction + mDefaultIndicatorWidth / 2, mHeight - mDefaultTempHeight); //定義三角形的左邊點的座標 x= tempView的寬度*當前位置佔比y=tempView的高度-圓角矩形的高度-三角形的高度 indexPath.lineTo(mWidth * selction, mHeight - mDefaultTempHeight - mDefaultIndicatorHeight + paddingTop); indexPath.close(); indexPaint.setShader(shader); canvas.drawPath(indexPath, indexPaint); //繪製文字 String text = (int) currentCount + "°c"; //確定文字的位置 x=tempViwe的寬度*當前位置佔比 y=tempView的高度-圓角矩形的高度-三角形的高度-文字的間隙 canvas.drawText(text, mWidth * selction, mHeight - mDefaultTempHeight - mDefaultIndicatorHeight - textSpace, textPaint); }
ps:繪製三角形指標,由於位置會變 所以要確定繪製的位置如圖

image
程式碼還算比較好理解,詳細程式碼如下:
package androidtest.project.com.customview; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.LinearGradient; import android.graphics.Paint; import android.graphics.Path; import android.graphics.RectF; import android.graphics.Shader; import android.support.annotation.Nullable; import android.text.TextPaint; import android.util.AttributeSet; import android.view.View; /** * Created by liuboyu on 2018/10/15. */ public class TemperatureView extends View { private Context mContext; /** * 分段顏色 */ private static final int[] SECTION_COLORS = {Color.GREEN, Color.YELLOW, Color.RED}; /** * 預設寬度 */ private int mWidth = 1000; /** * 預設高度 */ private int mHeight = 200; /** * 設定溫度的最大範圍 */ private float maxCount = 100f; /** * 設定當前溫度 */ private float currentCount = 50f; /** * 當前刻度位置 */ private float selction; /** * 主畫筆,畫刻度尺 */ private Paint mPaint; /** * 文字畫筆 */ private Paint textPaint; /** * 當前刻度指標 */ private Path indexPath; private Paint indexPaint; /** * 畫圓柱 */ private RectF rectProgressBg; private LinearGradient shader; /** * 指標的寬高 */ private int mDefaultIndicatorWidth = dipToPx(10); private int mDefaultIndicatorHeight = dipToPx(8); /** * 圓角矩形的高度 */ private int mDefaultTempHeight = dipToPx(20); /** * 預設字型大小 */ private int mDefaultTextSize = 30; private int textSpace = dipToPx(5); public TemperatureView(Context context) { super(context); init(context); } public TemperatureView(Context context, @Nullable AttributeSet attrs) { super(context, attrs); init(context); } public TemperatureView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(context); } /** * 初始化各種畫筆 * * @param context */ private void init(Context context) { this.mContext = context; //圓角矩形paint mPaint = new Paint(); //防止邊緣的鋸齒 mPaint.setAntiAlias(true); //文字paint textPaint = new TextPaint(); textPaint.setAntiAlias(true); textPaint.setTextSize(mDefaultTextSize); textPaint.setTextAlign(Paint.Align.CENTER); textPaint.setColor(mContext.getResources().getColor(R.color.colorAccent)); //三角形指標paint indexPath = new Path(); indexPaint = new Paint(); indexPaint.setAntiAlias(true); indexPaint.setStyle(Paint.Style.FILL); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec); int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec); int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec); int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec); mHeight = mDefaultTextSize + mDefaultTempHeight + mDefaultIndicatorHeight + textSpace; if (widthSpecMode == MeasureSpec.AT_MOST || heightSpecMode == MeasureSpec.AT_MOST) { setMeasuredDimension(mWidth, mHeight); } else if (widthSpecMode == MeasureSpec.AT_MOST) { setMeasuredDimension(mWidth, heightSpecSize); } else if (heightSpecMode == MeasureSpec.AT_MOST) { setMeasuredDimension(widthSpecSize, mHeight); } } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); final int paddingLeft = getPaddingLeft(); final int paddingTop = getPaddingTop(); final int paddingRight = getPaddingRight(); final int paddingBottom = getPaddingBottom(); //確定圓角矩形的範圍,在TmepView的最底部,top位置為總高度-圓角矩形的高度 rectProgressBg = new RectF(); rectProgressBg.left = 0 + paddingLeft; rectProgressBg.top = mHeight - mDefaultTempHeight; rectProgressBg.right = mWidth - paddingRight; rectProgressBg.bottom = mHeight - paddingBottom; shader = new LinearGradient(0, mHeight - mDefaultTempHeight, mWidth, mHeight, SECTION_COLORS, null, Shader.TileMode.MIRROR); mPaint.setShader(shader); //繪製圓角矩形 mDefaultTempHeight / 2確定圓角的圓心位置 canvas.drawRoundRect(rectProgressBg, mDefaultTempHeight / 2, mDefaultTempHeight / 2, mPaint); //當前位置佔比 selction = currentCount / maxCount; //繪製指標 指標的位置在當前溫度的位置 也就是三角形的頂點落在當前溫度的位置 //定義三角形的左邊點的座標 x= tempView的寬度*當前位置佔比-三角形的寬度/2y=tempView的高度-圓角矩形的高度 indexPath.moveTo(mWidth * selction - mDefaultIndicatorWidth / 2, mHeight - mDefaultTempHeight); //定義三角形的右邊點的座標 = tempView的寬度*當前位置佔比+三角形的寬度/2y=tempView的高度-圓角矩形的高度 indexPath.lineTo(mWidth * selction + mDefaultIndicatorWidth / 2, mHeight - mDefaultTempHeight); //定義三角形的左邊點的座標 x= tempView的寬度*當前位置佔比y=tempView的高度-圓角矩形的高度-三角形的高度 indexPath.lineTo(mWidth * selction, mHeight - mDefaultTempHeight - mDefaultIndicatorHeight + paddingTop); indexPath.close(); indexPaint.setShader(shader); canvas.drawPath(indexPath, indexPaint); //繪製文字 String text = (int) currentCount + "°c"; //確定文字的位置 x=tempViwe的寬度*當前位置佔比 y=tempView的高度-圓角矩形的高度-三角形的高度-文字的間隙 canvas.drawText(text, mWidth * selction, mHeight - mDefaultTempHeight - mDefaultIndicatorHeight - textSpace, textPaint); } /** * 單位轉換 * * @param dip * @return */ private int dipToPx(int dip) { float scale = getContext().getResources().getDisplayMetrics().density; return (int) (dip * scale + 0.5f * (dip >= 0 ? 1 : -1)); } /*** * 設定最大的溫度值 * @param maxCount */ public void setMaxCount(float maxCount) { this.maxCount = maxCount; } /*** * 設定當前的溫度 * @param currentCount */ public void setCurrentCount(float currentCount) { if (currentCount > maxCount) { this.currentCount = maxCount - 5; } else if (currentCount < 0f) { currentCount = 0f + 5; } else { this.currentCount = currentCount; } invalidate(); } /** * 設定溫度指標的大小 * * @param width * @param height */ public void setIndicatorSize(int width, int height) { this.mDefaultIndicatorWidth = width; this.mDefaultIndicatorHeight = height; } /** * 設定溫度計厚度 * * @param height */ public void setTempHeight(int height) { this.mDefaultTempHeight = height; } /** * 設定文字大小 * * @param textSize */ public void setTextSize(int textSize) { this.mDefaultTextSize = textSize; } /** * 獲取溫度計最大刻度 * * @return */ public float getMaxCount() { return maxCount; } /** * 獲取當前刻度 * * @return */ public float getCurrentCount() { return currentCount; } }