最近由于项目需要,需要自制一个钟表视图,并加一些业务逻辑,所以根据自定义一个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