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

来源:互联网 发布:酥酥淘宝同款摄像头 编辑:程序博客网 时间:2024/05/18 03:43

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



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


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



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

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

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

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

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


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

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


添加的代码如下:

Workspace.java

[javascript] view plaincopy
  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.     }  
  24.   
  25. }  

AppsCustomizePagedView.java

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


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


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

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


其实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 plaincopy
  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.   
  11. public void updateIcon(IconCache iconCache) {  
  12.     mIcon = iconCache.getIcon(intent);  
  13.     usingFallbackIcon = iconCache.isDefaultIcon(mIcon);  
  14. }  


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

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


跟踪一下IconCache的代码

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


在cacheLocked方法的最后根据包名判断,如果是日历应用的话,就直接绘制日历的customized图标,标红的地方就是绘制图标的核心代码。
[javascript] view plaincopy
  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.   
  7.             mCache.put(componentName, entry);  
  8.   
  9.             ComponentName key = LauncherModel.getComponentNameFromResolveInfo(info);  
  10.             if (labelCache != null && labelCache.containsKey(key)) {  
  11.                 entry.title = labelCache.get(key).toString();  
  12.             } else {  
  13.                 entry.title = info.loadLabel(mPackageManager).toString();  
  14.                 if (labelCache != null) {  
  15.                     labelCache.put(key, entry.title);  
  16.                 }  
  17.             }  
  18.             if (entry.title == null) {  
  19.                 entry.title = info.activityInfo.name;  
  20.             }  
  21.   
  22.             entry.icon = Utilities.createIconBitmap(  
  23.                     getFullResIcon(info), mContext);  
  24.         }  
  25.           
  26. // Added for refresh CalendarIcon start  
  27.         if(null != entry && componentName.getPackageName().equals("com.android.calendar")) {  
  28.             entry.icon = Utilities.createCalendarIconBitmap(  
  29.                                 getFullResIcon(info), mContext);  
  30.   
  31.         }  
  32. // Added end  
  33.           
  34.           
  35.         return entry;  
  36.     }  


第三部分

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

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


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


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

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

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

[html] view plaincopy
  1. <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">  
  2.   
  3.     <string-array name="week_days">  
  4.         <item>Sun</item>  
  5.         <item>Mon</item>  
  6.         <item>Tue</item>  
  7.         <item>Wed</item>  
  8.         <item>Thu</item>  
  9.         <item>Fri</item>  
  10.         <item>Sat</item>  
  11.     </string-array>  
  12. </resources>  

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

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


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


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