本问转自:http://blog.csdn.net/qq3162380/article/details/41850039

我在5.0源码下修改没生效,估计是哪里有问题,但原作写的思路还是很清晰的。

最近项目告一段落,开始review Android4.4中的Launcher2模块。

偶然间看到同事的iPhone6(高大上)上的图标能显示今天的日期与时间,于是就自己琢磨着怎么能在Android设备上也这么实现。

于是,就试着修改Launcher2的源码,将此功能实现了。

下面就共享出来我的修改,不保证无BUG,但是自己测试下来,还是比较稳定的。

首先先看一下修改前后的效果图,仿照iPhone6的图标进行的修改

修改前的效果

修改后的效果

此代码的逻辑是直接修改IconCache中的数据,然后在每次日期改变的时候都重新绘制Icon,这时往往会从IconCache中去获取缓存的图标,在获取之前修改icon并保存到iconCache中,从而保证了每个地方获取到的Icon都会改变。

下面就是需要修改的代码部分:

第一部分

第一、监听系统日期变化

1.在LauncherApplicaiton.java中注册监听

[java] view plain copy
  1. public static final String sApplicationIconChanged = "com.android.iconchanged"; // 自定义action用于接收Icon变化的广播(为了以后能实现刷新其他应用Icon)
  2. @Override
  3. public void onCreate() {
  4. super.onCreate(); // set sIsScreenXLarge and sScreenDensity *before* creating icon cache
  5. sIsScreenLarge = getResources().getBoolean(R.bool.is_large_screen);
  6. sScreenDensity = getResources().getDisplayMetrics().density;
  7. mWidgetPreviewCacheDb = new WidgetPreviewLoader.CacheDb(this);
  8. mIconCache = new IconCache(this); mModel = new LauncherModel(this, mIconCache);
  9. // ... ...
  10. filter = new IntentFilter();
  11. filter.addAction(SearchManager.INTENT_GLOBAL_SEARCH_ACTIVITY_CHANGED);
  12. registerReceiver(mModel, filter);
  13. filter = new IntentFilter();
  14. filter.addAction(SearchManager.INTENT_ACTION_SEARCHABLES_CHANGED);
  15. registerReceiver(mModel, filter);
  16. / Added Added by hao for refresh CalendarIcon start
  17. filter = new IntentFilter(); filter.addAction(Intent.ACTION_TIME_CHANGED);
  18. filter.addAction(Intent.ACTION_DATE_CHANGED);
  19. filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
  20. filter.addAction(sApplicationIconChanged);
  21. registerReceiver(mModel, filter);
  22. / Added end
  23. // Register for changes to the favorites
  24. ContentResolver resolver = getContentResolver();
  25. resolver.registerContentObserver(LauncherSettings.Favorites.CONTENT_URI, true, mFavoritesObserver);
  26. }

2、在LauncherModel.java中的Callbacks接口中添加回调方法,在onReceive中添加对日期变化的判断

[java] view plain copy
  1. public interface Callbacks {
  2. // ...
  3. public void bindSearchablesChanged();
  4. public void onPageBoundSynchronously(int page);
  5. public void updateApplicationsIcon(String pkgName); // Added by hao for refresh CalendarIcon
  6. }
  7. /**
  8. * Call from the handler for ACTION_PACKAGE_ADDED, ACTION_PACKAGE_REMOVED and
  9. * ACTION_PACKAGE_CHANGED.
  10. */
  11. @Override
  12. public void onReceive(Context context, Intent intent) {
  13. if (DEBUG_LOADERS) Log.d(TAG, "onReceive intent=" + intent);
  14. final String action = intent.getAction();
  15. if (Intent.ACTION_PACKAGE_CHANGED.equals(action)
  16. || Intent.ACTION_PACKAGE_REMOVED.equals(action)
  17. || Intent.ACTION_PACKAGE_ADDED.equals(action)) {
  18. // ... ...
  19. } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(action)) {
  20. // ... ...
  21. }
  22. // ... ...
  23. } else if (SearchManager.INTENT_GLOBAL_SEARCH_ACTIVITY_CHANGED.equals(action) ||
  24. SearchManager.INTENT_ACTION_SEARCHABLES_CHANGED.equals(action)) {
  25. if (mCallbacks != null) {
  26. Callbacks callbacks = mCallbacks.get();
  27. if (callbacks != null) {
  28. callbacks.bindSearchablesChanged();
  29. }
  30. }
  31. // Added by hao for refrash CalendarIcon start
  32. } else if (Intent.ACTION_TIME_CHANGED.equals(action) ||
  33. Intent.ACTION_DATE_CHANGED.equals(action) ||
  34. Intent.ACTION_TIMEZONE_CHANGED.equals(action) ||
  35. mApp.sApplicationIconChanged.equals(action)) {
  36. String pkgName = null;
  37. if(mApp.sApplicationIconChanged.equals(action)) {
  38. pkgName = intent.getStringExtra("packageName");
  39. } else {
  40. pkgName = "com.android.calendar";
  41. }
  42. final ArrayList<ApplicationInfo> list
  43. = (ArrayList<ApplicationInfo>) mBgAllAppsList.data.clone();
  44. ApplicationInfo info = null;
  45. if(null == list || list.isEmpty()) {
  46. return;
  47. }
  48. for(ApplicationInfo ai : list) {
  49. if(ai.componentName.getPackageName().equals(pkgName)) {
  50. info = ai;
  51. break;
  52. }
  53. }
  54. if(null != info) {
  55. if(mCallbacks != null) {
  56. Callbacks callbacks = mCallbacks.get();
  57. if (callbacks != null) {
  58. callbacks.updateApplicationsIcon(info.componentName.getPackageName());
  59. }
  60. }
  61. }
  62. // Added end
  63. }
  64. }

