自定義View-實現簡易車速器(真的夠簡易)
學習自定義View挺久了,好久沒用都快忘了,這里實現一個簡易的車速器算是一個回顧,項目比較簡單,代碼較少,但自定義View的流程基本都涉及到了.本文不是一篇講解自定義View基礎的文章,而是一個小的實戰,如果想看講解自定義View的文章,強烈推薦博客:http://blog.csdn.net/aigestudio,強烈=_=.
效果如下圖:
接下來是代碼部分:
attrs:
<?xml version="1.0" encoding="utf-8"?>
<resources><declare-styleable name="SpeedometerView"><attr name="centerCircleWidth" format="dimension"/><attr name="outCircleWidth" format="dimension"/><attr name="whiteScaleWidth" format="dimension"/><attr name="redScaleWidth" format="dimension"/><attr name="numberWidth" format="dimension"/><attr name="pointerWidth" format="dimension"/><attr name="outCircleColor" format="color"/><attr name="innerCircleColor" format="color"/><attr name="numberColor" format="color"/><attr name="normalScaleColor" format="color"/><attr name="multipleOfTenScaleColor" format="color"/></declare-styleable>
</resources>
attrs文件包含自定義的屬性,可以在布局文件或者代碼中使用.
colors:
<?xml version="1.0" encoding="utf-8"?>
<resources><color name="colorPrimary">#3F51B5</color><color name="colorPrimaryDark">#303F9F</color><color name="colorAccent">#FF4081</color><color name="color_out_arc">@android:color/holo_red_dark</color><color name="color_full_ten_number">@android:color/holo_red_dark</color><color name="color_inner_arc">@android:color/white</color><color name="color_full_ten_indicator">@android:color/holo_red_dark</color><color name="color_not_full_ten_indicator">@android:color/white</color>
</resources>
布局文件:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"xmlns:SpeedometerView="http://schemas.android.com/apk/res-auto"android:id="@+id/activity_main"android:layout_width="match_parent"android:layout_height="match_parent"android:background="@android:color/darker_gray"tools:context="com.example.why.speedometerview.MainActivity"><com.example.why.speedometerview.SpeedometerViewandroid:background="@android:color/black"android:id="@+id/speedometer_view"android:layout_width="match_parent"android:layout_height="match_parent"SpeedometerView:outCircleWidth="5dp"/>
</FrameLayout>
接下來是Java代碼:
SpeedometerView:
/*** Created by why on 17-3-9.* @author why* 簡易View實現類*/public class SpeedometerView extends View {private static final String TAG = "SpeedometerView";private Paint mInnerArcPaint; // 內部弧線畫筆private Paint mOutArcPaint; // 外部弧線畫筆private Paint mNotFullTenIndicatorPaint; // 非整10刻度畫筆private Paint mFullTenIndicatorPaint; // 整10加粗刻度畫筆private Paint mNumberPaint; // 數字畫筆private Paint mIndicatorPathPaint; // 刻度指針畫筆,計可旋轉的線條private int mInnerArcLineWidth; // 內部弧線線條寬度private int mOutArcLineWidth; // 外部弧線線條寬度private int mNormalIndicatorWidth; // 刻度線條寬度private int mFullTenScaleWidth; // 整10加粗刻度線條寬度private int mFullTenScaleLen ; // 非整10加粗線條長度private int mNotFullScaleLen; // 白色刻度線條長度private int mNumberWidth; // 數字線條刻度private int mIndicatorPathLineWidth;//private float mNumberTextSize; // 數字字體大小private int mCenterCircleRadius; // 內圓半徑private int mOutCircleRadius; // 外圓半徑private RectF mOutRectF; // 外弧線的基準矩形private RectF mCenterRectF; //內弧線基準矩形// 這兩個值是當view的width和height屬性為wrap_content時,屬性不起作用配置的默認值,詳情間onMeasure();private int mViewDefaultWidth; // 默認的view的寬度private int mViewDefaultHeight; // 默認的view的高度,private int[] mWindowSize = new int[2]; // 保存屏幕的寬/高private Path mIndicatorPath; // 刻度指針,用path實現private int mScaleIndicatorLen; // 刻度指針的長度private int mIndicatorX; // 刻度指針指尖的X坐標private int mIndicatorY; // 刻度指針指尖的Y坐標private int mOutArcColor = Color.RED; // 外部弧線默認顏色private int mNumberColor =Color.RED; // 刻度數字默認顏色private int mInnerArcColor = Color.WHITE; //內部弧線默認顏色private int mFullTenIndicatorColor = Color.RED; // 整10刻度顏色private int mNotFullTenIndicatorColor = Color.WHITE; // 非整10刻度顏色public SpeedometerView(Context context) {super(context);init(context,null,0);}public SpeedometerView(Context context, AttributeSet attrs) {super(context,attrs);init(context,attrs,0);}public SpeedometerView(Context context, AttributeSet attrs, int defStyleAttr) {super(context,attrs,defStyleAttr);init(context,attrs,defStyleAttr);}/*** 初始化默認數值*/private void initSize(){mWindowSize = getWindowSize();mViewDefaultWidth = mWindowSize[0];mViewDefaultHeight = mWindowSize[1];mOutCircleRadius = mWindowSize[0]/2-20;mNumberTextSize = mOutCircleRadius / 13;mInnerArcLineWidth = mOutCircleRadius/150;mCenterCircleRadius = mOutCircleRadius / 8;mOutArcLineWidth = mOutCircleRadius /150;mNormalIndicatorWidth = mOutCircleRadius /150;mFullTenScaleWidth = mOutCircleRadius / 150;mFullTenScaleLen = mOutCircleRadius /6;mNotFullScaleLen = mOutCircleRadius / 7;mIndicatorPathLineWidth = mOutCircleRadius/100;mNumberWidth = mOutCircleRadius/100;}/*** 初始化默認顏色*/private void initColor() {Context context = getContext();mOutArcColor = ContextCompat.getColor(context, R.color.color_out_arc);mNumberColor = ContextCompat.getColor(context, R.color.color_full_ten_number);mInnerArcColor = ContextCompat.getColor(context, R.color.color_inner_arc);mFullTenIndicatorColor = ContextCompat.getColor(context, R.color.color_full_ten_indicator);mNotFullTenIndicatorColor = ContextCompat.getColor(context,R.color.color_not_full_ten_indicator);}/*** 初始化* @param context* @param attrs* @param defStyleAttr*/private void init(Context context,AttributeSet attrs, int defStyleAttr){initSize(); //為各個屬性設置默認的值initColor(); //設置默認顏色//從xml獲取設置的屬性,注意:這里只提供顏色屬性if (attrs != null) {TypedArray typedArray = context.obtainStyledAttributes(attrs,R.styleable.SpeedometerView,defStyleAttr,0);mOutArcColor = typedArray.getColor(R.styleable.SpeedometerView_outCircleColor,mOutArcColor);mNumberColor = typedArray.getColor(R.styleable.SpeedometerView_numberColor, mNumberColor);mInnerArcColor = typedArray.getColor(R.styleable.SpeedometerView_innerCircleColor, mInnerArcColor);mNotFullTenIndicatorColor = typedArray.getColor(R.styleable.SpeedometerView_normalScaleColor, mNotFullTenIndicatorColor);mFullTenIndicatorColor = typedArray.getColor(R.styleable.SpeedometerView_multipleOfTenScaleColor, mFullTenIndicatorColor);typedArray.recycle();}//設置內弧線畫筆mInnerArcPaint = new Paint();mInnerArcPaint.setStyle(Paint.Style.STROKE);mInnerArcPaint.setColor(mInnerArcColor);mInnerArcPaint.setAntiAlias(true); //抗鋸齒mInnerArcPaint.setStrokeWidth(mInnerArcLineWidth);//設置外弧線畫筆mOutArcPaint = new Paint();mOutArcPaint.setStyle(Paint.Style.STROKE);mOutArcPaint.setColor(mOutArcColor);mOutArcPaint.setAntiAlias(true);mOutArcPaint.setStrokeWidth(mOutArcLineWidth);//設置非整十刻度畫筆mNotFullTenIndicatorPaint = new Paint();mNotFullTenIndicatorPaint.setStyle(Paint.Style.FILL);mNotFullTenIndicatorPaint.setColor(mNotFullTenIndicatorColor);mNotFullTenIndicatorPaint.setAntiAlias(true);mNotFullTenIndicatorPaint.setStrokeWidth(mNormalIndicatorWidth);//設置整十刻度畫筆mFullTenIndicatorPaint = new Paint();mFullTenIndicatorPaint.setStyle(Paint.Style.FILL);mFullTenIndicatorPaint.setColor(mFullTenIndicatorColor);mFullTenIndicatorPaint.setAntiAlias(true);mFullTenIndicatorPaint.setStrokeWidth(mFullTenScaleWidth);//設置數字畫筆mNumberPaint = new Paint();mNumberPaint.setStyle(Paint.Style.FILL);mNumberPaint.setColor(mNumberColor);mNumberPaint.setAntiAlias(true);mNumberPaint.setStrokeWidth(mNumberWidth);mNumberPaint.setTextSize(mNumberTextSize);mNumberPaint.setTextAlign(Paint.Align.CENTER);//設置刻度指針畫筆mIndicatorPathPaint = new Paint();mIndicatorPathPaint.setAntiAlias(true);mIndicatorPathPaint.setStyle(Paint.Style.STROKE);mIndicatorPathPaint.setColor(Color.RED);mIndicatorPathPaint.setStrokeWidth(mIndicatorPathLineWidth);mScaleIndicatorLen = mOutCircleRadius*8/13;//指針的長度mIndicatorX = -mScaleIndicatorLen;//因為指針一開始是放在-180°的位置mIndicatorY = 0; //指針的起止坐標為內圓的圓心(onDraw()中會把原點坐標平移到屏幕的中心,方便畫圖),所以y為0mIndicatorPath = new Path();mOutRectF = new RectF();mCenterRectF = new RectF();}@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {super.onMeasure(widthMeasureSpec, heightMeasureSpec);//通過MeasureSpec獲取各自的mode和sizeint widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);/*** 這里這么做的原因是自定義View的時,width或者height的屬性wrap_content不起作用,效果和match_parant相同,這里有牽扯到自* View扥繪制流程這里暫時不表,建議看看《Android開發藝術探索》中自定義View篇章或者看官網和相應的博客,這里只是當屬性為* wrap_content時設置默認的mViewDefaultWidth,mViewDefaultHeight;*/if (widthSpecMode == MeasureSpec.AT_MOST && heightMeasureSpec == MeasureSpec.AT_MOST) {setMeasuredDimension(mViewDefaultWidth, mViewDefaultHeight);}else if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode != MeasureSpec.AT_MOST) {setMeasuredDimension(mViewDefaultWidth, heightSpecSize);}else if (widthMeasureSpec != MeasureSpec.AT_MOST && heightMeasureSpec == MeasureSpec.AT_MOST){setMeasuredDimension(widthSpecSize, heightMeasureSpec);}else {setMeasuredDimension(widthSpecSize, heightSpecSize);}}/*** 在onDraw(Canvas canvas)中畫圖,每次調用重繪方法invalidate()都會再次調用onDraw(Canvas canvas);* @param canvas*/@Overrideprotected void onDraw(Canvas canvas) {canvas.translate(mWindowSize[0]/2, mWindowSize[1]/2);//canvas坐標平移到屏幕中心,這樣容易操作//畫外層的弧形,默認紅色drawOutArc(mOutRectF, canvas);//畫內層的弧形,默認白色drawInnerWhiteArc(mCenterRectF, canvas);//畫整十刻度以及整10數字drawFullTenIndicatorAndNumber(canvas);//接下來畫普通的刻度,即未加粗的刻度drawNotFullTenIndicator(canvas);//接下來畫指示圖標,注意這里使用path的drawPath()方法畫line,每次畫之前path調用path.lineTo(mIndicatorX, mIndicatorY)//連接內圓心和該點drawIndicator(mIndicatorPath, canvas);}/*** 獲取觸摸的坐標* @param event* @return*/@Overridepublic boolean onTouchEvent(MotionEvent event) {int action = MotionEventCompat.getActionMasked(event);float posX;float posY;double scale;switch (action) {case MotionEvent.ACTION_DOWN:posX = event.getX();posY = event.getY();//如果觸摸的坐標不在弧線的基準矩形中,那就不做處理,直接returnif (!mOutRectF.contains(posX-mWindowSize[0]/2,posY - mWindowSize[1]/2) || posY - mWindowSize[1]/2 > 0){return true;}/*** 由于刻度指針的長度是固定的,但是手指觸摸的點不是固定的,不能直接將指針的終點坐標設置為獲取的x,y,但可以先算出*手指觸摸的點到內圓中心的距離,然后除于指針的長度,得到比例,根據相似三角形的性質可知,這個比例對于三角形的三條邊都是一樣的*注意:這里的三角形是假設圓心,觸摸點,以及X坐標相連獲得的直角三角形,于是我們可以根據比例算出在點與內圓心的距離為默認的指針*長度時,此時另外兩條直角邊的長度,即可求出,x,y的值*/scale = Math.sqrt((posX-mWindowSize[0]/2)*(posX-mWindowSize[0]/2)+ (posY - mWindowSize[1]/2)*(posY - mWindowSize[1]/2))/mScaleIndicatorLen;mIndicatorX = rawXToIndicatorX(posX, scale);mIndicatorY = rawXToIndicatorY(posY, scale);break;case MotionEvent.ACTION_MOVE://移動過程中不斷的獲取觸摸點的坐標,并按比例轉換為相應的指針終點坐標posX = event.getX();posY = event.getY();if (!mOutRectF.contains(posX-mWindowSize[0]/2,posY - mWindowSize[1]/2)|| posY - mWindowSize[1]/2 > 0){return true;}Log.d(TAG,"x的="+posX+"--y的值=" +""+posY);scale = Math.sqrt((posX-mWindowSize[0]/2)*(posX-mWindowSize[0]/2)+ (posY - mWindowSize[1]/2)*(posY - mWindowSize[1]/2))/mScaleIndicatorLen;//或最終的指針終點坐標,onDraw()根據mIndicatorX.mIndicatorY畫指針的終點mIndicatorX = rawXToIndicatorX(posX, scale);mIndicatorY = rawXToIndicatorY(posY, scale);break;}//重新繪制view,onDraw()會被調用invalidate();//return true表示自己處理觸摸事件,parent的onTouchEvent()方法不會被調用了return true;}/*** 按邊長比例算出最終的X* @param posX* @param scale* @return*/private int rawXToIndicatorX(double posX, double scale) {return (int)((posX-mWindowSize[0]/2) / scale);}/*** 按邊長比例算出最終的Y* @param posY* @param scale* @return*/private int rawXToIndicatorY(double posY, double scale) {return (int)((posY - mWindowSize[1]/2) / scale);}/*** 畫出外弧線* @param rectF* @param canvas*/private void drawOutArc(RectF rectF, Canvas canvas) {rectF.set(-mOutCircleRadius, - mOutCircleRadius, mOutCircleRadius, mOutCircleRadius);canvas.drawArc(mOutRectF,0,-180,false,mOutArcPaint);}//畫出內弧線private void drawInnerWhiteArc(RectF rectF, Canvas canvas) {rectF.set(-mCenterCircleRadius, -mCenterCircleRadius,mCenterCircleRadius, mCenterCircleRadius);canvas.drawArc(mCenterRectF, 0, -180, false, mInnerArcPaint);}/*** 畫整10的刻度,因為數字對應整時,可在同一個循環中畫出* @param canvas*/private void drawFullTenIndicatorAndNumber(Canvas canvas) {//整十刻度終點離內圓心的長度int distance = mOutCircleRadius - mFullTenScaleLen;//接下來畫10的倍數的刻度for (int i = -180; i<=0; i += 15) {double radian = ((double)i/180)*Math.PI;//弧度double cosValue = Math.cos(radian);//三角函數值double sinValue = Math.sin(radian);//根據弧度算出起始點和終點坐標float startX = (float)(distance*cosValue);float startY = (float)(distance*sinValue);float endX = (float)(mOutCircleRadius*Math.cos(radian));float endY = (float)(mOutCircleRadius*Math.sin(radian));canvas.drawLine(startX,startY,endX,endY,mFullTenIndicatorPaint);//刻度數字標識的坐標,float numberX =(float)((distance-50)*cosValue);float numberY =(float)((distance-50)*sinValue);canvas.drawText(""+Math.abs(i+180)*2/3,numberX, numberY, mNumberPaint);}}/*** 畫非整10刻度* @param canvas*/private void drawNotFullTenIndicator(Canvas canvas) {float normalIntervalRadian = (float)Math.PI / 60;int normalIntervalRadianCount = (int)(Math.PI/normalIntervalRadian);int distance = mOutCircleRadius - mNotFullScaleLen;for (int i = 0; i <= normalIntervalRadianCount; i++) {if (!(i%5 == 0) ){float cosValue = (float)Math.cos(-normalIntervalRadian*i);float sinValue = (float)Math.sin(-normalIntervalRadian*i);float startX = (distance*cosValue);float startY = (distance*sinValue);float endX = (float)((mOutCircleRadius-10)*Math.cos((-normalIntervalRadian*i)));float endY = (float)((mOutCircleRadius-10)*Math.sin((-normalIntervalRadian*i)));canvas.drawLine(startX,startY,endX,endY,mNotFullTenIndicatorPaint);}}}/*** 畫刻度指針* @param path* @param canvas*/private void drawIndicator(Path path, Canvas canvas) {path.lineTo(mIndicatorX, mIndicatorY);canvas.drawPath(path,mIndicatorPathPaint);path.reset();}/*** 獲取屏幕的寬,高,按屏幕的比例設置view的各個屬性* @return*/private int[] getWindowSize() {int[] size = new int[2];Resources resources = getContext().getResources();DisplayMetrics displayMetrics = resources.getDisplayMetrics();size[0] = displayMetrics.widthPixels;size[1] = displayMetrics.heightPixels;return size;}//這里只提供修改顏色的方法public int getOutCircleColor() {return mOutArcColor;}public void setOutCircleColor(int outCircleColor) {this.mOutArcColor = outCircleColor;}public int getNumberColor() {return mNumberColor;}public void setmNumberColor(int numberColor) {this.mNumberColor = numberColor;}public int getInnerCircleColor() {return mInnerArcColor;}public void setInnerCircleColor(int innerArcColor) {this.mInnerArcColor = innerArcColor;}public int getmOutArcColor() {return mOutArcColor;}public void setmOutArcColor(int outArcColor) {this.mOutArcColor = outArcColor;}public int getmNumberColor() {return mNumberColor;}public int getmInnerArcColor() {return mInnerArcColor;}public void setmInnerArcColor(int innerArcColor) {this.mInnerArcColor = innerArcColor;}public int getmFullTenIndicatorColor() {return mFullTenIndicatorColor;}public void setmFullTenIndicatorColor(int fullTenIndicatorColor) {this.mFullTenIndicatorColor = fullTenIndicatorColor;}public int getmNotFullTenIndicatorColor() {return mNotFullTenIndicatorColor;}public void setmNotFullTenIndicatorColor(int notFullTenIndicatorColor) {this.mNotFullTenIndicatorColor = notFullTenIndicatorColor;}
}
Activity:
public class MainActivity extends AppCompatActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);SpeedometerView speedometerView = (SpeedometerView)findViewById(R.id.speedometer_view);}
}
注釋還是很清楚的,源碼地址https://github.com/whyrookie/...
總結
以上是生活随笔為你收集整理的自定义View-实现简易车速器(真的够简易)的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。