前幾天閑暇之余,寫了一個WatchView。
http://www.lxweimin.com/p/db6d25c809d3
不過真正要用起來其實靈活性并不好,所以我又改了一下,使其屬性可以定制化。
修改了:
- 在12點、3點、6點、9點刻度時,短線不會多畫一條,導致這四條長線顯得粗。
- 在秒針尾端加了一截,實際的表秒針也是有一截的。:) 實現方法很簡單,就是在秒針繪制的反方向再畫一條短線,相同的速度。
- 自定義屬性,實現可以定制化。
還需要完善的地方:
- 1~12點時間文字除了12和6其他事歪的,想要把他掰正,我有強迫癥:)
- 不同分辨率下實現文字線條靈活配置,希望能一個xml解決多個機型下的顯示。
- 缺少一點立體感,比如背景可以加個過渡繪制,顯得比較立體又感覺。
- 我對比了我mac上面的時間,view的時間相對于mac的時間慢了幾秒3秒左右。沒有確認具體原因。
在xml中添加屬性,即可輕松的使用。
首先看下我定義了哪些屬性:
自定義屬性定制
attr.xml
<!--WatchView Style start-->
<declare-styleable name="WatchView">
<attr name="viewSize" format="dimension"></attr>
<attr name="borderColor" format="color"></attr>
<attr name="backgroundColor" format="color"></attr>
<attr name="textSize" format="dimension"></attr>
<attr name="secondlineColor" format="color"></attr>
<attr name="hourlineColor" format="color"></attr>
<attr name="textColor" format="color"></attr>
<attr name="minutelineColor" format="color"></attr>
<attr name="hourlinePaintStrockwidth" format="dimension"></attr>
<attr name="minutelinePaintStrockwidth" format="dimension"></attr>
<attr name="secondlinePaintStrockwidth" format="dimension"></attr>
<attr name="borderLinePaintStrockWidth" format="dimension"></attr>
</declare-styleable>
<!--WatchView Style end-->
相信通過名稱你也看的出來是哪些屬性:
size,邊框顏色,時分秒線條顏色,文字顏色大小,線條粗細等。
使用
接下來在xml中引入我們的View,并使用自定義屬性:
<com.example.xxiang1x.teststudio.WatchView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="35dip"
android:layout_marginTop="120dip"
app:backgroundColor="#EBEBF3"
app:borderColor="#808080"
app:borderLinePaintStrockWidth="2dp"
app:hourlineColor="#000000"
app:hourlinePaintStrockwidth="5dp"
app:minutelineColor="#000000"
app:minutelinePaintStrockwidth="5dp"
app:secondlineColor="#DC143C"
app:secondlinePaintStrockwidth="3dp"
app:textSize="22sp"
app:viewSize="350dp" />
java代碼實現
java代碼中有足夠多的注釋相信能看懂。代碼不至于特別糟糕吧:)
package com.example.xxiang1x.teststudio;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.util.AttributeSet;
import android.view.View;
import java.util.Calendar;
import java.util.GregorianCalendar;
/**
* 手表表盤
* ReadMe:
* 實現:表盤中,各個元素比如時分秒的線顏色,粗細,背景顏色,邊框顏色,字體顏色,大小等都是可以在xml中自定義實現各個需求
* 目前還未完善的部分是還未在各個分辨率下查看效果,因為每個機型不一樣分辨率也不一樣,導致顯示的效果也不一樣。如何以
* 更小的改動來適應變化的因素,需要另外完善一下。另外,時間的文字還可以改成全部是正的,而不是除了12和6點其他都是歪的。
*
*/
public class WatchView extends View {
//create some variables
/**
* 分針畫筆
*/
private Paint minPaint;
/**
* 秒針畫筆
*/
private Paint secondPaint;
/**
* 時針畫筆
*/
private Paint hourPaint;
/**
* 表盤外輪廓畫筆
*/
private Paint circlePaint;
/**
* 表盤文字的畫筆
*/
private Paint textPaint;
/**
* 表盤內短線畫筆
*/
private Paint linePaint;
/**
* 背景顏色畫筆
*/
private Paint backgroundPaint;
private static final double ROUND = 2d * Math.PI;
private static final double QUARTER = 1d / 4d;
private Handler mHandler = new Handler(Looper.getMainLooper());
private Calendar calendar = new GregorianCalendar();
public WatchView(Context context) {
super(context);
initAttributes(context, null);
initPaint();
}
public WatchView(Context context, AttributeSet attrs) {
super(context, attrs);
initAttributes(context, attrs);
initPaint();
}
/////////////////////////////Attributes from attr.xml
/**
* 整個WatchView的大小,因為WatchView是一個圓,所以我們在處理的時候默認就是以正方形為基礎來看待。width==height
*/
private int mSize = 220;
/**
* 外邊框的顏色
*/
private int borderColor = Color.GRAY;
/**
* 背景顏色
*/
private int backgroundColor = Color.WHITE;
/**
* 秒針顏色
*/
private int secondLineColor = Color.RED;
/**
* 分針顏色
*/
private int minuteLineColor = Color.BLUE;
/**
* 時針顏色
*/
private int hourLineColor = Color.GRAY;
/**
* 時間文字顏色
*/
private int textColor = Color.GRAY;
/**
* 時間文字大小
*/
private int textSize = 22;
//////////////////////////Others:Paint: strockWidth;
/**
* 時針劃線粗細
*/
private int hourLinePaintStrockWidth = 5;
/**
* 秒針劃線粗細
*/
private int secondLinePaintStrockWidth = 3;
/**
* 分針線粗細
*/
private int minuteLinePaintStrockWidth = 5;
/**
* 邊框線粗細
*/
private int borderLinePaintStrockWidth = 5;
/**
* text線條粗細
*/
private int textPaintStrockWidth = 5;
/**
* initialize the attributes.
*
* @param ctx
* @param attrs
*/
private void initAttributes(Context ctx, AttributeSet attrs) {
TypedArray typeArray = null;
try {
typeArray = ctx.obtainStyledAttributes(attrs, R.styleable.WatchView);
mSize = typeArray.getDimensionPixelOffset(R.styleable.WatchView_viewSize, mSize);
borderColor = typeArray.getColor(R.styleable.WatchView_borderColor, borderColor);
backgroundColor = typeArray.getColor(R.styleable.WatchView_backgroundColor, backgroundColor);
secondLineColor = typeArray.getColor(R.styleable.WatchView_secondlineColor, secondLineColor);
minuteLineColor = typeArray.getColor(R.styleable.WatchView_minutelineColor, minuteLineColor);
hourLineColor = typeArray.getColor(R.styleable.WatchView_hourlineColor, hourLineColor);
textColor = typeArray.getColor(R.styleable.WatchView_textColor, textColor);
textSize = typeArray.getDimensionPixelSize(R.styleable.WatchView_textSize, 22);
hourLinePaintStrockWidth = typeArray.getDimensionPixelSize(R.styleable.WatchView_hourlinePaintStrockwidth, 5);
minuteLinePaintStrockWidth = typeArray.getDimensionPixelSize(R.styleable.WatchView_minutelinePaintStrockwidth, 5);
secondLinePaintStrockWidth = typeArray.getDimensionPixelSize(R.styleable.WatchView_secondlinePaintStrockwidth, 3);
borderLinePaintStrockWidth = typeArray.getDimensionPixelSize(R.styleable.WatchView_borderLinePaintStrockWidth, 3);
} finally {
typeArray.recycle();
}
}
/**
* 創建所有的paint
*/
private void initPaint() {
minPaint = getLinePaint(minuteLineColor, minuteLinePaintStrockWidth, Paint.Style.FILL_AND_STROKE);
secondPaint = getLinePaint(secondLineColor, secondLinePaintStrockWidth, Paint.Style.FILL_AND_STROKE);
hourPaint = getLinePaint(hourLineColor, hourLinePaintStrockWidth, Paint.Style.FILL_AND_STROKE);
circlePaint = getLinePaint(borderColor, borderLinePaintStrockWidth, Paint.Style.STROKE);
textPaint = getTextPaint(textColor, textSize, textPaintStrockWidth, Paint.Style.FILL);
linePaint = getLinePaint(textColor, textPaintStrockWidth, Paint.Style.FILL);
backgroundPaint = getLinePaint(backgroundColor, mSize / 2, Paint.Style.FILL);
}
/**
* get the Line Paint
*
* @param color
* @param strockWidth
* @param style
* @return
*/
private Paint getLinePaint(int color, int strockWidth, Paint.Style style) {
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FAKE_BOLD_TEXT_FLAG);
paint.setAntiAlias(true);
paint.setAlpha(1);
paint.setColor(color);
paint.setStyle(style);
paint.setStrokeWidth(strockWidth);
paint.setStrokeCap(Paint.Cap.ROUND);
paint.setTextAlign(Paint.Align.CENTER);
paint.setStrokeCap(Paint.Cap.ROUND);
paint.setDither(true);//設置圖像抖動處理
paint.setStrokeJoin(Paint.Join.ROUND);//畫筆線等連接處的輪廓樣式
paint.setSubpixelText(true);
return paint;
}
/**
* get a paint by ur request.
*
* @param color
* @param textSize
* @param strockWidth
* @param style
* @return
*/
private Paint getTextPaint(int color, int textSize, int strockWidth, Paint.Style style) {
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FAKE_BOLD_TEXT_FLAG);
paint.setAntiAlias(true);
paint.setTextSize(textSize);
paint.setAlpha(1);
paint.setColor(color);
paint.setStyle(style);
paint.setStrokeWidth(strockWidth);
paint.setStrokeCap(Paint.Cap.ROUND);
paint.setTextAlign(Paint.Align.CENTER);
paint.setStrokeCap(Paint.Cap.ROUND);
paint.setDither(true);//設置圖像抖動處理
paint.setStrokeJoin(Paint.Join.ROUND);//畫筆線等連接處的輪廓樣式
paint.setSubpixelText(true);
return paint;
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public WatchView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//獲取圓心坐標
int x, y;
x = y = mSize / 2;
//半徑
int radius = Math.min(x, y) - borderLinePaintStrockWidth;
// 為了讓秒針連續轉動,所以秒針的角度肯定不是從「秒」這個整數里來
// 剛好秒針走一圈是 1 分鐘,那么,就用分乘以「一圈(2π)」就是秒針要走的弧度數了
float millis = calendar.get(Calendar.MILLISECOND) / 1000f;
float second = (calendar.get(Calendar.SECOND) + millis) / 60f;
float minute = (calendar.get(Calendar.MINUTE) + second) / 60f;
float hour = (calendar.get(Calendar.HOUR) + minute) / 12f;
//畫背景顏色
canvas.drawCircle(x, y, radius, backgroundPaint);
//draw outer circle
circlePaint.setColor(borderColor);
circlePaint.setStyle(Paint.Style.STROKE);
canvas.drawCircle(x, y, radius, circlePaint);
//draw the hour line .
drawHourNumbers(canvas, textPaint, mSize, mSize);
//draw the minutes line .
drawMinuteLine(canvas, textPaint, mSize, mSize);
//畫時針,分針,秒針
drawHand(canvas, hourPaint, x, y, radius * 0.6f, hour);
drawHand(canvas, minPaint, x, y, radius * 0.8f, minute);
drawHand(canvas, secondPaint, x, y, radius * 0.9f, second);
drawSecondExtraHand(canvas, secondPaint, x, y, radius * 0.1f, second);
//draw center point
circlePaint.setColor(secondLineColor);
circlePaint.setStyle(Paint.Style.FILL);
canvas.drawCircle(x, y, secondLinePaintStrockWidth, circlePaint);
}
/**
* 畫小時的線,畫這個線都是以邊框寬度以及整個WatchView的大小來計算,這樣靈活性比較好。不會因為改動了
* WatchView的大小而導致要重新調整各個計算。
*
* @param canvas
* @param paint
* @param width
* @param height
*/
private void drawHourNumbers(Canvas canvas, Paint paint, int width, int height) {
//12個小時,12條線。
for (int i = 1; i < 13; i++) {
canvas.save(); //save current state of canvas.
canvas.rotate(360 / 12 * i, width / 2, height / 2);
//繪制表盤
canvas.drawLine(width / 2, borderLinePaintStrockWidth, width / 2, height / 15, paint);
//繪制文字
canvas.drawText(String.valueOf(i), width / 2, height / 15 * 2, paint);
//恢復開始位置
canvas.restore();
}
}
/**
* 畫分鐘的線
*
* @param canvas
* @param paint
* @param width
* @param height
*/
private void drawMinuteLine(Canvas canvas, Paint paint, int width, int height) {
//一共分出來有60條線
for (int i = 1; i < 61; i++) {
float degree = 360 / 12 / 5 * i;
//這里要避開下面幾個角度,否則會發生重疊繪制線條。你可以把判斷去掉發現12點,3點,6點,9點的線條哦要粗得多。
if (degree % 90 != 0) {//90 ,180,270,360
canvas.save(); //save current state of canvas.
canvas.rotate(degree, width / 2, height / 2);
//繪制表盤
canvas.drawLine(width / 2, borderLinePaintStrockWidth, width / 2, height/15/2, paint);
//恢復開始位置
canvas.restore();
}
}
}
/**
* @param canvas
* @param paint
* @param x
* @param y
* @param length
* @param round
*/
private void drawHand(Canvas canvas, Paint paint, float x, float y, float length, float round) {
// 三角函數的坐標軸是以 3 點方向為 0 的,所以記得要減去四分之一個圓周哦
double radians = (round - QUARTER) * ROUND;
canvas.drawLine(
x,
y,
x + (float) Math.cos(radians) * length,
y + (float) Math.sin(radians) * length,
paint);
}
/**
* draw the opposite direction's line .
*
* @param canvas
* @param paint
* @param x
* @param y
* @param length
* @param round
*/
private void drawSecondExtraHand(Canvas canvas, Paint paint, float x, float y, float length, float round) {
// 三角函數的坐標軸是以 3 點方向為 0 的,所以記得要減去四分之一個圓周哦
double radians = (round - QUARTER * 3) * ROUND;
canvas.drawLine(
x,
y,
x + (float) Math.cos(radians) * length,
y + (float) Math.sin(radians) * length,
paint);
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
mHandler.post(r);
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
mHandler.removeCallbacksAndMessages(null);//remove all of the messages.
}
Runnable r = new Runnable() {
@Override
public void run() {
calendar.setTimeInMillis(System.currentTimeMillis());
invalidate();
mHandler.postDelayed(this, 1000 / 60);
}
};
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int newSpec = MeasureSpec.makeMeasureSpec(mSize, MeasureSpec.getMode(Math.min(widthMeasureSpec, heightMeasureSpec)));
super.onMeasure(newSpec, newSpec);
}
//////////////////////////////
/**
* dp轉化為px unit
*
* @param dpValue
* @return
*/
private int dp2px(int dpValue) {
if (dpValue == 0) {
return 0;
}
final float density = getResources().getDisplayMetrics().density;
return (int) ((dpValue * density) + 0.5f);
}
/**
* 根據手機的分辨率從 px(像素) 的單位 轉成為 dp
*/
public int px2dp(float pxValue) {
final float scale = getResources().getDisplayMetrics().density;
return (int) (pxValue / scale + 0.5f);
}
}
運行效果
WatchView.gif
沒有找到一個比較好用的錄制工具,這個gif錄制出來效果不好??雌饋砻脶樧咭换赝R幌滤频?。擦....
結尾
- 自定義View其實蠻重要,很多知識點還需要學習,深入研究。希望后期會有比較好的成果展示分享出來。