读书人

Android之场景圆桌面(二)-模拟时钟实现

发布时间: 2013-10-08 16:46:23 作者: rapoo

Android之场景桌面(二)----模拟时钟实现

之前关于场景桌面Android之场景桌面(一)作了一个大概的描述,总体实现比较简单。今天跟大家分享一下一个自定义View ----模拟时钟的具体实现,先来看看效果图吧,单独提取出来的,相比场景桌面中的模拟时钟,多加了一个秒针、多显示了日期和星期。在场景桌面中,为了桌面的整体效率,就忍痛割爱,把秒针去掉了,因为一秒刷新一次界面实在是有点没必要,而且还比较影响桌面的流畅性。这里仅是一个简单的例子,加上亦无伤大雅。

Android之场景圆桌面(二)-模拟时钟实现


关于自定义View,不得不说说几个经常使用的函数了:

①.三个构造器:需要特别注意的一点是:这里在有三个参数的构造器里面做了所有的初始化工作,因此,另外两个构造器必须直接或间接的调用最后一个构造器,比如,在单个参数的构造器中调用this(context, null);即调用双参数的构造,再在双参数构造器中调用this(context, attrs, 0);最后实际上调用的第三个构造器。

另外,我们来详细分析一下第三个构造器,主要是自定义属性attrs。

