本问转自: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中注册监听
public static final String sApplicationIconChanged = "com.android.iconchanged" ;
@Override
public void onCreate() {
super .onCreate();
sIsScreenLarge = getResources().getBoolean(R.bool.is_large_screen);
sScreenDensity = getResources().getDisplayMetrics().density;
mWidgetPreviewCacheDb = new WidgetPreviewLoader.CacheDb( this );
mIconCache = new IconCache( this ); mModel = new LauncherModel( this , mIconCache);
filter = new IntentFilter();
filter.addAction(SearchManager.INTENT_GLOBAL_SEARCH_ACTIVITY_CHANGED);
registerReceiver(mModel, filter);
filter = new IntentFilter();
filter.addAction(SearchManager.INTENT_ACTION_SEARCHABLES_CHANGED);
registerReceiver(mModel, filter);
/ Added Added by hao for refresh CalendarIcon start
filter = new IntentFilter(); filter.addAction(Intent.ACTION_TIME_CHANGED);
filter.addAction(Intent.ACTION_DATE_CHANGED);
filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
filter.addAction(sApplicationIconChanged);
registerReceiver(mModel, filter);
/ Added end
ContentResolver resolver = getContentResolver();
resolver.registerContentObserver(LauncherSettings.Favorites.CONTENT_URI, true , mFavoritesObserver);
}
2、在LauncherModel.java中的Callbacks接口中添加回调方法,在onReceive中添加对日期变化的判断
public interface Callbacks {
public void bindSearchablesChanged();
public void onPageBoundSynchronously( int page);
public void updateApplicationsIcon(String pkgName);
}
@Override
public void onReceive(Context context, Intent intent) {
if (DEBUG_LOADERS) Log.d(TAG, "onReceive intent=" + intent);
final String action = intent.getAction();
if (Intent.ACTION_PACKAGE_CHANGED.equals(action)
|| Intent.ACTION_PACKAGE_REMOVED.equals(action)
|| Intent.ACTION_PACKAGE_ADDED.equals(action)) {
} else if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(action)) {
}
} else if (SearchManager.INTENT_GLOBAL_SEARCH_ACTIVITY_CHANGED.equals(action) ||
SearchManager.INTENT_ACTION_SEARCHABLES_CHANGED.equals(action)) {
if (mCallbacks != null ) {
Callbacks callbacks = mCallbacks.get();
if (callbacks != null ) {
callbacks.bindSearchablesChanged();
}
}
} else if (Intent.ACTION_TIME_CHANGED.equals(action) ||
Intent.ACTION_DATE_CHANGED.equals(action) ||
Intent.ACTION_TIMEZONE_CHANGED.equals(action) ||
mApp.sApplicationIconChanged.equals(action)) {
String pkgName = null ;
if (mApp.sApplicationIconChanged.equals(action)) {
pkgName = intent.getStringExtra("packageName" );
} else {
pkgName = "com.android.calendar" ;
}
final ArrayList list
= (ArrayList) mBgAllAppsList.data.clone();
ApplicationInfo info = null ;
if ( null == list || list.isEmpty()) {
return ;
}
for (ApplicationInfo ai : list) {
if (ai.componentName.getPackageName().equals(pkgName)) {
info = ai;
break ;
}
}
if ( null != info) {
if (mCallbacks != null ) {
Callbacks callbacks = mCallbacks.get();
if (callbacks != null ) {
callbacks.updateApplicationsIcon(info.componentName.getPackageName());
}
}
}
}
}
通过以上两步,当日期发生变化或者接收到自定义的广播"com.android.iconchanged" 的时候,能通过回调自定义的
public void updateApplicationsIcon(String pkgName); 接口来实现刷新操作
第二.在Launcher.java 派发刷新操作
我们知道,在源码中Launcher.java 是实现了 LauncherModel.Callbacks接口的,那么可以在Launcher中区Override updateApplicationsIcon方法。
[javascript] view plain copy
@Override
public void updateApplicationsIcon(String pkgName){
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
void updateShortcut(String pkgName) {
ArrayList childrenLayouts = getAllShortcutAndWidgetContainers();
for (ShortcutAndWidgetContainer layout: childrenLayouts) {
int childCount = layout.getChildCount();
for ( int j = 0; j < childCount; j++) {
final View view = layout.getChildAt(j);
Object tag = view.getTag();
if (tag instanceof ShortcutInfo) {
ShortcutInfo info = (ShortcutInfo) tag;
try {
if (pkgName.equals(info.intent.getComponent().getPackageName())) {
BubbleTextView bv = (BubbleTextView) view;
"color:#cc0000;" >bv.applyFromShortcutInfo(info, mIconCache);
}
} catch (Exception e) {
Log.e(TAG, "" + e);
}
}
}
}
}
AppsCustomizePagedView.java
[javascript] view plain copy
public void updateApp(String pkgName) {
for ( int i = 0; i < mNumAppsPages; i++) {
PagedViewCellLayout cl = (PagedViewCellLayout) getPageAt(i);
if (cl == null ) return ;
final int count = cl.getPageChildCount();
View appIcon = null ;
ApplicationInfo appInfo = null ;
ShortcutInfo shortcut = null ;
for ( int j = 0; j < count; j++) {
appIcon = cl.getChildOnPageAt(j);
appInfo = (ApplicationInfo) appIcon.getTag();
if (appInfo != null && appInfo.componentName.getPackageName().equals(pkgName)) {
PagedViewIcon pv = (PagedViewIcon) appIcon;
"color:#cc0000;" >shortcut = appInfo.makeShortcut();
pv.setCompoundDrawablesWithIntrinsicBounds(null ,
new FastBitmapDrawable(shortcut.getIcon(mIconCache)), null , null );
}
}
在这两个类中都是通过遍历自己的View然后去将新绘制的Drawable 对象显示到package对应的Icon上去。标红的代码就是重新添加Icon的位置。
我们来看看BubbleTextView::applyFromShortcutInfo(ShortcutInfo info, IconCache iconCache)方法:
[javascript] view plain copy
public void applyFromShortcutInfo(ShortcutInfo info, IconCache iconCache) {
Bitmap b = info.getIcon(iconCache);
setCompoundDrawablesWithIntrinsicBounds(null ,
new FastBitmapDrawable(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
public Bitmap getIcon(IconCache iconCache, boolean showUnread) {
if ( true || mIcon == null ) {
updateIcon(iconCache);
}
if (showUnread) {
}
return mIcon;
}
public void updateIcon(IconCache iconCache) {
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
public Bitmap getIcon(Intent intent) {
synchronized (mCache) {
final ResolveInfo resolveInfo = mPackageManager.resolveActivity(intent, 0);
ComponentName component = intent.getComponent();
if (resolveInfo == null || component == null ) {
return mDefaultIcon;
}
CacheEntry entry = cacheLocked(component, resolveInfo, null );
return entry.icon;
}
}
在cacheLocked方法的最后根据包名判断,如果是日历应用的话,就直接绘制日历的customized图标,标红的地方就是绘制图标的核心代码。
[javascript] view plain copy
private CacheEntry cacheLocked(ComponentName componentName, ResolveInfo info,
HashMap labelCache) {
CacheEntry entry = mCache.get(componentName);
if (entry == null ) {
entry = new CacheEntry();
mCache.put(componentName, entry);
ComponentName key = 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);
}
if ( null != entry && componentName.getPackageName().equals( "com.android.calendar" )) {
entry.icon = Utilities.createCalendarIconBitmap(
getFullResIcon(info), mContext);
}
return entry;
}
第三部分
接下来就是绘制Icon的方法。
Utilities.java是Launcher2中专门绘制位图的类,所有与绘制图标相关的方法,都可以添加到这个油条包中。
自己在Utilities.java 中添加了专门绘制CalendarIcon的方法:
[javascript] view plain copy
static Bitmap createCalendarIconBitmap(Drawable icon, Context context) {
String[] dayOfWeek = context.getResources().getStringArray(R.array.week_days );
Bitmap b = createIconBitmap(icon, context);
String day = String.valueOf(Calendar.getInstance().get(Calendar.DAY_OF_MONTH));
int weekIndex = Calendar.getInstance().get(Calendar.DAY_OF_WEEK);
String week = dayOfWeek[weekIndex - 1];
final float mDensity = context.getResources().getDisplayMetrics().density;
final Canvas canvas = sCanvas;
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
int width = b.getWidth();
int heigth = b.getHeight();
canvas.setBitmap(b);
RectF rectF = new RectF(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);
Rect rectWeek = new Rect();
paint.getTextBounds(week, 0, week.length(), rectWeek);
[javascript] view plain copy
int weekWidth = rectWeek.right - rectWeek.left;
int weekHeigth = rectWeek.bottom - rectWeek.top;
float weekX = Math.max(0, (width - weekWidth)/2 - rectWeek.left);
float weekY = 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);
Rect rectDay = new Rect();
paint.getTextBounds(day, 0, day.length(), rectDay);
int dayWidth = rectDay.right - rectDay.left;
int dayHeigth = rectDay.bottom - rectDay.top;
float dayX = (width - dayWidth)/2 - rectDay.left;
float dayY = (heigth + weekY + dayHeigth)/2- rectDay.bottom;
canvas.drawText(day, dayX, dayY, paint);
return b;
[javascript] view plain copy
}
在Value中添加对应的星期资源
添加value/array.xml 和value-zh-CN/array.xml文件
< resources xmlns:xliff = "urn:oasis:names:tc:xliff:document:1.2" >
< string-array name = "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 >
< resources xmlns:xliff = "urn:oasis:names:tc:xliff:document:1.2" >
< string-array name = "week_days" >
< item > 星期日 item >
< item > 星期一 item >
< item > 星期二 item >
< item > 星期三 item >
< item > 星期四 item >
< item > 星期五 item >
< item > 星期六 item >
string-array >
resources >
通过这三个部分,就能完成Calendar Icon 的动态刷新了。
下面再绘制一张时序图来辅助一下我的实现方法:
以上就是全部内容,只是个人的一个思路,如果有意见或者建议,欢迎指正。