確定一個矩形最少需要四個數據,就是對角線的兩個點的座標值,這裏一般採用左上角和右下角的兩個點的座標。
關於繪製矩形,Canvas提供了三種重載方法,第一種就是提供四個數值(矩形左上角和右下角兩個點的座標)來確定一個矩形進行繪製。 其餘兩種是先將矩形封裝爲Rect或RectF(實際上仍然是用兩個座標點來確定的矩形),然後傳遞給Canvas繪製,如下:
爲什麼會有Rect和RectF兩種?兩者有什麼區別嗎?
答案當然是存在區別的,兩者最大的區別就是精度不同,Rect是int(整形)的,而RectF是float(單精度浮點型)的。除了精度不同,兩種提供的方法也稍微存在差別,在這裏我們暫時無需關注,想了解更多參見官方文檔 Rect 和 RectF
3.6繪製圓角矩形
下面簡單解析一下圓角矩形的幾個必要的參數的意思。
很明顯可以看出,第二種方法前四個參數和第一種方法的RectF作用是一樣的,都是爲了確定一個矩形,最後一個參數Paint是畫筆,無需多說,與矩形相比,圓角矩形多出來了兩個參數rx 和 ry,這兩個參數是幹什麼的呢?
稍微分析一下,既然是圓角矩形,他的角肯定是圓弧(圓形的一部分),我們一般用什麼確定一個圓形呢?
答案是圓心 和 半徑,其中圓心用於確定位置,而半徑用於確定大小。
由於矩形位置已經確定,所以其邊角位置也是確定的,那麼確定位置的參數就可以省略,只需要用半徑就能描述一個圓弧了。
但是,半徑只需要一個參數,但這裏怎麼會有兩個呢?
好吧,讓你發現了,這裏圓角矩形的角實際上不是一個正圓的圓弧,而是橢圓的圓弧,這裏的兩個參數實際上是橢圓的兩個半徑,他們看起來個如下圖:
紅線標註的 rx 與 ry 就是兩個半徑,也就是相比繪製矩形多出來的那兩個參數。
我們瞭解到原理後,就可以爲所欲爲了,通過計算可知我們上次繪製的矩形寬度爲700,高度爲300,當你讓 rx大於350(寬度的一半), ry大於150(高度的一半) 時奇蹟就出現了, 你會發現圓角矩形變成了一個橢圓, 他們畫出來是這樣的 ( 爲了方便確認我更改了畫筆顏色, 同時繪製出了矩形和圓角矩形 ):
實際上在rx爲寬度的一半,ry爲高度的一半時,剛好是一個橢圓,通過上面我們分析的原理推算一下就能得到,而當rx大於寬度的一半,ry大於高度的一半時,實際上是無法計算出圓弧的,所以drawRoundRect對大於該數值的參數進行了限制(修正),凡是大於一半的參數均按照一半來處理。
3.7繪製橢圓
相對於繪製圓角矩形,繪製橢圓就簡單的多了,因爲他只需要一個矩形矩形作爲參數:
繪製橢圓實際上就是繪製一個矩形的內切圖形,如果你傳遞進來的是一個長寬相等的矩形(即正方形),那麼繪製出來的實際上就是一個圓。原理如下,就不多說了:
3.8繪製圓形
繪製圓形有四個參數,前兩個是圓心座標,第三個是半徑,最後一個是畫筆。
3.9繪製圓弧
繪製圓弧就比較神奇一點了,爲了理解這個比較神奇的東西,我們先看一下它需要的幾個參數:關鍵是後邊的StartAngle是開始掃描的角度,Sweep angle是從開始位置計算起,掃描多少度。userCenter是否啓用中心位置,如果不啓用,扇形的面積是掃描之後起點和結束點的連線,如果是true將會包括中心位置,是一個真正的扇形。
我們來看一下實例代碼:
在看一下正圓的情況下是什麼樣子的:
四、簡單介紹一下畫筆
如果我想繪製一個圓,只要邊不要裏面的顏色怎麼辦?很簡單,繪製的基本形狀Canvas確定,但繪製出來的顏色,具體效果則由Paint確定。設置畫筆的填充模式就可以實現我們想要的效果。如果你注意到了的話,在一開始我們設置畫筆樣式的時候是這樣的:
實例代碼如下
五、下面我們來做一個百分比的餅狀圖來實際操作一下
簡要介紹畫布的操作:
製作一個餅狀圖如下:
簡單分析一下我們所需要的數據和思路
其實根據我們上面的知識已經能自己製作一個餅狀圖了。不過製作東西最重要的不是製作結果,而是製作思路。 相信我貼上代碼大家一看就立刻明白了,非常簡單的東西。不過嘛,咱們還是想了解一下製作思路:
先分析餅狀圖的構成,非常明顯,餅狀圖就是一個又一個的扇形構成的,每個扇形都有不同的顏色,對應的有名字,數據和百分比。
經以上信息可以得出餅狀圖的最基本數據應包括:名字 數據值 百分比 對應的角度 顏色。
用戶關心的數據 : 名字 數據值 百分比
需要程序計算的數據: 百分比 對應的角度
其中顏色這一項可以用戶指定也可以用程序指定(我們這裏採用程序指定)。
封裝數據:
package net.fitrun.mysvg;
/**
* Created by 晁東洋 on 2017/3/28.
* 繪製餅狀圖的數據
*/
public class PieData {
// 用戶關心數據
private String name; // 名字
private float value; // 數值
private float percentage; // 百分比
// 非用戶關心數據
private int color = 0; // 顏色
private float angle = 0; // 角度
public PieData(String name, float value) {
this.name = name;
this.value = value;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public float getValue() {
return value;
}
public void setValue(float value) {
this.value = value;
}
public float getPercentage() {
return percentage;
}
public void setPercentage(float percentage) {
this.percentage = percentage;
}
public int getColor() {
return color;
}
public void setColor(int color) {
this.color = color;
}
public float getAngle() {
return angle;
}
public void setAngle(float angle) {
this.angle = angle;
}
}
自定義View:
package net.fitrun.mysvg;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.util.Log;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import java.util.ArrayList;
/**
* Created by 晁東洋 on 2017/3/24.
*/
public class CustomView extends View {
private Paint mPaint;
// 顏色表(注意: 此處定義顏色使用的是ARGB,帶Alpha通道的)
private int[] mColors = {0xFFCCFF00, 0xFF6495ED, 0xFFE32636, 0xFF800000, 0xFF808000, 0xFFFF8C69, 0xFF808080,
0xFFE6B800, 0xFF7CFC00};
// 餅狀圖初始繪製角度
private float mStartAngle = 0;
//數據
private ArrayList<PieData> mDate;
//寬高
private int mWidth,mHeight;
public CustomView(Context context) {
super(context);
//初始化畫筆
intiPaint();
}
private void intiPaint() {
mPaint = new Paint();
mPaint.setStyle(Paint.Style.FILL); //設置畫筆模式爲填充
//mPaint.setStrokeWidth(10f); //設置畫筆的寬度爲10px
mPaint.setAntiAlias(true);
}
public CustomView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CustomView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public CustomView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//取出寬度的確切數值
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
//取出寬度的測量模式
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
//取出高度的確切數值
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
//取出高度的測量模式
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mWidth = w;
mHeight = h;
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
}
@Override
public void layout(int l, int t, int r, int b) {
super.layout(l, t, r, b);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (null == mDate)
return;;
float currentStartAngle = mStartAngle; //當前的起始角度,爲0度
canvas.translate(mWidth/2,mHeight/2); //將畫布的中心圓點移動到中心位置
float r = (float)(Math.min(mWidth,mHeight)/2*0.8); //繪圖的半徑,取寬和高較小的值
RectF mRectf = new RectF(-r,-r,r,r); //繪圖的區域,其實是一個正方形
//開始根據數據繪製扇形
for (int i =0; i<mDate.size();i++){
PieData pie = mDate.get(i);
mPaint.setColor(pie.getColor());
canvas.drawArc(mRectf,currentStartAngle,pie.getAngle(),true,mPaint);
currentStartAngle += pie.getAngle();
}
}
//設置起始角度
public void setStartAngle(int mStartAngle){
this.mStartAngle = mStartAngle;
invalidate(); //刷新界面
}
//設置數據
public void setData(ArrayList<PieData> mData){
this.mDate = mData;
initData(mData);
invalidate();
}
//計算數據
private void initData(ArrayList<PieData> mData) {
if (null == mData || mData.size() ==0){
return;
}else {
float sumValue =0; //記錄數據的總和
for (int i=0; i<mData.size();i++){
PieData pie = mData.get(i);
sumValue += pie.getValue();
int j = i % mColors.length;
pie.setColor(mColors[j]); //循環的爲每一個設置顏色
}
float sumAngle =0; //總的角度
for (int i=0; i<mData.size();i++){
PieData pie = mData.get(i);
float percentage = pie.getValue()/sumValue; //百分比
float angle = percentage * 360; //對應占的角度是多少
//記錄百分比
pie.setPercentage(percentage);
pie.setAngle(angle);
sumAngle += angle;
Log.e("總的角度",pie.getAngle()+"");
}
}
}
//監聽鍵盤的點擊事件
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
return super.onKeyDown(keyCode, event);
}
//監聽鍵盤的彈起事件
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
return super.onKeyUp(keyCode, event);
}
// 監聽軌跡球的移動事件
@Override
public boolean onTrackballEvent(MotionEvent event) {
return super.onTrackballEvent(event);
}
//觸摸的事件監聽
@Override
public boolean onTouchEvent(MotionEvent event) {
return super.onTouchEvent(event);
}
//視圖焦點變化的監聽
@Override
protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
}
//包含這一視圖的窗口焦點的監聽
@Override
public void onWindowFocusChanged(boolean hasWindowFocus) {
super.onWindowFocusChanged(hasWindowFocus);
}
//視圖被附加add時被調用
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
}
//視圖和窗口分離事調用
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
}
//調用視圖的窗口可見性變化時調用
@Override
protected void onWindowVisibilityChanged(int visibility) {
super.onWindowVisibilityChanged(visibility);
}
}
Activity中引用我們的控件
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final PorterDuffXfermodeView taiJi = new PorterDuffXfermodeView(this);
ArrayList<PieData> mlist = new ArrayList<>();
for (int i =0; i<5;i++){
float mF = 20f;
mF+= mF;
PieData pieData = new PieData("生產",mF);
mlist.add(pieData);
}
CustomView customView = new CustomView(this);
customView.setData(mlist);
customView.setStartAngle(180);
setContentView(customView);
最後是我們的效果圖
最後如果大家喜歡我的學習筆記,可以掃描左邊的二維碼關注我的微信公衆號,有更多的乾貨,更及時的分享給大家。
參考GcsSloop:github詳細內容