本篇文章将实现一个简单的可替代系统桌面的launcher应用。
此应用应该具有如下功能:
1. 自定义的桌面应用能够被设置为系统的默认桌面,替代原有桌面。
2. 系统桌面上的app图标能够排列在我们的自定义桌面上。
3. 点击自定义桌面上的app图标,能够打开对应的app。

下面就看看具体实现:
新建一个空项目,命名为LauncherTest,然后打开项目的manifest文件:
一般情况下,自动生成的MainActivity的配置如下:

<activity android:name=".MainActivity" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

其中”android.intent.action.MAIN”这个action指定了应用的入口activity,”android.intent.category.LAUNCHER”指定了程序的图标要显示在应用程序列表里。

一、功能一

要实现功能一,要对上面的代码intent-filter里加上如下两行:

<category android:name="android.intent.category.HOME"/>
                <category android:name="android.intent.category.DEFAULT"/>

如果此桌面activity不需要另外显示图标在系统桌面上的话,就不需要category android:name=”android.intent.category.LAUNCHER”这行。
这样的话,我们的程序运行起来后,再按手机Home键时,系统就会弹出一个列表,列出所有的桌面应用,让用户选择用哪个应用打开桌面。里面就会包含我们的LauncherTest。

但是我们的应用什么功能都没有,即使选为桌面也是任何功能都没有的。

二、功能二:

要怎么列出系统桌面上显示的所有的app的图标呢?
我们可以先获取对应的app列表,然后逐个获取并显示其图标。实现如下:

//系统桌面上显示的app列表
    private List<ResolveInfo> localApps;

在MainActivity的onCreate()里调用如下得到app列表信息:

Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
        mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);

        localApps = getPackageManager().queryIntentActivities(mainIntent, 0);

这些app图标里也会包含我们自己的应用图标,但理想效果应该是不显示比较好,因为我们的app是用来替代系统桌面的,没有别的作用。如果不显示自己的图标呢?加上以下的代码:

//遍历获取的app列表,如果哪个元素的app包名和我们的一样,就说明是我们本应用,则移除此元素。
        Iterator<ResolveInfo> iterator = localApps.iterator();
        while(iterator.hasNext()) {
            ResolveInfo resolveInfo = iterator.next();
            String packageName = resolveInfo.activityInfo.packageName;
            if(packageName.equals(getApplication().getPackageName())) {
                iterator.remove();
            }
        }

在这里注意用到了iterator的remove()来移除List中的元素。那么用List类的remove()方法可以不?答案是不行,如果在List遍历的过程中用List的remove()方法删除List的元素,会发生CurrentModificationException。而Iterator的remove()方法是安全有效的,不会发生异常。

接下来,我们要获取每个app的图标并显示出来,显示图标可以用到GridView,用以把图标网格状显示,和系统桌面效果一样。
修改布局文件activity_main.xml, 加入GridView:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/darker_gray">


    <GridView
        android:id="@+id/apps_list"
        android:layout_width="match_parent"
        android:numColumns="4"
        android:gravity="center"
        android:verticalSpacing="20dp"
        android:paddingTop="10dp"
        android:paddingBottom="10dp"
        android:scrollbars="none"
        android:overScrollMode="never"
        android:layout_height="wrap_content">
    </GridView>

</RelativeLayout>

其中有几个属性说一下:
android:numColumns=”4”是设置图标列数为4。
android:verticalSpacing=”20dp”是设置每行之间的间距。
android:scrollbars=”none”是为了去掉列表滑动时右边的滚动条。
android:overScrollMode=”never”是为了去掉列表下拉或者上拉时,顶部或底部出现的半月形的阴影。

然后看看MainActivity中怎么使用:
我们需要给GridView设置一个适配器,然后需要给app图标的显示定义一个布局:

app图标显示的布局如下:
icon_layout.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:orientation="vertical"
    android:gravity="center_horizontal"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <ImageView
        android:id="@+id/app_icon"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        tools:src ="@mipmap/ic_launcher"/>
    <TextView
        android:id="@+id/app_name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        tools:text="相机"/>
</LinearLayout>

显示的效果就是上面一个图标,下面一个app名。

然后下面是对应的适配器的定义:

public class AppsAdapter extends BaseAdapter {
        private List<ResolveInfo> apps;

        //构造器中传入app列表信息
        public AppsAdapter(List<ResolveInfo> apps){
            this.apps = apps;
        }

        @Override
        public int getCount() {
            return apps.size();
        }

        @Override
        public Object getItem(int i) {
            return apps.get(i);
        }

        @Override
        public long getItemId(int i) {
            return i;
        }


        @Override
        public View getView(int i, View view, ViewGroup viewGroup) {

            //为了简单起见,这里没有用ViewHolder,可以改成ViewHolder以改善显示效率
            LayoutInflater inflater = LayoutInflater.from(MainActivity.this);
            View appView = inflater.inflate(R.layout.icon_layout, null);

            ImageView iv = (ImageView) appView.findViewById(R.id.app_icon);
            //设置图标适应方式和大小                       
            iv.setScaleType(ImageView.ScaleType.FIT_CENTER);
            iv.setLayoutParams(new LinearLayout.LayoutParams(70, 70));
            TextView tv = (TextView) appView.findViewById(R.id.app_name);

            ResolveInfo info = apps.get(i);

            //显示app图标            
    iv.setImageDrawable(info.activityInfo.loadIcon(getPackageManager()));
            //显示app名
            String appName = info.loadLabel(getPackageManager()).toString();
            tv.setText(appName);
            return appView;
        }
    }

上面的代码里,getView方法每次都会创建一个新的view,当数据很多时,显示效率会很低。
然后设置此适配器为GridView的适配器:

GridView appsGrid = (GridView) findViewById(R.id.apps_list);
        appsGrid.setAdapter(new AppsAdapter(localApps));

自此,第二个功能就完成了,app列表已经可以完全显示在我们的app界面上了。

三、功能三

第三个功能,点击各个app图标启动对应的app。
下面是点击事件的代码:

private AdapterView.OnItemClickListener clickListener = new AdapterView.OnItemClickListener() {
        @Override
        public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
            ResolveInfo info = localApps.get(i);
            //该应用的包名
            String pkg = info.activityInfo.packageName;
            //应用的主activity类
            String cls = info.activityInfo.name;
            ComponentName componet = new ComponentName(pkg, cls);
            //打开该应用的主activity
            Intent intent = new Intent();
            intent.setComponent(componet);
            startActivity(intent);
        }
    };

然后把点击事件应用到GridView上:

appsGrid.setOnItemClickListener(clickListener);

这样第三个功能也就完成了。

至此一个简单的桌面应用就完成了。效果图如下:

android launcher 选择 安卓launcher_xml