Android仿華為天氣繪制刻度盤
效果圖
能夠看到這個自己定義控件結合了顏色漸變、動態繪制刻度、動態水球效果。接下來我們就來看看這個效果是怎樣一步一步實現的。
開始自己定義控件
和非常多自己定義控件方式一樣須要去基礎某種View或者某種ViewGroup
我這裏選擇的是View,例如以下所看到的:
public class HuaWeiView extends View {
/**
* 用來初始化畫筆等
* @param context
* @param attrs
*/
public HuaWeiView(Context context, @Nullable AttributeSet attrs) {
super (context, attrs);
}
/**
* 用來測量限制view為正方形
* @param widthMeasureSpec
* @param heightMeasureSpec
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
/**
* 實現各種繪制功能
* @param canvas
*/
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
}
}
當中構造方法用來布局中使用。
onMeasure()方法用來測量和限定view大小
onDraw()方法用來進行詳細的繪制功能
如想詳細了解請點擊:
構造方法
onMeasure()
MeasureSpec
onDraw()
了解以上方法功能後,我們在來看看怎樣詳細使用吧
1使用onMeasure()方法將View限制為一個正方形
僅僅有確定了一個矩形才幹夠去畫橢圓。假設這個矩形是正方形,橢圓也就隨之變成了圓形。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width=MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
//以最小值為正方形的長
len=Math.min(width,height);
//設置測量高度和寬度(必須要調用。不然無效果)
setMeasuredDimension(len,len);
}
分別通過MeasureSpec取得用戶設置的寬和高。然後取出最小值。設置給我們的view,這樣我們就做好了一個矩形
如今使用在布局中:
<?xml version="1.0" encoding="utf-8"?
>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="@color/colorPrimary"
android:padding="20dp"
tools:context="com.example.huaweiview.MainActivity">
<com.example.huaweiview.HuaWeiView
android:layout_gravity="center"
android:background="@color/colorAccent"
android:layout_width="200dp"
android:layout_height="300dp"
/>
</LinearLayout>
父布局背景為藍色背景,控件背景為粉色背景。並且設置的寬高不同。可是控件的顯示效果還是一個正方形,並且以小值為準。我們的onMeasure()生效了
接下來就是怎樣在確定一個圓形區域了
2onDraw()繪制圓形區域
繪制之前我們須要對Android中的坐標系有個了解
我們都知道手機屏幕左上角為坐標原點。往右為X正軸。往下為Y正軸。事實上手機頁面就是activity的展示界面,也是一個View。那可不能夠說全部的View在繪制圖形的時候都有自己的這麽一個坐標系呢(個人想法。。。
)
也就是所每一個View都有自己的一個坐標系,比方如今的自己定義View:
如今我們須要在我們自己定義的view中繪制一個圓弧,那麽這個圓弧的半徑就是我們自己定義view的長度的一半。即:
radius=len/2;
那麽圓心的坐標剛好是(radius,radius)
接下來開始繪制
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//畫圓弧的方法
canvas.drawArc(oval, startAngle, sweepAngle, useCenter,paint);
}
介紹一下繪制圓弧的方法:
- 參數一oval是一個RectF對象為一個矩形
- 參數二startAngle為圓弧的起始角度
- 參數三sweepAngle為圓弧的經過角度(掃過角度)
- 參數四useCenter為圓弧是一個boolean值,為true時畫的是圓弧。為false時畫的是割弧
- 參數五paint為一個畫筆對象
也就是說僅僅要確定了一個矩形,在確定他起始和經過的角度就能夠畫出一個圓弧(這點大家能夠用畫板測試)
接下來就是初始化這些參數
初始化矩形
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
//以最小值為正方形的長
len = Math.min(width, height);
//實例化矩形
oval=new RectF(0,0,len,len);
//設置測量高度和寬度(必須要調用。不然無效果)
setMeasuredDimension(len, len);
}
畫矩形須要確定左上角和右下角的坐標(通過畫板能夠測試),通過上面的分析坐標原點就是我們view的左上角。右下角的坐標當然就是len了。
接下來就是初始化起始和經過角度
private float startAngle=120;
private float sweepAngle=300;
須要搞清楚往下為Y軸正軸,剛好和上學時候學的相反,也就是說90度在下方,-90度在上方
初始化畫筆
public HuaWeiView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
paint =new Paint();
//設置畫筆顏色
paint.setColor(Color.WHITE);
//設置畫筆抗鋸齒
paint.setAntiAlias(true);
//讓畫出的圖形是空心的(不填充)
paint.setStyle(Paint.Style.STROKE);
}
useCenter=false
到這裏真不easy呀,然而發現僅僅畫個圓弧沒用呀,我要的是刻度線呀。canvas裏面又沒用給我們提供畫刻度線的方法,這個時候就須要我們自己去寫一個畫刻度線的方法了。
通過觀察圖片我們能夠看出,全部的線都是從圓弧上的點為起點向某個方向畫一條直線,那麽該怎樣確定這兩個點呢,須要我們做兩件事:
移動坐標系
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//畫圓弧的方法
canvas.drawArc(oval, startAngle, sweepAngle, useCenter,paint);
//畫刻度線的方法
drawViewLine(canvas);
}
private void drawViewLine(Canvas canvas) {
//先保存之前canvas的內容
canvas.save();
//移動canvas(X軸移動距離。Y軸移動距離)
canvas.translate(radius,radius);
//操作完畢後恢復狀態
canvas.restore();
}
我們自己寫了一個繪制刻度線的方法並在onDraw()方法中調用。移動坐標系之前須要保存之前的canvas狀態,然後X和Y軸分別移動圓弧半徑的距離,例如以下圖:
canvas.translate(radius,radius);方法移動的是坐標系(通過實際效果和查資料所得)
canvas.save()和canvas.restore()要成對出現。就好像流用完要關閉一樣。
第一件事情完畢後,開始第二件事情,旋轉坐標系
僅僅通過移動坐標系,仍然非常難確定圓弧點上的坐標,和另外一點的坐標,
假設這兩個點都在坐標軸上該多好呀。以下實現:
private void drawViewLine(Canvas canvas) {
//先保存之前canvas的內容
canvas.save();
//移動canvas(X軸移動距離。Y軸移動距離)
canvas.translate(radius,radius);
//旋轉坐標系
canvas.rotate(30);
//操作完畢後恢復狀態
canvas.restore();
}
畫刻度線的方法了添加了一個旋轉30度的代碼,旋轉後的坐標系應該怎麽樣呢;
由於起始點和90度相差30,旋轉之後。起始點剛好落在了Y軸上,那麽這個點的坐標就非常好確定了吧。沒錯就是(0,radius);假設我們在Y軸上在找一點不就能夠畫出一條刻度線了嗎。那麽它的坐標是多少呢?對,應該是(0,radius-y)。由於我們要往內部化刻度線,因此是減去一個值,趕快去試試吧,代碼例如以下:
private void drawViewLine(Canvas canvas) {
//先保存之前canvas的內容
canvas.save();
//移動canvas(X軸移動距離。Y軸移動距離)
canvas.translate(radius,radius);
//旋轉坐標系
canvas.rotate(30);
Paint linePatin=new Paint();
//設置畫筆顏色
linePatin.setColor(Color.WHITE);
//線寬
linePatin.setStrokeWidth(2);
//設置畫筆抗鋸齒
linePatin.setAntiAlias(true);
//畫一條刻度線
canvas.drawLine(0,radius,0,radius-40,linePatin);
//操作完畢後恢復狀態
canvas.restore();
}
依據得到的兩個點的坐標,畫出來一條白線,如圖:
當然這些點都是移動後的坐標系在旋轉30度得到的,這裏畫好了一條線。假設畫多條呢,還是剛才的思路每次都讓它旋轉一個小角度然後畫條直線不就好了嗎。那麽旋轉多少度呢,比方這裏:總共掃過的角度sweepAngle=300;須要100條刻度,那麽每次須要旋轉的角度rotateAngle=sweepAngle/100,詳細代碼例如以下:
private void drawViewLine(Canvas canvas) {
//先保存之前canvas的內容
canvas.save();
//移動canvas(X軸移動距離,Y軸移動距離)
canvas.translate(radius,radius);
//旋轉坐標系
canvas.rotate(30);
Paint linePatin=new Paint();
//設置畫筆顏色
linePatin.setColor(Color.WHITE);
//線寬
linePatin.setStrokeWidth(2);
//設置畫筆抗鋸齒
linePatin.setAntiAlias(true);
//確定每次旋轉的角度
float rotateAngle=sweepAngle/99;
for(int i=0;i<100;i++){
//畫一條刻度線
canvas.drawLine(0,radius,0,radius-40,linePatin);
canvas.rotate(rotateAngle);
}
//操作完畢後恢復狀態
canvas.restore();
}
100個刻度,須要101次循環畫線(請看你的手表)。畫完線就旋轉。
依次循環,如圖
經過這麽久的時間總於完畢了刻度盤了,接下來就是去確定不同角度顯示什麽樣的顏色,首選我們須要確定要繪制的範圍targetAngle:
繪制有色部分
private void drawViewLine(Canvas canvas) {
//先保存之前canvas的內容
canvas.save();
//移動canvas(X軸移動距離,Y軸移動距離)
canvas.translate(radius,radius);
//旋轉坐標系
canvas.rotate(30);
Paint linePatin=new Paint();
//設置畫筆顏色
linePatin.setColor(Color.WHITE);
//線寬
linePatin.setStrokeWidth(2);
//設置畫筆抗鋸齒
linePatin.setAntiAlias(true);
//確定每次旋轉的角度
float rotateAngle=sweepAngle/100;
//繪制有色部分的畫筆
Paint targetLinePatin=new Paint();
targetLinePatin.setColor(Color.GREEN);
targetLinePatin.setStrokeWidth(2);
targetLinePatin.setAntiAlias(true);
//記錄已經繪制過的有色部分範圍
float hasDraw=0;
for(int i=0;i<=100;i++){
if(hasDraw<=targetAngle&&targetAngle!=0){//須要繪制有色部分的時候
//畫一條刻度線
canvas.drawLine(0,radius,0,radius-40,targetLinePatin);
}else {//不須要繪制有色部分
//畫一條刻度線
canvas.drawLine(0,radius,0,radius-40,linePatin);
}
//累計繪制過的部分
hasDraw+=rotateAngle;
//旋轉
canvas.rotate(rotateAngle);
}
//操作完畢後恢復狀態
canvas.restore();
}
我們須要不斷的去記錄繪制過的有效部分,之外的部分畫白色。
依據角度的比例,顏色漸變
須要計算出已經繪制過的角度占總角度(300)的比例
for(int i=0;i<=100;i++){
if(hasDraw<=targetAngle&&targetAngle!=0){//須要繪制有色部分的時候
//計算已經繪制的比例
float percent=hasDraw/sweepAngle;
int red= 255-(int) (255*percent);
int green= (int) (255*percent);
targetLinePatin.setARGB(255,red,green,0);
//畫一條刻度線
canvas.drawLine(0,radius,0,radius-40,targetLinePatin);
}else {//不須要繪制有色部分
//畫一條刻度線
canvas.drawLine(0,radius,0,radius-40,linePatin);
}
hasDraw+=rotateAngle;
canvas.rotate(rotateAngle);
}
僅僅是在繪制有色部分的時候,利用三元素來實現漸變。
所占比例越低紅色值越大,反正綠色值越大。
實現動態顯示
先想一下它的運動情況。分為前進狀態和後退狀態。假設正在運動(一次完整的後退和前進沒用結束)。就不能開始下次運動,須要兩個參數,state和isRunning
//推斷是否在動
private boolean isRunning;
//推斷是回退的狀態還是前進狀態
private int state = 1;
public void changeAngle(final float trueAngle) {
if (isRunning){//假設在動直接返回
return;
}
final Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
switch (state) {
case 1://後退狀態
isRunning=true;
targetAngle -= 3;
if (targetAngle <= 0) {//假設回退到0
targetAngle = 0;
//改為前進狀態
state = 2;
}
break;
case 2://前進狀態
targetAngle += 3;
if (targetAngle >= trueAngle) {//假設添加到指定角度
targetAngle = trueAngle;
//改為後退狀態
state = 1;
isRunning=false;
//結束本次運動
timer.cancel();
}
break;
default:
break;
}
//又一次繪制(子線程中使用的方法)
postInvalidate();
}
}, 500, 30);
}
利用時間任務。每一個30毫秒去運行一次run方法,每次都又一次繪制圖片,然後在activity中調用此方法
HuaWeiView hwv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
hwv= (HuaWeiView) findViewById(R.id.hwv);
hwv.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//點擊事件中。調用動的方法
hwv.changeAngle(200);
}
});
}
看到這裏了,相信你對坐標系和角度動態變化,以及刻度盤的繪制有了個非常好的認識。多多驗證會有助於理解。
接下來要實現背景動態漸變
想想咱們的view中哪裏用了漸變呢?對。在繪制有色部分的時候。假設我們能將顏色漸變的值不斷的傳到activity中該多好呀,以下就要用接口傳值實現這一功能了:
- 首選在自己定義View中聲明一個內部接口:
private OnAngleColorListener onAngleColorListener;
public void setOnAngleColorListener(OnAngleColorListener onAngleColorListener) {
this.onAngleColorListener = onAngleColorListener;
}
public interface OnAngleColorListener{
void colorListener(int red,int green);
}
我們在自己定義View中聲明一個內部接口。並聲明一個全局接口對象。提供一個set方法
接口內有個方法用來獲取顏色值
接下來就是在合適的地方調用這種方法,那麽哪裏呢。就是我們繪制顏色刻度時調用:
for (int i = 0; i <= 100; i++) {
if (hasDraw <= targetAngle && targetAngle != 0) {//須要繪制有色部分的時候
//計算已經繪制的比例
float percent = hasDraw / sweepAngle;
int red = 255 - (int) (255 * percent);
int green = (int) (255 * percent);
//實現接口回調,傳遞顏色值
if (onAngleColorListener!=null){
onAngleColorListener.colorListener(red,green);
}
targetLinePatin.setARGB(255, red, green, 0);
//畫一條刻度線
canvas.drawLine(0, radius, 0, radius - 40, targetLinePatin);
} else {//不須要繪制有色部分
//畫一條刻度線
canvas.drawLine(0, radius, 0, radius - 40, linePatin);
}
我們在繪制的時候實現了接口回調,接下來去activity中實現接口
public class MainActivity extends AppCompatActivity {
HuaWeiView hwv;
LinearLayout ll_parent;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
hwv= (HuaWeiView) findViewById(R.id.hwv);
//實例父布局
ll_parent= (LinearLayout) findViewById(R.id.ll_parent);
hwv.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//點擊事件中。調用動的方法
hwv.changeAngle(200);
}
});
//設置角度顏色變化監聽
hwv.setOnAngleColorListener(new HuaWeiView.OnAngleColorListener() {
@Override
public void colorListener(int red, int green) {
Color color=new Color();
//通過Color對象將RGB值轉為int類型
int backColor=color.argb(100,red,green,0);
//父布局設置背景
ll_parent.setBackgroundColor(backColor);
}
});
}
}
給父布局一個id,然後實例化。
給我們的自己定義控件設置一個角度顏色變化監聽,從而拿到回調中傳過來的值。然後借助Color對象將RGB值轉為int值,再設置給父布局背景。這裏背景稍稍透明一些。
效果圖:
到了這裏是不是感覺炫酷了不少呢,事實上功能已經實現的幾乎相同了,接下來就是去繪制裏面的內容吧
繪制文字
當然不去繪制文字也是能夠的。你能夠直接在布局中加入textview等。好話不多說,先分析一下繪制的過程吧,在刻度盤的內部有一個小圓。然後這些文字就在小圓內,繪制小圓僅僅須要讓它的半徑小點就OK了。
/**
* 繪制小圓和文本的方法,小圓顏色相同漸變
* @param canvas
*/
private void drawScoreText(Canvas canvas) {
//先繪制一個小圓
Paint smallPaint = new Paint();
smallPaint.setARGB(100,red,green,0);
// 畫小圓指定圓心坐標,半徑,畫筆就可以
int smallRadius=radius-60;
canvas.drawCircle(radius, radius, radius - 60, smallPaint);
//繪制文本
Paint textPaint=new Paint();
//設置文本居中對齊
textPaint.setTextAlign(Paint.Align.CENTER);
textPaint.setColor(Color.WHITE);
textPaint.setTextSize(smallRadius/2);
//score須要通過計算得到
canvas.drawText(""+score,radius,radius,textPaint);
//繪制分,在分數的右上方
textPaint.setTextSize(smallRadius/6);
canvas.drawText("分",radius+smallRadius/2,radius-smallRadius/4,textPaint);
//繪制點擊優化在分數的下方
textPaint.setTextSize(smallRadius/6);
canvas.drawText("點擊優化",radius,radius+smallRadius/2,textPaint);
}
這裏將之前漸變的red和green提為全局變量。先繪制一個小圓。畫筆顏色漸變。
然後繪制文字分數score須要通過計算的到
//計算得到的分數
score=(int)(targetAngle/sweepAngle*100);
//又一次繪制(子線程中使用的方法)
postInvalidate();
在時間任務中。每次繪制之前計算得到分數。然後在右上方畫一個固定值分。再在下方一個固定內容點擊優化(這個時候的坐標已經回到最初的模樣)
到此為止功能已經寫的幾乎相同了。另一個水波加速球效果,下篇博客中寫吧。
最後對於原理底層方面,我也有待學習,有錯的地方歡迎指正,謝謝。
項目已經上傳到github
github點擊下載
最後的最後。個人淘寶店(抱歉。請見諒)。。霓裳雅閣
Android仿華為天氣繪制刻度盤