通过以上两步,当日期发生变化或者接收到自定义的广播"com.android.iconchanged"  的时候,能通过回调自定义的

public void updateApplicationsIcon(String pkgName); 接口来实现刷新操作

第二.在Launcher.java 派发刷新操作

我们知道,在源码中Launcher.java 是实现了 LauncherModel.Callbacks接口的,那么可以在Launcher中区Override updateApplicationsIcon方法。

[javascript] view plain copy
  1. @Override
  2. public void updateApplicationsIcon(String pkgName){
  3. Log.d(TAG, "----------------------pkgName :" + pkgName);
  4. if (mWorkspace != null) {
  5. mWorkspace.updateShortcut(pkgName);
  6. }
  7. if (mAppsCustomizeContent != null) {
  8. mAppsCustomizeContent.updateApp(pkgName);
  9. }
  10. }

在上面重写的方法中,分别调用了Workspace.java的updateShortcut(String pkgName)去更新桌面图标,同时调用AppsCustomizedView.java的updateApp(String pkgName) 方法去更新应用列表中的图标。

当然,源码中没有这两个方法,是自己添加的。

添加的代码如下:

Workspace.java

[javascript] view plain copy
  1. void updateShortcut(String pkgName) {
  2. ArrayList<ShortcutAndWidgetContainer> childrenLayouts = getAllShortcutAndWidgetContainers();
  3. for (ShortcutAndWidgetContainer layout: childrenLayouts) {
  4. int childCount = layout.getChildCount();
  5. for (int j = 0; j < childCount; j++) {
  6. final View view = layout.getChildAt(j);
  7. Object tag = view.getTag();
  8. if (tag instanceof ShortcutInfo) {
  9. ShortcutInfo info = (ShortcutInfo) tag;
  10. try {
  11. if (pkgName.equals(info.intent.getComponent().getPackageName())) {
  12. BubbleTextView bv = (BubbleTextView) view;
  13. //bv.setCompoundDrawablesWithIntrinsicBounds(null,
  14. //            new FastBitmapDrawable(info.getIcon(mIconCache)), null, null);
  15. <strong><span style="color:#cc0000;">bv.applyFromShortcutInfo(info, mIconCache);</span></strong>
  16. }
  17. } catch (Exception e) {
  18. Log.e(TAG, "" + e);
  19. }
  20. }
  21. }
  22. }
  23. }

AppsCustomizePagedView.java

