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;
	}
}
时间: 2024-10-18 04:27:43

android自定义ClockView的相关文章

Android自定义View绘制闹钟

Android自定义View绘制闹钟 本文简单实现了一个闹钟,扩展View,Canvas绘制 效果如下: 代码如下: package com.gaofeng.mobile.clock_demo; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.gr

Android 自定义Gallery浏览图片

之前写的<Android ImageSwitcher和Gallery的使用>一文中提到我在教室一下午为实现那个效果找各种资料.期间在网上找了一个个人觉得比较不错的效果,现在贴图上来: 其实这个效果使用的知识点就是图像的获取.创建.缩放.旋转.Matrix类.Canvas类等,另外就是自定义的Gallery控件. 相信大家都期待马上上代码了吧,嘻嘻.(注释比较多,相信大家都能看懂.) main.xml: <?xml version="1.0" encoding=&quo

Android自定义View探索(一)—生命周期

Activity代码: public class FiveActivity extends AppCompatActivity { private MyView myView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Log.e("log", "Activity生命周期:onCreate"); setConte

android自定义UI模板图文详解

不知道大家在实际开发中有没有自定义过UI模板?今天花时间研究了一下android中自定义UI模板,与大家分享一下. 每个设计良好的App都是自定义标题栏,在自定义标题栏的过程中大部分人可能都是自定义一个标题的xml文件,然后在需要的地方直接通过include来引用,这比起在每个布局文件中写标题栏已经进化很多了,但仍然不是最简单有效的方法,我们为什么不能自定义一个标题控件呢?今天就带大家自己做一个标题栏控件.效果图如下: 开始啦: 第一步:自定义xml属性 新建一个android项目,在value

Android自定义进度条样式

最近在做一个widget,上面需要一个progressbar,产品经理和设计师给出来的东西是要实现一个圆角的progress和自定义的颜色,研究一小下,分享出来给大家哦. 测试于:Android4.0+ 操作步骤: 1.创建你的layout文件引用progressbar如下,标红处引用你自定的样式: <ProgressBar android:id="@+id/progressDownload" style="?android:attr/progressBarStyleH

Android 自定义RecyclerView 实现真正的Gallery效果

转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/38173061 ,本文出自:[张鸿洋的博客] 上一篇博客我使用自定义HorizontalScrollView写了一个具有HorizontalScrollView效果和ViewPager特性的横向图片轮播,详见:Android 自定义 HorizontalScrollView 打造再多图片(控件)也不怕 OOM 的横向滑动效果.其实制作横向滚动的不得不说另一个控件,就是Google

Android 自定义 HorizontalScrollView 打造再多图片(控件)也不怕 OOM 的横向滑动效果

转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/38140505 自从Gallery被谷歌废弃以后,Google推荐使用ViewPager和HorizontalScrollView来实现Gallery的效果.的确HorizontalScrollView可以实现Gallery的效果,但是HorizontalScrollView存在一个很大的问题,如果你仅是用来展示少量的图片,应该是没问题的,但是如果我希望HorizontalScr

Android 自定义UI圆角按钮

Android实际开发中我们一般需要圆角的按钮,一般情况下我们可以让美工做出来相应的按钮图片,然后放上去即可,另外我们可以在布局文件中直接设置,也可以达到一样的效果.下面讲解在布局文件中自定义圆角按钮的小Demo. 代码很简单,实现效果图: 源代码: 源代码: 这里主要是xml布局文件实现: MainActivity: package com.android_drawableresource; import android.app.Activity; import android.os.Bund

Android 自定义View视图

创建全新的视图将满足我们独特的UI需求. 本文介绍在指南针开发中会用到的罗盘的界面UI,通过继承View类实现的自定义视图,以此来深刻了解自定义视图. 实现效果图: 源代码: 布局文件activity_main(其中CompassView继承View类): <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.