/** *  * This widget display an analogic clock with three hands for hours minutes and * seconds. *  * @author way */@SuppressLint("NewApi")public class AnalogClock extends View {private Time mCalendar;private Drawable mHourHand;// 时针private Drawable mMinuteHand;// 分针private Drawable mSecondHand;// 秒针private Drawable mDial;// 表盘private String mDay;// 日期private String mWeek;// 星期private int mDialWidth;// 表盘宽度private int mDialHeight;// 表盘高度private final Handler mHandler = new Handler();private float mHour;// 时针值private float mMinutes;// 分针之private float mSecond;// 秒针值private boolean mChanged;// 是否需要更新界面private Paint mPaint;// 画笔private Runnable mTicker;// 由于秒针的存在,因此我们需要每秒钟都刷新一次界面,用的就是此任务private boolean mTickerStopped = false;// 是否停止更新时间,当View从窗口中分离时,不需要更新时间了public AnalogClock(Context context) {this(context, null);}public AnalogClock(Context context, AttributeSet attrs) {this(context, attrs, 0);}public AnalogClock(Context context, AttributeSet attrs, int defStyle) {super(context, attrs, defStyle);Resources r = getContext().getResources();// 下面是从layout文件中读取所使用的图片资源,如果没有则使用默认的TypedArray a = context.obtainStyledAttributes(attrs,R.styleable.AnalogClock, defStyle, 0);mDial = a.getDrawable(R.styleable.AnalogClock_dial);mHourHand = a.getDrawable(R.styleable.AnalogClock_hand_hour);mMinuteHand = a.getDrawable(R.styleable.AnalogClock_hand_minute);mSecondHand = a.getDrawable(R.styleable.AnalogClock_hand_second);// 为了整体美观性,只要缺少一张图片,我们就用默认的那套图片if (mDial == null || mHourHand == null || mMinuteHand == null|| mSecondHand == null) {mDial = r.getDrawable(R.drawable.appwidget_clock_dial);mHourHand = r.getDrawable(R.drawable.appwidget_clock_hour);mMinuteHand = r.getDrawable(R.drawable.appwidget_clock_minute);mSecondHand = r.getDrawable(R.drawable.appwidget_clock_second);}a.recycle();// 不调用这个函数,则上面的都是白费功夫// 获取表盘的宽度和高度mDialWidth = mDial.getIntrinsicWidth();mDialHeight = mDial.getIntrinsicHeight();// 初始化画笔mPaint = new Paint();mPaint.setColor(Color.parseColor("#3399ff"));mPaint.setTypeface(Typeface.DEFAULT_BOLD);mPaint.setFakeBoldText(true);mPaint.setAntiAlias(true);// 初始化Time对象if (mCalendar == null) {mCalendar = new Time();}}/** * 时间改变时调用此函数,来更新界面的绘制 */private void onTimeChanged() {mCalendar.setToNow();// 时间设置为当前时间// 下面是获取时、分、秒、日期和星期int hour = mCalendar.hour;int minute = mCalendar.minute;int second = mCalendar.second;mDay = String.valueOf(mCalendar.year) + "-"+ String.valueOf(mCalendar.month + 1) + "-"+ String.valueOf(mCalendar.monthDay);mWeek = this.getWeek(mCalendar.weekDay);mHour = hour + mMinutes / 60.0f + mSecond / 3600.0f;// 小时值,加上分和秒,效果会更加逼真mMinutes = minute + second / 60.0f;// 分钟值,加上秒,也是为了使效果逼真mSecond = second;mChanged = true;// 此时需要更新界面了updateContentDescription(mCalendar);// 作为一种辅助功能提供,为一些没有文字描述的View提供说明}@Overrideprotected void onAttachedToWindow() {mTickerStopped = false;// 添加到窗口中就要更新时间了super.onAttachedToWindow();/** * requests a tick on the next hard-second boundary */mTicker = new Runnable() {public void run() {if (mTickerStopped)return;onTimeChanged();invalidate();long now = SystemClock.uptimeMillis();long next = now + (1000 - now % 1000);// 计算下次需要更新的时间间隔mHandler.postAtTime(mTicker, next);// 递归执行,就达到秒针一直在动的效果}};mTicker.run();}@Overrideprotected void onDetachedFromWindow() {super.onDetachedFromWindow();mTickerStopped = true;// 当view从当前窗口中移除时,停止更新}@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {// 模式: UNSPECIFIED(未指定),父元素不对子元素施加任何束缚,子元素可以得到任意想要的大小;// EXACTLY(完全),父元素决定自元素的确切大小,子元素将被限定在给定的边界里而忽略它本身大小;// AT_MOST(至多),子元素至多达到指定大小的值。// 根据提供的测量值(格式)提取模式(上述三个模式之一)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(resolveSizeAndState((int) (mDialWidth * scale),widthMeasureSpec, 0),resolveSizeAndState((int) (mDialHeight * scale),heightMeasureSpec, 0));}@Overrideprotected void onSizeChanged(int w, int h, int oldw, int oldh) {super.onSizeChanged(w, h, oldw, oldh);mChanged = true;}@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);boolean changed = mChanged;if (changed) {mChanged = false;}int availableWidth = getRight() - getLeft();// view可用宽度,通过右坐标减去左坐标int availableHeight = getBottom() - getTop();// view可用高度,通过下坐标减去上坐标int x = availableWidth / 2;// view宽度中心点坐标int y = availableHeight / 2;// view高度中心点坐标final Drawable dial = mDial;// 表盘图片int w = dial.getIntrinsicWidth();// 表盘宽度int h = dial.getIntrinsicHeight();// int dialWidth = w;int dialHeight = h;boolean scaled = false;// 最先画表盘,最底层的要先画上画板if (availableWidth < w || availableHeight < h) {// 如果view的可用宽高小于表盘图片,就要缩小图片scaled = true;float scale = Math.min((float) availableWidth / (float) w,(float) availableHeight / (float) h);// 计算缩小值canvas.save();canvas.scale(scale, scale, x, y);// 实际上是缩小的画板}if (changed) {// 设置表盘图片位置。组件在容器X轴上的起点; 组件在容器Y轴上的起点; 组件的宽度;组件的高度dial.setBounds(x - (w / 2), y - (h / 2), x + (w / 2), y + (h / 2));}dial.draw(canvas);// 这里才是真正把表盘图片画在画板上canvas.save();// 一定要保存一下// 其次画日期if (changed) {w = (int) (mPaint.measureText(mWeek));// 计算文字的宽度canvas.drawText(mWeek, (x - w / 2), y - (dialHeight / 8), mPaint);// 画文字在画板上,位置为中间两个参数w = (int) (mPaint.measureText(mDay));canvas.drawText(mDay, (x - w / 2), y + (dialHeight / 8), mPaint);// 同上}// 再画时针canvas.rotate(mHour / 12.0f * 360.0f, x, y);// 旋转画板,第一个参数为旋转角度,第二、三个参数为旋转坐标点final Drawable hourHand = mHourHand;if (changed) {w = hourHand.getIntrinsicWidth();h = hourHand.getIntrinsicHeight();hourHand.setBounds(x - (w / 2), y - (h / 2), x + (w / 2), y+ (h / 2));}hourHand.draw(canvas);// 把时针画在画板上canvas.restore();// 恢复画板到最初状态canvas.save();// 然后画分针canvas.rotate(mMinutes / 60.0f * 360.0f, x, y);final Drawable minuteHand = mMinuteHand;if (changed) {w = minuteHand.getIntrinsicWidth();h = minuteHand.getIntrinsicHeight();minuteHand.setBounds(x - (w / 2), y - (h / 2), x + (w / 2), y+ (h / 2));}minuteHand.draw(canvas);canvas.restore();canvas.save();// 最后画秒针canvas.rotate(mSecond / 60.0f * 360.0f, x, y);final Drawable secondHand = mSecondHand;if (changed) {w = secondHand.getIntrinsicWidth();h = secondHand.getIntrinsicHeight();secondHand.setBounds(x - (w / 2), y - (h / 2), x + (w / 2), y+ (h / 2));}secondHand.draw(canvas);canvas.restore();if (scaled) {canvas.restore();}}/** * 对这个view描述一下, *  * @param time */private void updateContentDescription(Time time) {final int flags = DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_24HOUR;String contentDescription = DateUtils.formatDateTime(getContext(),time.toMillis(false), flags);setContentDescription(contentDescription);}/** * 获取当前星期 *  * @param week * @return */private String getWeek(int week) {switch (week) {case 1:return this.getContext().getString(R.string.monday);case 2:return this.getContext().getString(R.string.tuesday);case 3:return this.getContext().getString(R.string.wednesday);case 4:return this.getContext().getString(R.string.thursday);case 5:return this.getContext().getString(R.string.friday);case 6:return this.getContext().getString(R.string.saturday);case 0:return this.getContext().getString(R.string.sunday);default:return "";}}}


读书人网 >Android

热点推荐