[javascript] view plain copy
  1. public void updateApp(String pkgName) {
  2. for(int i = 0; i < mNumAppsPages; i++) {
  3. PagedViewCellLayout cl = (PagedViewCellLayout) getPageAt(i);
  4. if (cl == null) return;
  5. final int count = cl.getPageChildCount();
  6. View appIcon = null;
  7. ApplicationInfo appInfo = null;
  8. ShortcutInfo shortcut = null;
  9. for (int j = 0; j < count; j++) {
  10. appIcon = cl.getChildOnPageAt(j);
  11. appInfo = (ApplicationInfo) appIcon.getTag();
  12. if (appInfo != null && appInfo.componentName.getPackageName().equals(pkgName)) {
  13. PagedViewIcon pv = (PagedViewIcon) appIcon;
  14. <span style="color:#cc0000;"><strong>shortcut = appInfo.makeShortcut();
  15. pv.setCompoundDrawablesWithIntrinsicBounds(null,
  16. new FastBitmapDrawable(shortcut.getIcon(mIconCache)), null, null);</strong></span>
  17. }
  18. }

在这两个类中都是通过遍历自己的View然后去将新绘制的Drawable 对象显示到package对应的Icon上去。标红的代码就是重新添加Icon的位置。

我们来看看BubbleTextView::applyFromShortcutInfo(ShortcutInfo info, IconCache iconCache)方法:

[javascript] view plain copy
  1. public void applyFromShortcutInfo(ShortcutInfo info, IconCache iconCache) {
  2. Bitmap b = info.getIcon(iconCache);
  3. setCompoundDrawablesWithIntrinsicBounds(null,
  4. new FastBitmapDrawable(b),
  5. null, null);
  6. setText(info.title);
  7. setTag(info);
  8. }

其实applyFromShortcutInfo 也是通过View的setCompoundDrawablesWithIntrinsicBounds方法,为图标附上一个新的Drawable

到此,告一段落,我们来归纳一下。

在绘制Icon时的前期操作为:

1.监听Data Changed类的广播-->

2.接收到广播时,回调LauncherModel.lava中Callbacks中自定义的接口-->

3.Launcher.java 实现Callbacks接口,它会处理该回调事件-->

4.Launcher.java通知workspace和AppsCustomizeView分别去更新自己的View中pakcage对应的图标。

第二部分

接下来就是更新Icon的实现。

在第4步中仔细看代码,会发现,在创建FastBitmapDrawable对象所使用的Bitmap参数,都是使用ShortcutInfo::getIcon(IconCache)获取到的。

这是因为我再最基层的IconCache做了修改,让其每次getIcon时判断如果是该package对应的icon,就去更新IconCache,这样获取到的Icon就是更新好了的。

第三. 修改ShortcutInfo.java中的getIcon方法

[javascript] view plain copy
  1. public Bitmap getIcon(IconCache iconCache, boolean showUnread) {
  2. if (true || mIcon == null) { // 强制执行updateIcon方法,刷新IconCache中的图标
  3. updateIcon(iconCache);
  4. }
  5. if(showUnread) {
  6. // ... ...
  7. }
  8. return mIcon;
  9. }
  10. public void updateIcon(IconCache iconCache) {
  11. mIcon = iconCache.getIcon(intent);
  12. usingFallbackIcon = iconCache.isDefaultIcon(mIcon);
  13. }

在updateIcon时会调用IconCache::getIcon(Intent intent)方法,下面继续跟进代码,可以看到最终Icon的绘制是在IconCahce::cacheLocked()方法中进行的,(至少我从代码中看到是这样的)。

我觉得在这里修改最原始的Icon还是比较合适的,修改了最原始的IconCache中的资源后,在没有收到data changed 的广播时,也能直接显示修改后的Icon。

跟踪一下IconCache的代码

[javascript] view plain copy
  1. public Bitmap getIcon(Intent intent) {
  2. synchronized (mCache) {
  3. final ResolveInfo resolveInfo = mPackageManager.resolveActivity(intent, 0);
  4. ComponentName component = intent.getComponent();
  5. if (resolveInfo == null || component == null) {
  6. return mDefaultIcon;
  7. }
  8. CacheEntry entry = cacheLocked(component, resolveInfo, null);//封装ResolveInfo到CacheEntry中
  9. return entry.icon;
  10. }
  11. }

在cacheLocked方法的最后根据包名判断,如果是日历应用的话,就直接绘制日历的customized图标,标红的地方就是绘制图标的核心代码。

[javascript] view plain copy
  1. private CacheEntry cacheLocked(ComponentName componentName, ResolveInfo info,
  2. HashMap<Object, CharSequence> labelCache) {
  3. CacheEntry entry = mCache.get(componentName);
  4. if (entry == null) {
  5. entry = new CacheEntry();
  6. mCache.put(componentName, entry);
  7. ComponentName key = LauncherModel.getComponentNameFromResolveInfo(info);
  8. if (labelCache != null && labelCache.containsKey(key)) {
  9. entry.title = labelCache.get(key).toString();
  10. } else {
  11. entry.title = info.loadLabel(mPackageManager).toString();
  12. if (labelCache != null) {
  13. labelCache.put(key, entry.title);
  14. }
  15. }
  16. if (entry.title == null) {
  17. entry.title = info.activityInfo.name;
  18. }
  19. entry.icon = Utilities.createIconBitmap(
  20. getFullResIcon(info), mContext);
  21. }
  22. // Added for refresh CalendarIcon start
  23. if(null != entry && componentName.getPackageName().equals("com.android.calendar")) {
  24. entry.icon = Utilities.createCalendarIconBitmap(
  25. getFullResIcon(info), mContext);
  26. }
  27. // Added end
  28. return entry;
  29. }

第三部分

接下来就是绘制Icon的方法。

Utilities.java是Launcher2中专门绘制位图的类,所有与绘制图标相关的方法,都可以添加到这个油条包中。

自己在Utilities.java 中添加了专门绘制CalendarIcon的方法:

[javascript] view plain copy
  1. static Bitmap createCalendarIconBitmap(Drawable icon, Context context) {
  2. String[] dayOfWeek = context.getResources().getStringArray(<strong>R.array.week_days</strong>);
  3. Bitmap b = createIconBitmap(icon, context);
  4. String day = String.valueOf(Calendar.getInstance().get(Calendar.DAY_OF_MONTH));
  5. int weekIndex = Calendar.getInstance().get(Calendar.DAY_OF_WEEK);
  6. String week = dayOfWeek[weekIndex - 1];
  7. final float mDensity = context.getResources().getDisplayMetrics().density;
  8. final Canvas canvas = sCanvas;
  9. Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
  10. int width = b.getWidth();
  11. int heigth = b.getHeight();
  12. canvas.setBitmap(b);
  13. RectF rectF = new RectF(0, 0, width, heigth);
  14. paint.setColor(Color.WHITE);
  15. canvas.drawRoundRect(rectF, 8, 8, paint);
  16. paint.setColor(Color.RED);
  17. paint.setAlpha(180);
  18. paint.setTextSize(14f*mDensity);
  19. paint.setTypeface(Typeface.DEFAULT_BOLD);
  20. Rect rectWeek = new Rect();
  21. paint.getTextBounds(week, 0, week.length(), rectWeek);
[javascript] view plain copy
  1. int weekWidth = rectWeek.right - rectWeek.left;
  2. int weekHeigth = rectWeek.bottom - rectWeek.top;
  3. float weekX = Math.max(0, (width - weekWidth)/2 - rectWeek.left);
  4. float weekY = Math.max(0, weekHeigth - rectWeek.bottom) + 2f*mDensity;
  5. canvas.drawText(week, weekX, weekY, paint);
  6. paint.setColor(Color.DKGRAY);
  7. paint.setAlpha(220);
  8. paint.setTextSize(32f*mDensity);
  9. Rect rectDay = new Rect();
  10. paint.getTextBounds(day, 0, day.length(), rectDay);
  11. int dayWidth = rectDay.right - rectDay.left;
  12. int dayHeigth = rectDay.bottom - rectDay.top;
  13. float dayX = (width - dayWidth)/2 - rectDay.left;
  14. float dayY = (heigth + weekY + dayHeigth)/2- rectDay.bottom;
  15. canvas.drawText(day, dayX, dayY, paint);
  16. return b;
[javascript] view plain copy
  1. }

在Value中添加对应的星期资源

添加value/array.xml 和value-zh-CN/array.xml文件

[html] view plain copy
  1. <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
  2. <string-array name="week_days">
  3. <item>Sun</item>
  4. <item>Mon</item>
  5. <item>Tue</item>
  6. <item>Wed</item>
  7. <item>Thu</item>
  8. <item>Fri</item>
  9. <item>Sat</item>
  10. </string-array>
  11. </resources>
[html] view plain copy
  1. <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
  2. <string-array name="week_days">
  3. <item>星期日</item>
  4. <item>星期一</item>
  5. <item>星期二</item>
  6. <item>星期三</item>
  7. <item>星期四</item>
  8. <item>星期五</item>
  9. <item>星期六</item>
  10. </string-array>
  11. </resources>

通过这三个部分,就能完成Calendar Icon 的动态刷新了。

下面再绘制一张时序图来辅助一下我的实现方法:

以上就是全部内容,只是个人的一个思路,如果有意见或者建议,欢迎指正。

Android 原生Launcher2中动态刷新日历图标 显示日期与星期相关推荐

  1. android launcher 日历图标显示日期

    看到iphone上的日历图标上的数字会随着日期的变化而变化,最近在android平台上也研究了 一下,实现方法如下: 直接上源码 在launcher里改的 首先,在IconCache.java文件中, ...

  2. 服务器声卡图标显示x,win10电脑中桌面扬声器图标显示X标记怎么修复

    近日有用户安装完win10系统之后,要点击桌面扬声器图标对声音进行设置的时候,却发现扬声器图标显示X标记,移动鼠标悬停在上面的时候显示音频服务没有在Windows10 PC上运行,导致无法调整声音,要 ...

  3. (转)解决android开发人员,手机app图标显示不正确问题

    (转)解决android开发人员,手机app图标显示不正确问题 参考文章: (1)(转)解决android开发人员,手机app图标显示不正确问题 (2)https://www.cnblogs.com/ ...

  4. Android 类似未读短信图标显示数字效果的分析

     之前一直以为是应用本身在对图标进行修改,看了源码之后发现其实主要的工作并不是应用自己完成的,主要的工作在是launcher里面完成的. 关于系统里面类似未读短信的具体处理流程如下,   原理 一个应 ...

  5. android开发rn插件,在Android原生应用中嵌入React Native

    开发工具:Android Studio , WebStorm 参考链接:React Native官方中文文档 一.创建React Native项目 1.在本地React Native项目目录下,创建一 ...

  6. Android 7.1.1 插入耳机图标显示

    在Android手机上插入耳机后,状态栏会显示耳机图标,之前手机都是在手机最上面状态栏右边的区域显示,在Android 7.1.1上,插入耳机后不会显示这个图标,而是在最左边有个耳机的通知,这个通知是 ...

  7. Android 8.0 SystemUI消息列表图标显示问题

    Google为了统一风格,对消息列表的图标做了统一处理,设置消息icon的时候不能随便用一张带有色彩的图片,只能使用白色和透明两个颜色,具体设置代码位置在frameworks/base/core/ja ...

  8. deepin系统中.txt文件图标显示内容问题_深度系统Deepin 20最新正式版发布:从DDE到应用全面升级-Deepin 20,深度系统 ——快科技(驱动之家旗下媒体)-...

    距离Deepin 20正式版发布一个月后,社区版本迎来了第一次更新(1003). 本次从DDE到应用全面进行了升级,对桌面环境.应用进行功能优化和问题修复,同时本次也推送磁盘管理器.相机应用,邮件和手 ...

  9. Mac android原生工程中潜入react-native混合开发项目搭建

    最近公司有意向要用react-native做开发,自己在去年的时候也接触过,不过没有深入探讨,这两天,抽出时间看了看,顺便看了一下文档,做了一个简易的dome,由于我本人用的mac os 系统,Mac ...

最新文章

  1. spark 资源参数调优
  2. MySQL Server 5.0安装教程
  3. 软件外包项目中的进度管理
  4. 笔记36 Spring Web Flow——配置
  5. [Algorithm] 字符串匹配算法——KMP算法
  6. java将图片转byte存入数据库_Java将byte[]转图片存储到本地的案例
  7. sis最新ip地址2020_2020年12月版 最新IP数据库 号段归属地 省市区镇村行政区划
  8. [转]HashMap,LinkedHashMap,TreeMap的区别
  9. 捷联惯导基础知识解析之六(捷联惯导与组合导航仿真)
  10. 《初级会计电算化应用教程(金蝶KIS专业版)》——导读
  11. 树莓派系统迁移到移动硬盘
  12. proxyconnect tcp: dial tcp: lookup proxy.example.com on 8.8.8.8:53: no such host
  13. oracle自动清理归档,Oracle rman 自动清理归档日志
  14. 关于高德地图的分析报告
  15. 秦小明 第七讲 资产定价模型,股票定价
  16. 86.3 安全性问题 xss、DDOS、CC、sql注入 攻击等
  17. logo在线生成怎么操作?手机也能轻松生成
  18. 微信小程序 java多商家多用户网上商城购物系统#计算机毕业设计
  19. 利用Flash获取摄像头视频进行动态捕捉
  20. 小菜鸟的python学习之路(2)

热门文章

  1. 交换机路由器作用以及工作原理详解
  2. 基于JAVA漫画网站系统计算机毕业设计源码+数据库+lw文档+系统+部署
  3. [转] 如何在VirtualBox中启用3D加速和使用Windows Aero
  4. SQL语句 查看Oracle版本
  5. Android ActionBar 兼容2.x注意细节
  6. python循环画简单烟花_如何用python画烟花
  7. Ubuntu的复制粘贴操作及常用快捷键
  8. pytorch_gru理解
  9. [机缘参悟-78]:深度思考-职场中注意事项与大忌-员工版
  10. 一个网恋致毁者的独白