一個鐘錶帶你進入Android繪圖的世界
前言
顧名思義,就是在Android手機螢幕中繪製我們需要的內容,根據繪製內容的大小(measure),佈局(layout)來講具體內容展示在螢幕中,通過繪製(draw)來實現我們需要的效果.
繪圖原理(三部曲)
Measure
measure操作主要用於計算檢視的大小,即檢視的寬度和長度。在view中定義為final型別,要求子類不能修改。measure()函式中又會呼叫下面的函式:
(1)onMeasure(),檢視大小的將在這裡最終確定,也就是說measure只是對onMeasure的一個包裝,子類可以覆寫onMeasure()方法實現自己的計算檢視大小的方式,並通過setMeasuredDimension(width, height)儲存計算結果。
Layout
layout操作用於設定檢視在螢幕中顯示的位置。在view中定義為final型別,要求子類不能修改。layout()函式中有兩個基本操作:
(1)setFrame(l,t,r,b),l,t,r,b即子檢視在父檢視中的具體位置,該函式用於將這些引數儲存起來;
(2)onLayout(),在View中這個函式什麼都不會做,提供該函式主要是為viewGroup型別佈局子檢視用的;
Draw
draw操作利用前兩部得到的引數,將檢視顯示在螢幕上,到這裡也就完成了整個的檢視繪製工作。子類也不應該修改該方法,因為其內部定義了繪圖的基本操作:
(1)繪製背景;
(2)如果要檢視顯示漸變框,這裡會做一些準備工作;
(3)繪製檢視本身,即呼叫onDraw()函式。在view中onDraw()是個空函式,也就是說具體的檢視都要覆寫該函式來實現自己的顯示(比如TextView在這裡實現了繪製文字的過程)。而對於ViewGroup則不需要實現該函式,因為作為容器是“沒有內容“的,其包含了多個子view,而子View已經實現了自己的繪製方法,因此只需要告訴子view繪製自己就可以了,也就是下面的dispatchDraw()方法;
(4)繪製子檢視,即dispatchDraw()函式。在view中這是個空函式,具體的檢視不需要實現該方法,它是專門為容器類準備的,也就是容器類必須實現該方法;
(5)如果需要(應用程式呼叫了setVerticalFadingEdge或者setHorizontalFadingEdge),開始繪製漸變框;
(6)繪製滾動條;
從上面可以看出自定義View需要最少覆寫onMeasure()和onDraw()兩個方法。
參考:http://blog.csdn.net/xu_fu/article/details/7829721
分類
2D繪圖
基於Android SDK內部自己提供,也是我們學習的主要內容,2D繪圖的api大部分是android.graphics和android.graphics.drawable包中,他們提供了Canvas,ColorFilter,Point和RetcF等,以及一些動畫相關的:AnimationDrawable,BitmapDrawable和TransitionDrawable等.
3D繪圖
用Open GL ES 1.0
參考:http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2012/1212/703.html
2D繪圖
內容
通過canvas物件可以繪製的[弧線],[Bitmap],[圓],[點],[矩形],[圓角矩形],[文字],[路徑]等圖形,其次還提供了旋轉(rotate),縮放(scale),漸變(translate)和扭曲(skew)等轉換方法.
正文
今天就以2D我學習例子,來完成對鐘錶的繪製實現.
知識點
1.Paint的創建於使用
2.Canvas的使用
3.Handler使用
4.onMeasure使用
5.Canvas.restore()和Canvas.save的使用
效果圖:
實現步驟:
1.準備工作,初始化畫筆工具,初始化時間,初始化圓環半徑
2.畫圓環
3.畫刻度
4.移動座標中心到畫布中心
5.畫指標(畫時針,畫分針,畫秒針)
6.畫時間文字
原始碼解析
第一步|準備工作
private Paint mPaintRing;// 圓環畫筆
private Paint mPaintDegree;// 刻度/圓心畫筆
private Paint mPaintText;// 文字畫筆
private float strokeWidthText = 2;// 文字畫筆厚度
private float strokeWidthRing = 4;// 圓環畫筆厚度
private float radius = 0;// 圓環半徑
//用於初始化時間的角度設定
private float hourDegree = 0;// 時針角度
private float minuteDegree = 0;// 分針角度
private float secondDegree = 0;// 秒針角度
private Date mCurrentDate=null;// 使用者設定時間,預設為當前時間
private void initPaint() {
mCurrentDate=new Date();
setDate(mCurrentDate);
// 初始化圓環畫筆
mPaintRing = new Paint();
mPaintRing.setColor(Color.RED);
mPaintRing.setStyle(Paint.Style.STROKE);
mPaintRing.setStrokeWidth(strokeWidthRing);
mPaintRing.setAntiAlias(true);
// 初始化刻度畫筆
mPaintDegree = new Paint();
mPaintDegree.setColor(Color.RED);
mPaintDegree.setStyle(Paint.Style.FILL);
mPaintDegree.setAntiAlias(true);
// 初始化文字畫筆
mPaintText = new Paint();
mPaintText.setColor(Color.RED);
mPaintText.setStyle(Paint.Style.FILL);
mPaintText.setAntiAlias(true);
mPaintText.setStrokeWidth(strokeWidthText);
}
第二步|繪圖
//初始化半徑,由於圓環的厚度也要佔據一定的寬度,因此需要減除厚度值,這樣才能保證圓環
radius = Math.min(getWidth() / 2, getHeight() / 2) - strokeWidthRing;
// 1.畫圓環
canvas.drawCircle(getWidth() / 2, getHeight() / 2, radius, mPaintRing);
// 2.畫刻度
drawMark(canvas);
// 3.畫圓心
canvas.drawCircle(getWidth() / 2, getHeight() / 2, 5, mPaintDegree);
// 4.移動座標中心到畫布中心
canvas.save();
canvas.translate(getWidth() / 2, getHeight() / 2);
canvas.save();
// 5.畫時針
drawHourMark(hourDegree, canvas);
// 6.畫分針
drawMinuteMark(minuteDegree, canvas);
// 7.畫秒針
drawSecondMark(secondDegree, canvas);
// 8.畫時間文字
mPaintText.setTextSize(30);
mPaintText.setTextAlign(Paint.Align.CENTER);
canvas.drawText(XDate.fmtDate(mCurrentDate,"HH:mm:ss"), 0, -20, mPaintText);
完整程式碼
/**
* 繪圖基礎-時鐘<br>
* 部落格:<a href="http://blog.csdn.net/qq243223991">安前鬆部落格</a>
*/
public class ClockView extends View {
private Paint mPaintRing;// 圓環畫筆
private Paint mPaintDegree;// 刻度/圓心畫筆
private Paint mPaintText;// 文字畫筆
private float strokeWidthText = 2;// 文字畫筆厚度
private float strokeWidthRing = 4;// 圓環畫筆厚度
private float radius = 0;// 圓環半徑
//用於初始化時間的角度設定
private float hourDegree = 0;// 時針角度
private float minuteDegree = 0;// 分針角度
private float secondDegree = 0;// 秒針角度
private Date mCurrentDate=null;// 使用者設定時間,預設為當前時間
private Handler handler=new Handler(){
@Override
public void handleMessage(Message msg) {
if(msg.what==0){
long times=mCurrentDate.getTime();
mCurrentDate.setTime(times+1000);
setDate(mCurrentDate);
handler.sendEmptyMessageDelayed(0,1000);
}
}
};
public ClockView(Context context) {
super(context);
initPaint();
}
public ClockView(Context context, AttributeSet attrs) {
super(context, attrs);
initPaint();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int min = Math.min(measureWidth(widthMeasureSpec), measureHeight(heightMeasureSpec));// 防止圓形變形,寬高度必須相等
setMeasuredDimension(min, min);// 重新設定寬高
}
/**
* 根據預設寬度測量寬度
*
* @param measureSpec
* @return
*/
private int measureWidth(int measureSpec) {
int result = 0;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
if (specMode == MeasureSpec.EXACTLY) {
result = specSize;
} else {
result = 200;
if (specMode == MeasureSpec.AT_MOST) {
result = Math.min(result, specSize);
}
}
return result;
}
/**
* 根據預設高度測量高度
*
* @param measureSpec
* @return
*/
private int measureHeight(int measureSpec) {
int result = 0;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
if (specMode == MeasureSpec.EXACTLY) {
result = specSize;
} else {
result = 200;
if (specMode == MeasureSpec.AT_MOST) {
result = Math.min(result, specSize);
}
}
return result;
}
private void initPaint() {
mCurrentDate=new Date();
setDate(mCurrentDate);
// 初始化圓環畫筆
mPaintRing = new Paint();
mPaintRing.setColor(Color.RED);
mPaintRing.setStyle(Paint.Style.STROKE);
mPaintRing.setStrokeWidth(strokeWidthRing);
mPaintRing.setAntiAlias(true);
// 初始化刻度畫筆
mPaintDegree = new Paint();
mPaintDegree.setColor(Color.RED);
mPaintDegree.setStyle(Paint.Style.FILL);
mPaintDegree.setAntiAlias(true);
// 初始化文字畫筆
mPaintText = new Paint();
mPaintText.setColor(Color.RED);
mPaintText.setStyle(Paint.Style.FILL);
mPaintText.setAntiAlias(true);
mPaintText.setStrokeWidth(strokeWidthText);
}
@Override
protected void onDraw(Canvas canvas) {
//初始化半徑,由於圓環的厚度也要佔據一定的寬度,因此需要減除厚度值,這樣才能保證圓環
radius = Math.min(getWidth() / 2, getHeight() / 2) - strokeWidthRing;
// 1.畫圓環
canvas.drawCircle(getWidth() / 2, getHeight() / 2, radius, mPaintRing);
// 2.畫刻度
drawMark(canvas);
// 3.畫圓心
canvas.drawCircle(getWidth() / 2, getHeight() / 2, 5, mPaintDegree);
// 4.移動座標中心到畫布中心
canvas.save();
canvas.translate(getWidth() / 2, getHeight() / 2);
canvas.save();
// 5.畫時針
drawHourMark(hourDegree, canvas);
// 6.畫分針
drawMinuteMark(minuteDegree, canvas);
// 7.畫秒針
drawSecondMark(secondDegree, canvas);
// 8.畫時間文字
mPaintText.setTextSize(30);
mPaintText.setTextAlign(Paint.Align.CENTER);
canvas.drawText(XDate.fmtDate(mCurrentDate,"HH:mm:ss"), 0, -20, mPaintText);
}
/**
* 畫時針
*
* @param degree
* @param canvas
*/
private void drawHourMark(float degree, Canvas canvas) {
canvas.rotate(degree);// 旋轉度數
mPaintDegree.setStrokeWidth(6);
canvas.drawLine(0, 0, 0, -radius / 2, mPaintDegree);
canvas.restore();
canvas.save();
}
/**
* 畫分針
*
* @param degree
* @param canvas
*/
private void drawMinuteMark(float degree, Canvas canvas) {
canvas.rotate(degree);// 旋轉度數
mPaintDegree.setStrokeWidth(3);
canvas.drawLine(0, 0, 0, -radius / 2 - 25, mPaintDegree);
canvas.restore();
canvas.save();
}
/**
* 畫秒針
*
* @param degree
* @param canvas
*/
private void drawSecondMark(float degree, Canvas canvas) {
canvas.rotate(degree);// 旋轉度數
mPaintDegree.setStrokeWidth(2);
canvas.drawLine(0, 0, 0, -radius / 2 - 40, mPaintDegree);
canvas.restore();
canvas.save();
}
/**
* 畫刻度
*/
private void drawMark(Canvas canvas) {
for (int i = 0; i < 60; i++) {
if (i % 5 == 0) {
mPaintDegree.setStrokeWidth(strokeWidthRing);
canvas.drawLine(getWidth() / 2, strokeWidthRing, getWidth() / 2, 40, mPaintDegree);
mPaintText.setTextAlign(Paint.Align.CENTER);
mPaintText.setTextSize(20);
if (i / 5 == 0) {
canvas.drawText("12", getWidth() / 2, 60, mPaintText);
} else {
canvas.drawText(i / 5 + "", getWidth() / 2, 60, mPaintText);
}
} else {
mPaintDegree.setStrokeWidth(2);
canvas.drawLine(getWidth() / 2, strokeWidthRing, getWidth() / 2, 30, mPaintDegree);
}
canvas.rotate(6, getWidth() / 2, getHeight() / 2);
}
}
/**
* 設定時間
*
* @param date
*/
public void setDate(@NonNull Date date) {
mCurrentDate=date;
float hourDegree = getHourDegree(getHours(date), getMinutes(date), getSeconds(date));
float minuteDegree = getMinuteDegree(getMinutes(date), getSeconds(date));
float secondDegree = getSecondDegree(getSeconds(date));
rotate(hourDegree, minuteDegree, secondDegree);
}
/**
* 設定時間戳
*
* @param timeMills
*/
public void setTimeMills(long timeMills) {
Date date = new Date();
date.setTime(timeMills);
setDate(date);
}
/**
* 開始時間旋轉
*/
public void start(){
handler.sendEmptyMessageDelayed(0,1000);
}
/**
* 旋轉
*/
private void rotate(float hourDegree, float minuteDegree, float secondDegree) {
this.hourDegree = hourDegree;
this.minuteDegree = minuteDegree;
this.secondDegree = secondDegree;
invalidate();
}
/**
* 根據小時獲取角度
*
* @param hour
* @return
*/
private float getHourDegree(int hour, int minute, int second) {
float hourDegree = (hour + minute / 60.0f + second / 3600.0f) * 30;
return hourDegree;
}
/**
* 根據分鐘獲取角度
*
* @param minute
* @return
*/
private float getMinuteDegree(int minute, int second) {
float minuteDegree = (minute + second / 60.0f) * 6;
return minuteDegree;
}
/**
* 根據秒鐘獲取角度
*
* @param second
* @return
*/
private float getSecondDegree(int second) {
int secondDegree = second * 6;
return secondDegree;
}
/**
* 獲取時間資訊
*
* @param date
* @return
*/
private HashMap<Integer, Integer> getTime(Date date) {
HashMap<Integer, Integer> time = new HashMap<>();
Calendar calendar = Calendar.getInstance();
calendar.setTime(date);
time.put(Calendar.HOUR, calendar.get(Calendar.HOUR));//12小時制
time.put(Calendar.MINUTE, calendar.get(Calendar.MINUTE));
time.put(Calendar.SECOND, calendar.get(Calendar.SECOND));
return time;
}
/**
* 獲取當天中小時
*
* @param date
* @return
*/
private int getHours(Date date) {
return getTime(date).get(Calendar.HOUR);
}
/**
* 獲取分鐘
*
* @param date
* @return
*/
private int getMinutes(Date date) {
return getTime(date).get(Calendar.MINUTE);
}
/**
* 獲取秒鐘
*
* @param date
* @return
*/
private int getSeconds(Date date) {
return getTime(date).get(Calendar.SECOND);
}
}
覺得有用就支援一下,謝謝!