首页 > 代码库 > android自定义ClockView

android自定义ClockView

  最近由于项目需要,需要自制一个钟表视图,并加一些业务逻辑,所以根据自定义一个View的步骤,自制了一个钟表,见下图:

下面是我自定义View的代码,参考了网上大神的代码,自己做了一些项目业务的逻辑,优化了一下整个View.

package com.hp.clock;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.PaintFlagsDrawFilter;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.text.format.DateUtils;
import android.text.format.Time;
import android.util.AttributeSet;
import android.view.View;

/**
 * 时钟控件
 *
 */
public class ClockView extends View {
	private Time mCalendar;

	private final Drawable mHourHand; // 时钟时针图片
	private final Drawable mMinuteHand; // 时钟分针图片
	private final Drawable mSecondHand; // 时钟秒针图片
	private final Drawable mDial; // 时钟表盘图片

	private final int mDialWidth; // 组件宽度
	private final int mDialHeight; // 组件高度

	private boolean mAttached;

	private final Handler mHandler = new Handler();
	private float mSeconds; // 秒数,例如23s
	private float mMinutes; // 分钟数,例如12m
	private float mHour; // 小时数,例如5h
	private boolean mChanged;
	private final Context mContext;
//	private String mTimeZoneId;
	private int mTimeZoneOffset = 0; // 时差,单位是小时
	private boolean mNoSeconds = false; // 用来判断是否需要显示秒针
	
	public ClockView(Context context) {
		this(context, null);
	}

	public ClockView(Context context, AttributeSet attrs) {
		this(context, attrs, 0);
	}

	public ClockView(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);
		mContext = context;
		mCalendar = new Time();
		
		// 此处获取自定义的组件属性,定义在attr.xml中,用法详见activity_main.xml布局文件
		TypedArray a = context.obtainStyledAttributes(attrs,
				R.styleable.ClockView);

		mDial = a.getDrawable(R.styleable.ClockView_dialogDrawable);
		mHourHand = a.getDrawable(R.styleable.ClockView_hourHandDrawable);
		mMinuteHand = a.getDrawable(R.styleable.ClockView_minuteHandDrawable);
		mSecondHand = a.getDrawable(R.styleable.ClockView_secondHandDrawable);

		mDialWidth = mDial.getIntrinsicWidth();
		mDialHeight = mDial.getIntrinsicHeight();

