Android自定義View制作儀表盤界面
前言
最近我跟自定義View杠上了,甚至說有點(diǎn)上癮到走火入魔了。身為菜鳥的我自然要查閱大量的資料,學(xué)習(xí)大神們的代碼,這不,前兩天正好在郭神在微信公眾號(hào)里推送一片自定義控件的文章——一步步實(shí)現(xiàn)精美的鐘表界面。正適合我這種菜鳥來學(xué)習(xí),閑著沒事,我就差不多依葫蘆畫瓢也寫了一個(gè)自定義表盤View,現(xiàn)在純粹最為筆記記錄下來。先展示下效果圖:
下面進(jìn)入正題
自定義表盤屬性
老規(guī)矩,先在attrs文件里添加表盤自定義屬性
<declare-styleable name="WatchView"> <attr name="watchRadius" format="dimension"/> //表盤半徑 <attr name="watchPadding" format="dimension"/> //表盤相對(duì)控件邊框距離 <attr name="watchScalePadding" format="dimension"/> //刻度相對(duì)表盤距離 <attr name="watchScaleColor" format="color|reference"/> //常規(guī)刻度顏色 <attr name="watchScaleLength" format="dimension|reference"/> //常規(guī)刻度長(zhǎng)度 <attr name="watchHourScaleColor" format="dimension|reference"/> //整點(diǎn)刻度顏色 <attr name="watchHourScaleLength" format="dimension|reference"/> //整點(diǎn)刻度長(zhǎng)度 <attr name="hourPointColor" format="color|reference"/> //時(shí)針顏色 <attr name="hourPointLength" format="dimension|reference"/> //時(shí)針長(zhǎng)度 <attr name="minutePointColor" format="color|reference"/> //分針顏色 <attr name="minutePointLength" format="dimension|reference"/> //分針長(zhǎng)度 <attr name="secondPointColor" format="color|reference"/> //秒針顏色 <attr name="secondPointLength" format="dimension|reference"/> //秒針長(zhǎng)度 <attr name="timeTextSize" format="dimension|reference"/> //表盤字體大小 <attr name="timeTextColor" format="color|reference"/> //表盤字體顏色 </declare-styleable>
在自定義View的構(gòu)造方法種獲取自定義屬性
先將屬性變量聲明如下:
<span style="font-size:14px;"> /**表盤邊距*/ private float mWatchPadding = 5; /**表盤與刻度邊距*/ private float mWatchScalePadding = 5; /**表盤半徑*/ private float mWatchRadius = 250; /**表盤刻度長(zhǎng)度*/ private float mWatchScaleLength; /**表盤刻度顏色*/ private int mWatchScaleColor = Color.BLACK; /**表盤整點(diǎn)刻度長(zhǎng)度*/ private float mHourScaleLength = 8; /**表盤整點(diǎn)刻度顏色*/ private int mHourScaleColor = Color.BLUE; /**表盤時(shí)針顏色*/ private int mHourPointColor = Color.BLACK; /**表盤時(shí)針長(zhǎng)度*/ private float mHourPointLength = 100; /**表盤分針顏色*/ private int mMinutePointColor = Color.BLACK; /**表盤分針長(zhǎng)度*/ private float mMinutePointLength = 130; /**表盤秒針顏色*/ private int mSecondPointColor = Color.RED; /**表盤秒針長(zhǎng)度*/ private float mSecondPointLength = 160; /**表盤尾部指針長(zhǎng)度*/ private float mEndPointLength; /**表盤數(shù)字顏色*/ private int mTimeTextColor = Color.BLACK; /**表盤數(shù)字大小*/ private int mTimeTextSize = 15;</span>
在構(gòu)造方法種獲取自定義屬性
<span style="font-size:14px;"> public WatchView(Context context, AttributeSet attrs) { super(context, attrs); TypedArray array = context.obtainStyledAttributes(attrs,R.styleable.WatchView); int n = array.getIndexCount(); for (int i = 0;i<n;i++){ int attr = array.getIndex(i); switch (attr){ case R.styleable.WatchView_watchRadius: mWatchRadius = array.getDimensionPixelSize(attr,MyUtil.dip2px(context,60)); break; case R.styleable.WatchView_watchPadding: mWatchPadding = array.getDimensionPixelSize(attr,MyUtil.dip2px(context,5)); break; case R.styleable.WatchView_watchScalePadding: mWatchScalePadding = array.getDimensionPixelSize(attr,MyUtil.dip2px(context,3)); break; case R.styleable.WatchView_watchScaleLength: mWatchScaleLength = array.getDimensionPixelSize(attr,MyUtil.dip2px(context,5)); break; case R.styleable.WatchView_watchScaleColor: mWatchScaleColor = array.getColor(attr, Color.parseColor("#50000000")); break; case R.styleable.WatchView_watchHourScaleLength: mHourScaleLength = array.getDimensionPixelSize(attr,MyUtil.dip2px(context,10)); break; case R.styleable.WatchView_watchHourScaleColor: mHourScaleColor = array.getColor(attr,Color.BLACK); break; case R.styleable.WatchView_hourPointLength: mHourPointLength = array.getDimensionPixelSize(attr,MyUtil.dip2px(context,35)); break; case R.styleable.WatchView_hourPointColor: mHourPointColor = array.getColor(attr,Color.BLACK); break; case R.styleable.WatchView_minutePointLength: mMinutePointLength = array.getDimensionPixelSize(attr,MyUtil.dip2px(context,40)); break; case R.styleable.WatchView_minutePointColor: mMinutePointColor = array.getColor(attr,Color.BLACK); break; case R.styleable.WatchView_secondPointLength: mSecondPointLength = array.getDimensionPixelSize(attr,MyUtil.dip2px(context,50)); break; case R.styleable.WatchView_secondPointColor: mSecondPointColor = array.getColor(attr,Color.BLUE); break; case R.styleable.WatchView_timeTextSize: mTimeTextSize = array.getDimensionPixelSize(attr,MyUtil.dip2px(context,15)); break; case R.styleable.WatchView_timeTextColor: mTimeTextColor = array.getColor(attr,Color.BLACK); break; } } array.recycle(); }</span>
設(shè)置控件大小
這里當(dāng)然就是重寫onMeasure方法啦,這里我們處理的簡(jiǎn)單點(diǎn),如下面代碼所示,當(dāng)我們將控件的寬高都設(shè)定為wrap_content(即MeasureSpec.UNSPECIFED)時(shí),我們將寬高設(shè)定為默認(rèn)值(wrapContentSize)和圓盤半徑+圓盤邊距(mWatchRadius+mWatchPadding)之間取最大值,其他情況下就取系統(tǒng)自取值。當(dāng)然作為一個(gè)嚴(yán)謹(jǐn)?shù)目丶?,僅僅這樣處理肯定是不行的。項(xiàng)目中,我們要根據(jù)我們的需求自行修改里面的代碼以適配。
<span style="font-size:14px;"> @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int wrapContentSize = 1000; int widthSize = MeasureSpec.getSize(widthMeasureSpec); int widthMode = MeasureSpec.getMode(widthMeasureSpec); int heightSize = MeasureSpec.getSize(heightMeasureSpec); int heightMode = MeasureSpec.getMode(heightMeasureSpec); if (widthMode == MeasureSpec.UNSPECIFIED && heightMode == MeasureSpec.UNSPECIFIED){ wrapContentSize = (int) Math.max(wrapContentSize,mWatchRadius+mWatchPadding); setMeasuredDimension(wrapContentSize,wrapContentSize); }else { setMeasuredDimension(widthSize,heightSize); } }</span>
重寫onDraw方法
來到最關(guān)鍵真正畫表盤時(shí)刻了。一步一步來,首先初始化我們的畫筆(我的習(xí)慣,寫一個(gè)initPaint方法)
<span style="font-size:14px;"> private void initPaint(){ mPaint = new Paint(); mPaint.setAntiAlias(true); mPaint.setColor(Color.WHITE); mPaint.setStyle(Paint.Style.FILL); }</span>
為了不顯贅述,方便理解,我直接展示代碼,在代碼中解釋
開畫之前我們先將畫筆移動(dòng)到控件中心點(diǎn)位置,如下:
<span style="font-size:14px;">@Override protected void onDraw(Canvas canvas) { canvas.translate(getWidth()/2,getHeight()/2); }</span>
第一步,畫表盤
<span style="font-size:14px;"> /** * 畫表盤 * @param canvas */ private void paintWatchBoard(Canvas canvas){ initPaint(); canvas.save(); canvas.drawCircle(0,0,mWatchRadius,mPaint); //畫圓盤 canvas.restore(); }</span>
注:每次畫圖之前都要先調(diào)用canvas.save()方法,保存畫筆屬性,畫完之后要調(diào)用canvas.restore()方法,重置畫筆屬性
這里就不一一展示每次畫完之后的效果圖了。
第二步,畫刻度+整點(diǎn)時(shí)間數(shù)字(刻度從12點(diǎn)方向開始畫)
<span style="font-size:14px;"> /** * 畫刻度及整點(diǎn)數(shù)字 * @param canvas */ private void paintScale(Canvas canvas){ int lineLength; //刻度線長(zhǎng)度 canvas.save(); for (int i = 0;i<60;i++){ if (i%5 == 0){//整點(diǎn)刻度下畫筆相關(guān)屬性 mPaint.setStrokeWidth(MyUtil.dip2px(getContext(),1.5f)); mPaint.setColor(mHourScaleColor); lineLength = MyUtil.dip2px(getContext(),8); canvas.drawLine(0,-mWatchRadius+mWatchScalePadding,0,-mWatchRadius+mWatchScalePadding+lineLength,mPaint); mPaint.setColor(mTimeTextColor); mPaint.setTextSize(mTimeTextSize); canvas.drawText(mTimes[i/5],-mTimeTextSize/2,-mWatchRadius+mWatchScalePadding + lineLength+mTimeTextSize,mPaint);//整點(diǎn)的位置標(biāo)上整點(diǎn)時(shí)間數(shù)字 }else {//非整點(diǎn)刻度下畫筆相關(guān)屬性 mPaint.setStrokeWidth(MyUtil.dip2px(getContext(),0.8f)); mPaint.setColor(mWatchScaleColor); lineLength = MyUtil.dip2px(getContext(),5); canvas.drawLine(0,-mWatchRadius+mWatchScalePadding,0,-mWatchRadius+mWatchScalePadding+lineLength,mPaint); } canvas.rotate(6);//每次畫完一個(gè)刻度線,畫筆順時(shí)針旋轉(zhuǎn)6度(360/60,相鄰兩刻度之間的角度差為6度) } canvas.restore(); }</span>
其中,整點(diǎn)數(shù)字我用了羅馬數(shù)字來表示
<span style="font-size:14px;">private String[] mTimes = {"XII","Ⅰ","Ⅱ","Ⅲ","Ⅳ","Ⅴ","Ⅵ","Ⅶ","Ⅷ","Ⅸ","Ⅹ","XI"};</span>
第三步,畫時(shí)針、分針、秒針以及其它修飾圖
考慮到時(shí)針、分針和秒針大小長(zhǎng)度各不一樣,我這里特意定義了三支畫筆來分別畫時(shí)針、分針和秒針。
同樣的,先初始化指針畫筆:
<span style="font-size:14px;">/** * 初始化指針 */ private void initPointPaint(){ mHourPaint = new Paint(); mHourPaint.setAntiAlias(true); mHourPaint.setStyle(Paint.Style.FILL); mHourPaint.setStrokeWidth(16); mHourPaint.setColor(mHourPointColor); mMinutePaint = new Paint(); mMinutePaint.set(mHourPaint); mMinutePaint.setStrokeWidth(12); mMinutePaint.setColor(mMinutePointColor); mSecondPaint = new Paint(); mSecondPaint.set(mHourPaint); mSecondPaint.setStrokeWidth(7); mSecondPaint.setColor(mSecondPointColor); mEndPointLength = mWatchRadius/6; //(修飾部分)指針尾部長(zhǎng)度,定義為表盤半徑的六分之一 }</span>
畫指針
<span style="font-size:14px;">/** * 畫指針 * @param canvas */ private void paintPoint(Canvas canvas){ initPointPaint(); Calendar c = Calendar.getInstance(); //取當(dāng)前時(shí)間 int hour = c.get(Calendar.HOUR_OF_DAY); int minute = c.get(Calendar.MINUTE); int second = c.get(Calendar.SECOND); //繪制時(shí)針 canvas.save(); canvas.rotate(hour*30); canvas.drawLine(0,0,0,-mHourPointLength,mHourPaint); canvas.drawLine(0,0,0,mEndPointLength,mHourPaint); canvas.restore(); //繪制分針 canvas.save(); canvas.rotate(minute*6); canvas.drawLine(0,0,0,-mMinutePointLength,mMinutePaint); canvas.drawLine(0,0,0,mEndPointLength,mMinutePaint); canvas.restore(); //繪制秒針 canvas.save(); canvas.rotate(second*6); canvas.drawLine(0,0,0,-mSecondPointLength,mSecondPaint); canvas.drawLine(0,0,0,mEndPointLength,mSecondPaint); canvas.restore(); }</span>
OK,該有的差不多都有了,直接在onDraw中調(diào)用吧
<span style="font-size:14px;">@Override protected void onDraw(Canvas canvas) { canvas.translate(getWidth()/2,getHeight()/2); paintWatchBoard(canvas); //畫表盤 paintScale(canvas); //畫刻度 paintPoint(canvas); //畫指針 canvas.drawCircle(0,0,15,mSecondPaint); //為了美觀,也讓表盤更接近我們顯示生活中的樣子,我在圓盤中心畫了一個(gè)大紅圓點(diǎn)裝飾秒針 postInvalidateDelayed(1000); //每隔一秒鐘畫一次 }</span>
(⊙v⊙)嗯,自定義View大功告成,我們?cè)诓季治募镎{(diào)用看下效果吧
<span style="font-size:14px;"><?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:zhusp="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@color/colorAccent"> <com.wondertek.propertyanimatordemo.WatchView android:layout_width="wrap_content" android:layout_height="wrap_content" zhusp:timeTextSize="20dp" zhusp:watchRadius="150dp" zhusp:hourPointLength="80dp" zhusp:minutePointLength="100dp" zhusp:secondPointLength="115dp"/> </RelativeLayout></span>
最后我這里的靜態(tài)效果是這樣的:
相關(guān)文章
Android中通知Notification使用實(shí)例(振動(dòng)、燈光、聲音)
這篇文章主要介紹了Android中通知Notification使用實(shí)例,實(shí)現(xiàn)振動(dòng),燈光,聲音等效果,感興趣的小伙伴們可以參考一下2016-01-01Android Studio實(shí)現(xiàn)簡(jiǎn)單計(jì)算器功能
這篇文章主要為大家詳細(xì)介紹了Android Studio實(shí)現(xiàn)簡(jiǎn)單計(jì)算器功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-03-03Android實(shí)現(xiàn)向Launcher添加快捷方式的方法
這篇文章主要介紹了Android實(shí)現(xiàn)向Launcher添加快捷方式的方法,涉及Android添加快捷方式的相關(guān)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-09-09Android 動(dòng)態(tài)菜單實(shí)現(xiàn)實(shí)例代碼
這篇文章主要介紹了Android 動(dòng)態(tài)菜單實(shí)現(xiàn)實(shí)例代碼的相關(guān)資料,這里附有實(shí)例代碼及實(shí)現(xiàn)效果圖,需要的朋友可以參考下2017-01-01Android中BroadcastReceiver實(shí)現(xiàn)短信關(guān)鍵字自動(dòng)回復(fù)功能
實(shí)現(xiàn)手機(jī)短信監(jiān)聽的方式有兩種:一是通過ContentObserver觀察者實(shí)現(xiàn)監(jiān)聽,另一種就是通過廣播即BroadcastReceiver實(shí)現(xiàn)短信監(jiān)聽,文章中通過使用BroadcastReceiver實(shí)現(xiàn)有新短信的及時(shí)監(jiān)聽及包含設(shè)定的關(guān)鍵字時(shí)自動(dòng)回復(fù)2018-06-06Flutter集成高德地圖并添加自定義Maker的實(shí)踐
本文主要介紹了Flutter集成高德地圖并添加自定義Maker的實(shí)踐,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-04-04Android觸摸事件和mousedown、mouseup、click事件之間的關(guān)系
今天小編就為大家分享一篇關(guān)于Android觸摸事件和mousedown、mouseup、click事件之間的關(guān)系,小編覺得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來看看吧2019-01-01