本问转自:/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 publicstaticfinalStringsApplicationIconChanged="com.android.iconchanged";//自定义action用于接收Icon变化的广播(为了以后能实现刷新其他应用Icon)@OverridepublicvoidonCreate(){super.onCreate();//setsIsScreenXLargeandsScreenDensity*before*creatingiconcachesIsScreenLarge=getResources().getBoolean(R.bool.is_large_screen);sScreenDensity=getResources().getDisplayMetrics().density;mWidgetPreviewCacheDb=newWidgetPreviewLoader.CacheDb(this);mIconCache=newIconCache(this);mModel=newLauncherModel(this,mIconCache);//......filter=newIntentFilter();filter.addAction(SearchManager.INTENT_GLOBAL_SEARCH_ACTIVITY_CHANGED);registerReceiver(mModel,filter);filter=newIntentFilter();filter.addAction(SearchManager.INTENT_ACTION_SEARCHABLES_CHANGED);registerReceiver(mModel,filter);/AddedAddedbyhaoforrefreshCalendarIconstartfilter=newIntentFilter();filter.addAction(Intent.ACTION_TIME_CHANGED);filter.addAction(Intent.ACTION_DATE_CHANGED);filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);filter.addAction(sApplicationIconChanged);registerReceiver(mModel,filter);/Addedend//RegisterforchangestothefavoritesContentResolverresolver=getContentResolver();resolver.registerContentObserver(LauncherSettings.Favorites.CONTENT_URI,true,mFavoritesObserver);}
2、在LauncherModel.java中的Callbacks接口中添加回调方法,在onReceive中添加对日期变化的判断
[java]view plain copy publicinterfaceCallbacks{//...publicvoidbindSearchablesChanged();publicvoidonPageBoundSynchronously(intpage);publicvoidupdateApplicationsIcon(StringpkgName);//AddedbyhaoforrefreshCalendarIcon}/***CallfromthehandlerforACTION_PACKAGE_ADDED,ACTION_PACKAGE_REMOVEDand*ACTION_PACKAGE_CHANGED.*/@OverridepublicvoidonReceive(Contextcontext,Intentintent){if(DEBUG_LOADERS)Log.d(TAG,"onReceiveintent="+intent);finalStringaction=intent.getAction();if(Intent.ACTION_PACKAGE_CHANGED.equals(action)||Intent.ACTION_PACKAGE_REMOVED.equals(action)||Intent.ACTION_PACKAGE_ADDED.equals(action)){//......}elseif(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(action)){//......}//......}elseif(SearchManager.INTENT_GLOBAL_SEARCH_ACTIVITY_CHANGED.equals(action)||SearchManager.INTENT_ACTION_SEARCHABLES_CHANGED.equals(action)){if(mCallbacks!=null){Callbackscallbacks=mCallbacks.get();if(callbacks!=null){callbacks.bindSearchablesChanged();}}//AddedbyhaoforrefrashCalendarIconstart}elseif(Intent.ACTION_TIME_CHANGED.equals(action)||Intent.ACTION_DATE_CHANGED.equals(action)||Intent.ACTION_TIMEZONE_CHANGED.equals(action)||mApp.sApplicationIconChanged.equals(action)){StringpkgName=null;if(mApp.sApplicationIconChanged.equals(action)){pkgName=intent.getStringExtra("packageName");}else{pkgName="com.android.calendar";}finalArrayList<ApplicationInfo>list=(ArrayList<ApplicationInfo>)mBgAllAppsList.data.clone();ApplicationInfoinfo=null;if(null==list||list.isEmpty()){return;}for(ApplicationInfoai:list){if(ponentName.getPackageName().equals(pkgName)){info=ai;break;}}if(null!=info){if(mCallbacks!=null){Callbackscallbacks=mCallbacks.get();if(callbacks!=null){callbacks.updateApplicationsIcon(ponentName.getPackageName());}}}//Addedend}}
通过以上两步,当日期发生变化或者接收到自定义的广播"com.android.iconchanged" 的时候,能通过回调自定义的
public void updateApplicationsIcon(String pkgName); 接口来实现刷新操作
第二.在Launcher.java 派发刷新操作
我们知道,在源码中Launcher.java 是实现了LauncherModel.Callbacks接口的,那么可以在Launcher中区OverrideupdateApplicationsIcon方法。
[javascript]view plain copy @OverridepublicvoidupdateApplicationsIcon(StringpkgName){Log.d(TAG,"----------------------pkgName:"+pkgName);if(mWorkspace!=null){mWorkspace.updateShortcut(pkgName);}if(mAppsCustomizeContent!=null){mAppsCustomizeContent.updateApp(pkgName);}}
在上面重写的方法中,分别调用了Workspace.java的updateShortcut(String pkgName)去更新桌面图标,同时调用AppsCustomizedView.java的updateApp(String pkgName) 方法去更新应用列表中的图标。
当然,源码中没有这两个方法,是自己添加的。
添加的代码如下:
Workspace.java
[javascript]view plain copy voidupdateShortcut(StringpkgName){ArrayList<ShortcutAndWidgetContainer>childrenLayouts=getAllShortcutAndWidgetContainers();for(ShortcutAndWidgetContainerlayout:childrenLayouts){intchildCount=layout.getChildCount();for(intj=0;j<childCount;j++){finalViewview=layout.getChildAt(j);Objecttag=view.getTag();if(taginstanceofShortcutInfo){ShortcutInfoinfo=(ShortcutInfo)tag;try{if(pkgName.equals(info.intent.getComponent().getPackageName())){BubbleTextViewbv=(BubbleTextView)view;//bv.setCompoundDrawablesWithIntrinsicBounds(null,//newFastBitmapDrawable(info.getIcon(mIconCache)),null,null);<strong><spanstyle="color:#cc0000;">bv.applyFromShortcutInfo(info,mIconCache);</span></strong>}}catch(Exceptione){Log.e(TAG,""+e);}}}}}
AppsCustomizePagedView.java[javascript]view plain copy publicvoidupdateApp(StringpkgName){for(inti=0;i<mNumAppsPages;i++){PagedViewCellLayoutcl=(PagedViewCellLayout)getPageAt(i);if(cl==null)return;finalintcount=cl.getPageChildCount();ViewappIcon=null;ApplicationInfoappInfo=null;ShortcutInfoshortcut=null;for(intj=0;j<count;j++){appIcon=cl.getChildOnPageAt(j);appInfo=(ApplicationInfo)appIcon.getTag();if(appInfo!=null&&ponentName.getPackageName().equals(pkgName)){PagedViewIconpv=(PagedViewIcon)appIcon;<spanstyle="color:#cc0000;"><strong>shortcut=appInfo.makeShortcut();pv.setCompoundDrawablesWithIntrinsicBounds(null,newFastBitmapDrawable(shortcut.getIcon(mIconCache)),null,null);</strong></span>}}
在这两个类中都是通过遍历自己的View然后去将新绘制的Drawable 对象显示到package对应的Icon上去。标红的代码就是重新添加Icon的位置。
我们来看看BubbleTextView::applyFromShortcutInfo(ShortcutInfo info, IconCache iconCache)方法:
[javascript]view plain copy publicvoidapplyFromShortcutInfo(ShortcutInfoinfo,IconCacheiconCache){Bitmapb=info.getIcon(iconCache);setCompoundDrawablesWithIntrinsicBounds(null,newFastBitmapDrawable(b),null,null);setText(info.title);setTag(info);}
其实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 publicBitmapgetIcon(IconCacheiconCache,booleanshowUnread){if(true||mIcon==null){//强制执行updateIcon方法,刷新IconCache中的图标updateIcon(iconCache);}if(showUnread){//......}returnmIcon;}publicvoidupdateIcon(IconCacheiconCache){mIcon=iconCache.getIcon(intent);usingFallbackIcon=iconCache.isDefaultIcon(mIcon);}
在updateIcon时会调用IconCache::getIcon(Intent intent)方法,下面继续跟进代码,可以看到最终Icon的绘制是在IconCahce::cacheLocked()方法中进行的,(至少我从代码中看到是这样的)。
我觉得在这里修改最原始的Icon还是比较合适的,修改了最原始的IconCache中的资源后,在没有收到data changed 的广播时,也能直接显示修改后的Icon。
跟踪一下IconCache的代码
[javascript]view plain copy publicBitmapgetIcon(Intentintent){synchronized(mCache){finalResolveInforesolveInfo=mPackageManager.resolveActivity(intent,0);ComponentNamecomponent=intent.getComponent();if(resolveInfo==null||component==null){returnmDefaultIcon;}CacheEntryentry=cacheLocked(component,resolveInfo,null);//封装ResolveInfo到CacheEntry中returnentry.icon;}}
在cacheLocked方法的最后根据包名判断,如果是日历应用的话,就直接绘制日历的customized图标,标红的地方就是绘制图标的核心代码。
[javascript]view plain copy privateCacheEntrycacheLocked(ComponentNamecomponentName,ResolveInfoinfo,HashMap<Object,CharSequence>labelCache){CacheEntryentry=mCache.get(componentName);if(entry==null){entry=newCacheEntry();mCache.put(componentName,entry);ComponentNamekey=LauncherModel.getComponentNameFromResolveInfo(info);if(labelCache!=null&&labelCache.containsKey(key)){entry.title=labelCache.get(key).toString();}else{entry.title=info.loadLabel(mPackageManager).toString();if(labelCache!=null){labelCache.put(key,entry.title);}}if(entry.title==null){entry.title=info.activityInfo.name;}entry.icon=Utilities.createIconBitmap(getFullResIcon(info),mContext);}//AddedforrefreshCalendarIconstartif(null!=entry&&componentName.getPackageName().equals("com.android.calendar")){entry.icon=Utilities.createCalendarIconBitmap(getFullResIcon(info),mContext);}//Addedendreturnentry;}
第三部分
接下来就是绘制Icon的方法。
Utilities.java是Launcher2中专门绘制位图的类,所有与绘制图标相关的方法,都可以添加到这个油条包中。
自己在Utilities.java中添加了专门绘制CalendarIcon的方法:
[javascript]view plain copy staticBitmapcreateCalendarIconBitmap(Drawableicon,Contextcontext){String[]dayOfWeek=context.getResources().getStringArray(<strong>R.array.week_days</strong>);Bitmapb=createIconBitmap(icon,context);Stringday=String.valueOf(Calendar.getInstance().get(Calendar.DAY_OF_MONTH));intweekIndex=Calendar.getInstance().get(Calendar.DAY_OF_WEEK);Stringweek=dayOfWeek[weekIndex-1];finalfloatmDensity=context.getResources().getDisplayMetrics().density;finalCanvascanvas=sCanvas;Paintpaint=newPaint(Paint.ANTI_ALIAS_FLAG);intwidth=b.getWidth();intheigth=b.getHeight();canvas.setBitmap(b);RectFrectF=newRectF(0,0,width,heigth);paint.setColor(Color.WHITE);canvas.drawRoundRect(rectF,8,8,paint);paint.setColor(Color.RED);paint.setAlpha(180);paint.setTextSize(14f*mDensity);paint.setTypeface(Typeface.DEFAULT_BOLD);RectrectWeek=newRect();paint.getTextBounds(week,0,week.length(),rectWeek);[javascript]view plain copy intweekWidth=rectWeek.right-rectWeek.left;intweekHeigth=rectWeek.bottom-rectWeek.top;floatweekX=Math.max(0,(width-weekWidth)/2-rectWeek.left);floatweekY=Math.max(0,weekHeigth-rectWeek.bottom)+2f*mDensity;canvas.drawText(week,weekX,weekY,paint);paint.setColor(Color.DKGRAY);paint.setAlpha(220);paint.setTextSize(32f*mDensity);RectrectDay=newRect();paint.getTextBounds(day,0,day.length(),rectDay);intdayWidth=rectDay.right-rectDay.left;intdayHeigth=rectDay.bottom-rectDay.top;floatdayX=(width-dayWidth)/2-rectDay.left;floatdayY=(heigth+weekY+dayHeigth)/2-rectDay.bottom;canvas.drawText(day,dayX,dayY,paint);returnb;[javascript]view plain copy }
在Value中添加对应的星期资源
添加value/array.xml 和value-zh-CN/array.xml文件
[html]view plain copy <resourcesxmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"><string-arrayname="week_days"><item>Sun</item><item>Mon</item><item>Tue</item><item>Wed</item><item>Thu</item><item>Fri</item><item>Sat</item></string-array></resources>
[html]view plain copy <resourcesxmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"><string-arrayname="week_days"><item>星期日</item><item>星期一</item><item>星期二</item><item>星期三</item><item>星期四</item><item>星期五</item><item>星期六</item></string-array></resources>
通过这三个部分,就能完成Calendar Icon 的动态刷新了。
下面再绘制一张时序图来辅助一下我的实现方法:
以上就是全部内容,只是个人的一个思路,如果有意见或者建议,欢迎指正。