		a.recycle();
	}

	/* 
	 * 组件attach到窗口上的回调
	 * 
	 * @see android.view.View#onAttachedToWindow()
	 */
	@Override
	protected void onAttachedToWindow() {
		super.onAttachedToWindow();

		// 防止重复注册监听
		if (!mAttached) {
			mAttached = true;
			IntentFilter filter = new IntentFilter();

			filter.addAction(Intent.ACTION_TIME_TICK);
			filter.addAction(Intent.ACTION_TIME_CHANGED);
			filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);

			// 注册监听,监听时间变更事件
			getContext().registerReceiver(mIntentReceiver, filter, null,
					mHandler);
		}

		mCalendar = new Time();

		// 获取最新时间
		onTimeChanged();

		// 每隔一秒时钟变化1次
		post(mClockTick);

	}

	/*
	 * 组件从组件detach的回调
	 * @see android.view.View#onDetachedFromWindow()
	 */
	@Override
	protected void onDetachedFromWindow() {
		super.onDetachedFromWindow();
		
		// 注销监听和使循环停止
		if (mAttached) {
			getContext().unregisterReceiver(mIntentReceiver);
			removeCallbacks(mClockTick);
			mAttached = false;
		}
	}

	/* 
	 * 在绘制时钟组件之前 (onDraw)调用,用来决定时钟组件的大小
	 * @see android.view.View#onMeasure(int, int)
	 */
	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

		int widthMode = MeasureSpec.getMode(widthMeasureSpec);
		int widthSize = MeasureSpec.getSize(widthMeasureSpec);
		int heightMode = MeasureSpec.getMode(heightMeasureSpec);
		int heightSize = MeasureSpec.getSize(heightMeasureSpec);

		float hScale = 1.0f;
		float vScale = 1.0f;

		// 设置的组件宽度如果小于时钟表盘图片的宽度,则算出缩放比率
		if (widthMode != MeasureSpec.UNSPECIFIED && widthSize < mDialWidth) {
			hScale = (float) widthSize / (float) mDialWidth;
		}

		// 同上,此处为高度
		if (heightMode != MeasureSpec.UNSPECIFIED && heightSize < mDialHeight) {
			vScale = (float) heightSize / (float) mDialHeight;
		}

		// 保证组件的宽高一致
		float scale = Math.min(hScale, vScale);

		setMeasuredDimension(
				resolveSize((int) (mDialWidth * scale), widthMeasureSpec),
				resolveSize((int) (mDialHeight * scale), heightMeasureSpec));
	}

	/* 
	 * 组件大小发生变化时的回调,比如在外部设置组件的宽高
	 * 
	 * @see android.view.View#onSizeChanged(int, int, int, int)
	 */
	@Override
	protected void onSizeChanged(int w, int h, int oldw, int oldh) {
		super.onSizeChanged(w, h, oldw, oldh);
		mChanged = true;
	}

	/* 
	 * 组件绘制回调
	 * @see android.view.View#onDraw(android.graphics.Canvas)
	 */
	@Override
	protected void onDraw(Canvas canvas) {
		super.onDraw(canvas);

		canvas.setDrawFilter(new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG
				| Paint.FILTER_BITMAP_FLAG)); // 防锯齿

		boolean changed = mChanged;
		if (changed) {
			mChanged = false;
		}

		final Drawable dial = mDial;
		int w = dial.getIntrinsicWidth();
		int h = dial.getIntrinsicHeight();

		int availableWidth = getWidth();
		int availableHeight = h;

		int x = availableWidth / 2;
		int y = availableHeight / 2;

		boolean scaled = false;

		// 如有需要,等比例缩放,保证宽高一致
		if (availableWidth < w || availableHeight < h) {
			scaled = true;
			float scale = Math.min((float) availableWidth / (float) w,
					(float) availableHeight / (float) h);
			canvas.save();
			canvas.scale(scale, scale, x, y);
		}

		if (changed) {
			dial.setBounds(x - (w / 2), y - (h / 2), x + (w / 2), y + (h / 2));
		}
		dial.draw(canvas); // 绘制表盘

		if (!mNoSeconds) {
			drawHand(canvas, mSecondHand, x, y, mSeconds / 60.0f * 360.0f,
					changed); // 绘制秒针
		}
		drawHand(canvas, mMinuteHand, x, y, mMinutes / 60.0f * 360.0f, changed); // 绘制分针
		drawHand(canvas, mHourHand, x, y, mHour / 12.0f * 360.0f, changed); // 绘制时针

		if (scaled) {
			canvas.restore();
		}
	}

	/**
	 * 绘制时分秒针
	 * 
	 * @param canvas
	 * @param hand
	 * @param x
	 * @param y
	 * @param angle
	 * @param changed
	 */
	private void drawHand(Canvas canvas, Drawable hand, int x, int y,
			float angle, boolean changed) {
		canvas.save();
		canvas.rotate(angle, x, y);
		if (changed) {
			final int w = hand.getIntrinsicWidth();
			final int h = hand.getIntrinsicHeight();
			hand.setBounds(x - (w / 2), y - (h / 2), x + (w / 2), y + (h / 2));
		}
		hand.draw(canvas);
		canvas.restore();
	}

	/**
	 * 时间变更回调,计算出当前的时分秒
	 */
	private void onTimeChanged() {
		mCalendar.setToNow();
		
		if (mTimeZoneOffset != 0)
		{
			long offset = - mCalendar.gmtoff * 1000 + (mTimeZoneOffset * 3600000);
			if (offset != 0)
			{
				mCalendar.set(mCalendar.toMillis(false) + offset);
			}
		}

//		if (mTimeZoneId != null) {
//			mCalendar.switchTimezone(mTimeZoneId);
//		}

		int hour = mCalendar.hour;
		int minute = mCalendar.minute;
		int second = mCalendar.second;
		// long millis = System.currentTimeMillis() % 1000;

		mSeconds = second;// (float) ((second * 1000 + millis) / 166.666);
		mMinutes = minute + second / 60.0f;
		mHour = hour + mMinutes / 60.0f;
		mChanged = true;

		updateContentDescription(mCalendar);
	}

	/**
	 * 时间变化Receiver,回调后不做逻辑处理,因为使用了mClockTick循环
	 */
	private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
		@Override
		public void onReceive(Context context, Intent intent) {
			// if (intent.getAction().equals(Intent.ACTION_TIMEZONE_CHANGED)) {
			// String tz = intent.getStringExtra("time-zone");
			// mCalendar = new Time(TimeZone.getTimeZone(tz).getID());
			// }
			// onTimeChanged();
			// invalidate();
		}
	};

	/**
	 * 时间间隔1s循环1次
	 */
	private final Runnable mClockTick = new Runnable() {

		@Override
		public void run() {
			onTimeChanged();
			invalidate();
			ClockView.this.postDelayed(mClockTick, 1000);
		}
	};

	private void updateContentDescription(Time time) {
		final int flags = DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_24HOUR;
		String contentDescription = DateUtils.formatDateTime(mContext,
				time.toMillis(false), flags);
		setContentDescription(contentDescription);
	}

//	public void setTimeZone(String id) {
//		mTimeZoneId = id;
//		onTimeChanged();
//	}
	
	/**
	 * 时差,小时为单位
	 * 
	 * @param timezoneOffset
	 */
	public void setTimezoneOffset (int timezoneOffset) {
		this.mTimeZoneOffset = timezoneOffset;
	}

	/**
	 * 设置是否需要秒针
	 * 
	 * @param enable
	 */
	public void enableSeconds(boolean enable) {
		mNoSeconds = !enable;
	}
}


android自定义ClockView