最近在做一个桌面的快捷方式展示的功能。将其中的一些技术点做一个分享。
一、筛选应用程序
要完成桌面快捷方式展示,首先要能够拿到当前系统中安装的应用程序,且这些应用程序是用户可见的(不包含系统的app)
网上有人用如下的方法:
List<PackageInfo> list = getPackageManager().getInstalledPackages(0);
但是从源码中可以得知,这样会得到所有系统的package:
packageManager的getInstalledPackages()方法实际上是一个抽象方法,具体实现在ApplicationPackageManager中
@SuppressWarnings("unchecked")
@Override
public List<PackageInfo> getInstalledPackages(int flags) {
return getInstalledPackages(flags, mContext.getUserId());
}
/** @hide */
@Override
public List<PackageInfo> getInstalledPackages(int flags, int userId) {
try {
ParceledListSlice<PackageInfo> slice = mPM.getInstalledPackages(flags, userId);
return slice.getList();
} catch (RemoteException e) {
throw new RuntimeException("Package manager has died", e);
}
}
所以最终还是到packageManagerService中去:
@Override
public ParceledListSlice<PackageInfo> getInstalledPackages(int flags, int userId) {
final boolean listUninstalled = (flags & PackageManager.GET_UNINSTALLED_PACKAGES) != 0;
enforceCrossUserPermission(Binder.getCallingUid(), userId, true, "get installed packages");
// writer
synchronized (mPackages) {
ArrayList<PackageInfo> list;
if (listUninstalled) {
list = new ArrayList<PackageInfo>(mSettings.mPackages.size());
for (PackageSetting ps : mSettings.mPackages.values()) {
PackageInfo pi;
if (ps.pkg != null) {
pi = generatePackageInfo(ps.pkg, flags, userId);
} else {
pi = generatePackageInfoFromSettingsLPw(ps.name, flags, userId);
}
if (pi != null) {
list.add(pi);
}
}
} else {
list = new ArrayList<PackageInfo>(mPackages.size());
for (PackageParser.Package p : mPackages.values()) {
PackageInfo pi = generatePackageInfo(p, flags, userId);
if (pi != null) {
list.add(pi);
}
}
}
return new ParceledListSlice<PackageInfo>(list);
}
}
mSettings.mPackages.values()中存储的即为当前系统的所有package,包括系统app。因此这条路不行。
其实有非常方便的方法,实例代码如下:
Intent baseIntent = new Intent();
baseIntent .setAction(Intent.ACTION_MAIN);
baseIntent .addCategory(Intent.CATEGORY_LAUNCHER);
PackageManager packageManager = getPackageManager();
List<ResolveInfo> list = packageManager.queryIntentActivities(
baseIntent, 0 /* no flags */);
Collections.sort(list, new ResolveInfo.DisplayNameComparator(
packageManager));
Intent.CATEGORY_LAUNCHER帮助我们很好的过滤的系统app,good!
二、利用Gridview做内容展示
因为已安装的应用程序是动态的,所以这里选择使用gridview来做展示
ArrayList<HashMap<String, Object>> items = new ArrayList<HashMap<String, Object>>();
final int listSize = list.size();
for (int i = 0; i < listSize; i++) {
ResolveInfo resolveInfo = list.get(i);
HashMap<String, Object> map = new HashMap<String, Object>();
map.put("ItemImage",resolveInfo.loadIcon(packageManager));
map.put("ItemText", resolveInfo.loadLabel(packageManager));
items.add(map);
SimpleAdapter adapter = new SimpleAdapter(this, items,
R.layout.shortcut_item,
new String[] { "ItemImage", "ItemText" }, new int[] {
R.id.shortcut_image, R.id.shortcut_title });
gridview.setAdapter(adapter);
这里的list即为第一步中得到的package列表,我们将icon信息,和application的名字保存到items这个列表中,用来作为adapter的数据来源。
这里有一点需要特别注意的,gridview或者listview这些对象在显示图片的时候只能使用本地drawable中的资源,但是这些app的图标却在另外app当中,这时候就需要用到viewBinder的绑定机制,如此一来不仅能够显示外部app中的资源,也可以从网络获取资源图片。代码也很简单:
adapter.setViewBinder(new ViewBinder() {
@Override
public boolean setViewValue(View view, Object data,
String textRepresentation) {
if (view instanceof ImageView && data instanceof Drawable) {
ImageView iv = (ImageView) view;
iv.setImageDrawable((Drawable) data);
return true;
} else
return false;
}
});
三、实现快捷方式的选取
通过上面的步骤,我们已经实现了app的选取和展示,那么我们最后要实现的就是在上面的界面中选择一个应用,并添加到桌面。
那么这里在处理点击事件的时候要怎么处理呢?
其实Android源码已经提供了一个非常方便的Action:ACTION_PICK_ACTIVITY
Android SDK API对其的描述是:
ACTION_PICK_ACTIVITY
Input: get*Extra field EXTRA_INTENT is an Intent used with PackageManager.queryIntentActivities to determine the set of activities from which to pick.
Output: Class name of the activity that was selected.
要实现点击应用图标,是选中而不是启动这个app,只要做如下操作:
Intent pickIntent = new Intent(Intent.ACTION_PICK_ACTIVITY);
Intent mainIntent = new Intent();
mainIntent.setAction(Intent.ACTION_MAIN);
mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
pickIntent.putExtra(Intent.EXTRA_INTENT, mainIntent);
pickIntent.putExtra(Intent.EXTRA_TITLE, "选择应用程序"); // 设置界面title
在响应这个action的activity中的click处理函数中去获得这个对应app的intent,然后返回即可:
setResult(Activity.RESULT_OK, intent);
finish();
四、主页面的展示
到这里我们走到最后一步了,主页面的展示,展示是一个动态添加view的过程
LayoutInflater inflater = LayoutInflater.from(this);
View shortcut_view = inflater.inflate(R.layout.display_item, null);
ImageView img = (ImageView) shortcut_view
.findViewById(R.id.shortcut_img);
img.setImageDrawable(appIcon);
TextView tv = (TextView) shortcut_view.findViewById(R.id.shortcut_label);
tv.setText(applabel);
tv.setVisibility(View.INVISIBLE);
tv.setOnFocusChangeListener(this);
shortcut_view.setFocusable(true);
shortcut_view.setOnClickListener(this);
shortcut_view.setOnFocusChangeListener(this);
shortcut_view.setTag(data);
shortcut_view.setBackgroundResource(R.drawable.launcher_iv_border);
LinearLayout.LayoutParams llparams = new LinearLayout.LayoutParams(120, 120);
llparams.setMargins(10, 10, 10, 10);
appContainerLayout.addView(shortcut_view, llparams);
display_item.xml是每一个动态添加的快捷方式的布局信息
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
>
<ImageView
android:id="@+id/shortcut_img"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
/>
<TextView
android:id="@+id/shortcut_label"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:gravity="center_horizontal"
android:background="#a0000000"
android:textColor="#ffffff"
android:textSize="20dp"
/>
</RelativeLayout>
我们还想要做一个简单的动态效果,当光标聚焦时才显示包名,因此为TextView添加了一个FocusChangeListener,并把默认状态设成INVISIBLE
public void onFocusChange(View arg0, boolean arg1) {
// TODO Auto-generated method stub
TextView tv = (TextView) arg0.findViewById(R.id.shortcut_label);
if (arg1) {
tv.setVisibility(View.VISIBLE);
} else {
tv.setVisibility(View.INVISIBLE);
}
}