时钟窗口小部件实现
2018-02-26 19:55
260 查看
前言
Android自带的时钟AppWidget感觉特别的简单,只有一个圆盘加上时针和分针,看着也没有什么变化,这里就自己来实现一下简单的时钟小部件。实现效果
时钟控件实现
时钟控件最外层的大圆只可以调用Canvas.drawCircle实现,这个很简单,事实上canvas对象除了那些直接画线、画圆等画图操作外,它还能够像真正的画布那样做各种移位、旋转等动作,为了保证这些操作不会影响其他元素的绘制,通常会先保存画布内容在做移位旋转等操作,最后在还原画布内容。了解了这些之后就可以轻松的绘制内部的刻度线了,圆形最上方的12点钟刻度线位置很容易确定,如果直接绘制12点前后的刻度需要用三角函数计算它们的位置,这样太麻烦了,可以旋转画布360 / (12 * 60)也就是每个最小刻度之间的角度值,然后再绘制,不停的旋转直到所有的刻度都画完,时钟的时间文案也是用同样的方式绘制。private void drawClock(Canvas canvas) { paint.setStrokeWidth(CommonUtils.dp2px(1)); paint.setStyle(Paint.Style.STROKE); paint.setTextSize(CommonUtils.dp2px(13)); int centerX = width / 2; int centerY = height / 2; canvas.save(); canvas.drawCircle(centerX, centerY, centerX - CommonUtils.dp2px(5), paint); for (int i = 0; i < MINUTES_COUNT; i++) { if (i % 5 == 0) { // 画时 canvas.drawLine(centerX, CommonUtils.dp2px(5), centerX, LONG_TICK, paint); } else { // 画分钟 canvas.drawLine(centerX, CommonUtils.dp2px(5), centerX, SHORT_TICK, paint); } // 旋转画布,避免用三角函数计算位置 canvas.rotate(360 / MINUTES_COUNT, centerX, centerY); } canvas.restore(); // 画时刻 canvas.save(); paint.setStyle(Paint.Style.FILL); for (int i = 0; i < HOURS_COUNT; i++) { int textWidth = (int) paint.measureText(HOURS[i]); canvas.drawText(HOURS[i], centerX - textWidth / 2, CommonUtils.dp2px(5 + 13) + LONG_TICK, paint); canvas.rotate(360 / HOURS_COUNT, centerX, centerY); } canvas.restore(); ... }
画完刻度和文字之后就需要画时针,分针和秒针,实现方式类似前面的刻度画法,需要确定当前针与12点钟方向的角度值,旋转画布,画一条直线,然后还原转向的画布,继续下一只针的绘制。
// 拿到当前时分秒 int hour = calendar.get(Calendar.HOUR); int minute = calendar.get(Calendar.MINUTE); int second = calendar.get(Calendar.SECOND); // 绘制时针 canvas.save(); float degree = 360 / HOURS_COUNT * (hour + (float) minute / 60); canvas.rotate(degree, centerX, centerY); paint.setStrokeWidth(CommonUtils.dp2px(5)); canvas.drawLine(centerX, centerY, centerX, centerY - CommonUtils.dp2px(30), paint); // 绘制分针 canvas.rotate(-degree, centerX, centerY); canvas.rotate(360 / MINUTES_COUNT * minute, centerX, centerY); paint.setStrokeWidth(CommonUtils.dp2px(3)); canvas.drawLine(centerX, centerY, centerX, centerY - CommonUtils.dp2px(40), paint); // 绘制秒针 canvas.rotate(-360 / MINUTES_COUNT * minute, centerX, centerY); canvas.rotate(360 / MINUTES_COUNT * second, centerX, centerY); paint.setStrokeWidth(CommonUtils.dp2px(1)); canvas.drawLine(centerX, centerY, centerX, centerY - CommonUtils.dp2px(50), paint); canvas.restore();
这样的时钟布局只是当前时间秒针不会运动,为了让秒针每秒移动一小格,需要不停的发送移动要求。
private Runnable runnable = new Runnable() { @Override public void run() { if (autoUpdate) { calendar.setTimeInMillis(System.currentTimeMillis()); invalidate(); postDelayed(runnable, 1000); } } }; if (autoUpdate) { postDelayed(runnable, 1000); }
由于时分秒针都是根据当前的时间获取的角度值绘制,随着时间的流逝每根针的角度都会有所变化。
窗口小部件实现
窗口小部件AppWidget对象继承自BraodcaseReceiver,从本质上来说它们都是广播接受者对象。AppWidget中一个重要的概念就是RemoteViews,表明这些View实在其他的进程中展示的,要想操作它们必须通过AppWidgetManager来作用,而且系统实际支持的布局都是系统提供的布局,并不支持用户自定义生成的控件。这只能通过将用户布局做成Bitmap对象然后设置到ImageView对象上去,不停的更新Bitmap对象到ImageView实现指针的动态效果。Androi Studio本身集成了添加AppWidget的功能,这里主要看配置文件和AppWidget实现源码,配置文件如下:
<?xml version="1.0" encoding="utf-8"?> <appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android" android:initialKeyguardLayout="@layout/clock_widget" android:initialLayout="@layout/clock_widget" android:minHeight="110dp" android:minWidth="110dp" android:previewImage="@drawable/tower" android:resizeMode="horizontal|vertical" android:updatePeriodMillis="1000" android:widgetCategory="home_screen"> </appwidget-provider> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@color/transparent" android:padding="@dimen/widget_margin"> <ImageView android:id="@+id/clock_view" android:scaleType="centerInside" android:layout_centerInParent="true" android:layout_width="300dp" android:layout_height="300dp" /> </RelativeLayout>
上面的android:minHeight=”110dp”和android:minWidth=”110dp”就是用来配置窗口小部件在屏幕上的展示大小,需要注意如果之前添加过这个小部件之后又更改了这个大小,之前添加的那个小部件不会受影响,只有修改之后添加的小部件才会受到影响。android:updatePeriodMillis这个配置只要配置小于30分钟都会被重置为30分钟,所有窗口小部件的按时更新只能由用户内部实现,不要依靠这个属性的更新广播。
在窗口小部件布局里只需要定义一个ClockView和自定义的Canvas对象,通过ClockView把当前时间的时钟图片画到Canvas上,也就是Canvas加载的Bitmap上,调用RemoteViews的设置远程ImageView的setImageBitmap将最新的时钟图片展示到远程进程中。定时更新则是通过ScheduledExecutorService的定时器功能实现,每隔1秒重新画一幅最新的时钟图片并且更新到远程IamgeView上。
public class ClockWidget extends AppWidgetProvider { private static final String TAG = "ClockWidget"; // 更新广播动作 private static final String ACTION_UPDATE_TIME = "action_update_time"; // 时钟控件 private static ClockView clockView; // 绘制的Bitmap private static Bitmap bitmap; private static Canvas canvas; // 定时线程池 private static ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1); private static Set<Integer> widgetIds = new HashSet<>(); private static Paint paint; private static PorterDuffXfermode clear; private static PorterDuffXfermode src; static void updateAppWidget(final Context context, AppWidgetManager appWidgetManager, int appWidgetId) { Log.d(TAG, "updateAppWidget"); if (clockView == null) { // 初始化窗口小部件的内部数据 clockView = new ClockView(context); clockView.setAutoUpdate(false); bitmap = Bitmap.createBitmap(clockView.getRealWidth(), clockView.getRealHeight(), Bitmap.Config.ARGB_4444); canvas = new Canvas(bitmap); paint = new Paint(); paint.setColor(Color.TRANSPARENT); clear = new PorterDuffXfermode(PorterDuff.Mode.CLEAR); src = new PorterDuffXfermode(PorterDuff.Mode.SRC); // 开始定时发送更新广播 scheduledExecutorService.scheduleAtFixedRate(new Runnable() { @Override public void run() { Intent intent =new Intent(ACTION_UPDATE_TIME); context.sendBroadcast(intent); } }, 0,1000L, TimeUnit.MILLISECONDS); } // 绘制最新的时钟图片到Bitmap paint.setXfermode(clear); canvas.drawPaint(paint); paint.setXfermode(src); clockView.draw(canvas); RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.clock_widget); // 更新远程ImageView的时钟图片 views.setBitmap(R.id.clock_view, "setImageBitmap", bitmap); appWidgetManager.updateAppWidget(appWidgetId, views); } @Override public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { // There may be multiple widgets active, so update all of them for (int appWidgetId : appWidgetIds) { updateAppWidget(context, appWidgetManager, appWidgetId); widgetIds.add(appWidgetId); } } @Override public void onReceive(Context context, Intent intent) { super.onReceive(context, intent); // 接收到更新广播,开始更新操作 if (intent.getAction().equalsIgnoreCase(ACTION_UPDATE_TIME)) { int[] widgets = new int[widgetIds.size()]; int index = 0; for (Integer widgetId : widgetIds) { widgets[index++] = widgetId; } onUpdate(context, AppWidgetManager.getInstance(context), widgets); } } @Override public void onEnabled(Context context) { // Enter relevant functionality for when the first widget is created } @Override public void onDisabled(Context context) { // Enter relevant functionality for when the last widget is disabled } }
相关文章推荐
- 用PyQt实现透明桌面时钟小部件
- python3+PyQt5实现自定义窗口部件Counters
- python3+PyQt5+Qt Designer实现堆叠窗口部件
- Android app 实现AppWidget 窗口部件开发
- android 窗口小部件 的实现
- python3+PyQt5 实现自定义窗口部件--Counters自定窗口部件
- 桌面小部件----LED电子时钟实现
- Android开发5:应用程序窗口小部件App Widgets的实现
- python3+PyQt5实现自定义流体混合窗口部件
- python3+PyQt5 实现自定义窗口部件--分数滑块
- 实现一个用于显示当前时间的Google Android 窗口小部件(AppWidget) 推荐
- 实现一个用于显示当前时间的Google Android 窗口小部件
- 实现一个用于显示当前时间的Google Android 窗口小部件(AppWidge
- Android中实现Launcher功能之二 ----- 添加窗口小部件以及AppWidget的创建详解
- 实现一个用于显示当前时间的Google Android 窗口小部件(AppWidget)
- 137.safe窗口小部件功能的实现
- Android中实现Launcher功能之二 ----- 添加窗口小部件以及AppWidget的创建详解
- Android中实现Launcher功能之二 ----- 添加窗口小部件以及AppWidget的创建详解
- python3+PyQt5+Qt Designer实现堆叠窗口部件
- Android中实现Launcher功能之二 ----- 添加窗口小部件以及AppWidget的创建详解