前几天翻看之前下载的各种资料,无意中发现了一款AppWidght应用的源代码,想起之前一直想研究这块,却一直没机会,于是花费了两天时间,把这款桌面电量监控小插件的实现研究了一下,收获颇丰,特此把学到的东西与大家分享。明天就是苦逼的信息论的期末考试了,我是一点看不懂,唉,就这样吧,重修再说吧,我们换个好心情,看一下这款小软件是如何实现的。
虽然这个小软件实现的不错,但是代码质量我却不敢恭维,费了好大劲,才把很多没用的代码和文件剔除,并且对一些实现进行了优化,话不多说,咱们先来看看效果图饱饱眼福。
首先,这是AppWidght显示界面的效果,
下面是在桌面的显示效果
当我们点击这个小电池的时候,会进入下面的界面
在这个界面,我们可以设置以后点击小电池进入的软件,相当于一个快捷图标的功能,当然,若我们不设置,则会默认进入这个界面
下面是当我们点击“要加载的软件”的时候,显示的界面
这个界面有两组应用,一个是精选应用,这个是软件自带的推荐启动界面,我们点击预览,可以打开对应的界面。下面的是其他应用,是指目前手机上带的其他应用,我们点击预览按钮,也可以跳转到对应的软件启动界面,当我们点击的条目的时候,就相当于是修改了下次的启动软件,那么当我们下次点击桌面的小电池的时候,进入的就不是默认的设置界面了,而是我们设置好的启动软件的界面。如果我们想更换启动软件,我们只需要将小电池删除,然后重新添加一次即可重置。
好了,介绍完了这款软件的功能,下面我们就需要了解这样一款软件是如何开发的了。
首先,我们看一下整个工程的目录,让大家有个简单的认识
首先介绍一下各个数字代表的文件的功能
1.后台服务类,用于在后台运行,监听电量的变化情况,当电量发生变化的时候,通知小插件进行电量信息的更新
2.桌面布局更新的帮助类,里面封装了修改桌面电量文字图片显示的方法
3.继承自AppWidgetProvider,这是每个Widght必有的类,里面封装了Widght初始化、更新、销毁等方法,实际上AppWidgetProvider继承自BroadcastReceiver,就是说,桌面插件的状态变化,也是通过广播的形式进行的
4.点击桌面小电池进入的界面
5.设置启动软件的界面
6.不同电量下不同的显示图片
7.桌面小部件和Activity的布局文件
8.arrays存放了精选应用的信息,下面详细介绍
9.用于配置Widght的xml文件,每个Widght都必须有
10.清单文件
好了,介绍完各个文件的基本功能,下面我们就要开始介绍Widght的具体实现了。
首先,我们若要实现一个Widght,我们必须有一个类,继承自AppWidgetProvider,在这里,就是我们的BatteryWidgetProvider类,下面贴代码
BatteryWidgetProvider.java
/** * 实现AppWidgetProvider * * @Time 2014-6-27 下午2:21:02 */ public class BatteryWidgetProvider extends AppWidgetProvider { // 后台服务名 private static final String BATTERY_SERVICE_ACTION = "com.bwx.qs.battery.BatteryService"; // 当AppWidget被添加到桌面,开始初始化,开启后台监听服务 public void onEnabled(Context context) { Intent intent = new Intent(BATTERY_SERVICE_ACTION); context.startService(intent); } // 当AppWidget被用户从界面移除,会调用这个方法 public void onDisabled(Context context) { // 停止后台监听服务 Intent intent = new Intent(BATTERY_SERVICE_ACTION); context.stopService(intent); // 删除本地的用户配置文件 SharedPreferences preferences = context.getSharedPreferences( BatteryWidget.PREFS, Context.MODE_PRIVATE); preferences.edit().remove(BatteryWidget.PREF_ACTIVITY_NAME).commit(); } // 当电量发生变化的时候,会调用这个方法,更新界面 public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { BatteryService.requestWidgetUpdate(context); } }
这个类里面有三个很重要的方法,具体功能注释很清楚,就不过多解释了。
除了要实现这样一个类,我们还需要使用xml文件,对我们的Widght进行一些参数的配置,在我们这个项目里面,就是battery_widget_info.xml,下面是代码
<?xml version="1.0" encoding="utf-8"?> <appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android" android:configure="com.bwx.qs.battery.QuickBatteryActivity" android:initialLayout="@layout/battery_widget" android:minHeight="72px" android:minWidth="72px" android:updatePeriodMillis="86400000" > </appwidget-provider>
configure属性是设置点击后显示的Activity信息
initialLayout是设置Widght的布局文件,就是我们在桌面显示的小电池
minHeight、minWidth是设置显示的大小,72px对应的是一个单元格
updatePeriodMillis设置的是更新间隔,为了节省电量,最小间隔是30分钟,小于30分钟按照30分钟计算,我这里设置的是一天,设置为0则为不更新
现在我们的配置文件也有了,我们还需要做什么呢?因为BatteryWidgetProvider继承自AppWidgetProvider,所以也属于一个广播接收者,那么我们就需要在清单文件中进行配置,我们的清单文件的配置信息如下
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" package="com.bwx.qs.battery" android:versionCode="1" android:versionName="1" > <uses-sdk android:minSdkVersion="8" android:targetSdkVersion="19" /> <application android:allowBackup="true" android:icon="@drawable/ic_icon" android:label="@string/txt_quick_battery" > <receiver android:name=".BatteryWidgetProvider" > <intent-filter> <action android:name="android.appwidget.action.APPWIDGET_UPDATE" /> </intent-filter> <meta-data android:name="android.appwidget.provider" android:resource="@xml/battery_widget_info" /> </receiver> <service android:name="com.bwx.qs.battery.BatteryService" tools:ignore="ExportedService" > <intent-filter> <action android:name="com.bwx.qs.battery.BatteryService" /> </intent-filter> </service> <activity android:name=".SettingsActivityList" android:screenOrientation="portrait" /> <activity android:name=".QuickBatteryActivity" android:label="@string/txt_quick_battery" android:screenOrientation="portrait" android:theme="@android:style/Theme.NoTitleBar" > <intent-filter> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" /> <data android:mimeType="com.bwx.qs.battery/widget" /> </intent-filter> </activity> </application> </manifest>
我们先不关注其他信息,我们只关注下面这块
<receiver android:name=".BatteryWidgetProvider" > <intent-filter> <action android:name="android.appwidget.action.APPWIDGET_UPDATE" /> </intent-filter> <meta-data android:name="android.appwidget.provider" android:resource="@xml/battery_widget_info" /> </receiver>
在这里完成了provider的注册,同时,还需要添加意图过滤器
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
还有一点很重要,就是我们在meta-data节点,将我们的配置信息与BatteryWidgetProvider联系了起来,这样,我们就完成了一个Widght最基本的设置,剩下的就是我们业务方法的完成。
前面说道,在BatteryWidgetProvider的onEnabled方法中,我们开启了后台服务,那么,在后台的服务里面,我们又完成了哪些功能呢?
下面是BatteryService的代码
/** * 后台电量监听服务 * * @Time 2014-6-27 下午2:40:03 */ public class BatteryService extends Service { private int mBatteryChargeLevel = -1; //充电器是否连接 private boolean mChargerConnected; private ScreenStateService mScreenStateReceiver; public static final String EXT_UPDATE_WIDGETS = "updateWidgets"; private static final String TAG = "BatteryService"; /** * 电池状态的广播接受者 * * @Time 2014-6-27 下午2:42:21 */ private class BatteryStateReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); // 如果电池的电量发生变化 if (Intent.ACTION_BATTERY_CHANGED.equals(action)) { int rawlevel = intent.getIntExtra("level", -1); int scale = intent.getIntExtra("scale", -1); int level = 0; if (rawlevel >= 0 && scale > 0) { level = (rawlevel * 100) / scale; } mBatteryChargeLevel = level; mChargerConnected = intent.getIntExtra("plugged", 0) > 0 && level < 100; Log.d(TAG, "battery state: level=" + level + ", charging=" + mChargerConnected); } // 更新AppWidget BatteryWidget.updateWidgets(context, mBatteryChargeLevel, mChargerConnected); } } /** * 屏幕状态监听者,用于屏幕状态发生改变时,将电量的广播接收者进行注册与注销操作,节省电量损耗 * * @author Zhao KaiQiang * * @Time 2014-6-27 下午2:48:58 */ private class ScreenStateService extends BroadcastReceiver { private BatteryStateReceiver mBatteryStateReceiver; @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (Intent.ACTION_SCREEN_ON.equals(action)) { registerBatteryReceiver(true, context); } else if (Intent.ACTION_SCREEN_OFF.equals(action)) { registerBatteryReceiver(false, context); } } public void registerBatteryReceiver(boolean register, Context context) { // 屏幕亮的时候执行 if (register && mBatteryStateReceiver == null) { // 实例化BatteryStateReceiver,并且对BatteryStateReceiver进行注册 mBatteryStateReceiver = new BatteryStateReceiver(); IntentFilter filter = new IntentFilter( Intent.ACTION_BATTERY_CHANGED); context.registerReceiver(mBatteryStateReceiver, filter); // 屏幕关闭的时候,将广播接收者注销,节省电量,并将mBatteryStateReceiver置为null } else if (mBatteryStateReceiver != null) { context.unregisterReceiver(mBatteryStateReceiver); mBatteryStateReceiver = null; } } // 对屏幕状态的广播接收者进行注册与注销操作 public void registerScreenReceiver(boolean register, Context context) { if (register) { IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_SCREEN_ON); filter.addAction(Intent.ACTION_SCREEN_OFF); context.registerReceiver(this, filter); } else { registerBatteryReceiver(false, context); context.unregisterReceiver(this); } } } //开启服务 public void onStart(Intent intent, int startId) { if (mScreenStateReceiver == null) { mScreenStateReceiver = new ScreenStateService(); mScreenStateReceiver.registerScreenReceiver(true, this); if (isScreenOn(this)) { mScreenStateReceiver.registerBatteryReceiver(true, this); } } Bundle ext = intent.getExtras(); if (ext != null && ext.getBoolean(EXT_UPDATE_WIDGETS, false)) { BatteryWidget.updateWidgets(this, mBatteryChargeLevel, mChargerConnected); } } // 当服务被销毁的时候,注销所有的广播接收者 public void onDestroy() { if (mScreenStateReceiver != null) { mScreenStateReceiver.registerScreenReceiver(false, this); mScreenStateReceiver = null; } } @Override public IBinder onBind(Intent intent) { return null; } // 开启后台监听服务 public static void requestWidgetUpdate(Context context) { Intent serviceIntent = new Intent(context, BatteryService.class); serviceIntent.putExtra(EXT_UPDATE_WIDGETS, true); context.startService(serviceIntent); } // 判断当前屏幕状态 private static boolean isScreenOn(Context context) { PowerManager pm = (PowerManager) context .getSystemService(Context.POWER_SERVICE); int sdkVersion = Build.VERSION.SDK_INT; try { // >= 2.1 if (sdkVersion >= 7) { Boolean bool = (Boolean) PowerManager.class.getMethod( "isScreenOn").invoke(pm); return bool.booleanValue(); } else { // < 2.1 Field field = PowerManager.class.getDeclaredField("mService"); field.setAccessible(true); Object service = field.get(pm); Long timeOn = (Long) service.getClass() .getMethod("getScreenOnTime").invoke(service); return timeOn > 0; } } catch (Exception e) { Log.e(TAG, "cannot check whether screen is on", e); return true; } } }
因为后台服务类是功能的主要实现类,因此代码比较多,大家慢慢看,我稍微介绍下。
在后台服务类里面,定义了两个广播接收者,一个是监听屏幕状态的ScreenStateService,一个是监听电池电量状态的BatteryStateReceiver。
在开启后台服务之后,在onStart方法中,进行了ScreenStateService的广播注册,同时,若屏幕状态为亮,则也将BatteryStateReceiver进行注册。在BatteryStateReceiver的onReceive方法中,若发生了电量变化的事件,则调用BatteryWidget.updateWidgets()方法,对桌面的小电池的背景图片和文字进行修改,具体实现,我们一会在看。值得注意的是,在获取屏幕状态的isScreenOn方法中,使用到了反射机制,来获取当前屏幕状态,还是第一次见。
当服务开启之后,若屏幕亮着,就会注册两个广播接收者,若有电量修改,对桌面的电池图片进行修改。那么,当Widght被从桌面移除的时候,会调用什么方法呢?
在BatteryWidgetProvider的onDisabled方法,就是处理当Widght被用户从桌面移除的时候的处理逻辑,在这个方法里面,将我们的后台服务stop同时删除我们的配置文件,这个配置文件是用来记录我们设置的启动软件的,暂时还没介绍到。在服务的stop发生了什么呢?通过代码可以发现,在服务的stop方法中,完成了两个广播接收者的注销操作。
到目前为止,整个软件的实现思路应该已经很清楚了。用户添加Widght-->开启后台服务-->注册屏幕监听和电量监听事件-->电量发生变化-->通知桌面Widght更新电量显示的图片和文字。
那么,下面我们要介绍的,就是如何实现的Widght的图片和文字的更改。
首先,我们看一下小电池的布局是怎么实现的
battery_appearance.xml
<?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" > <ImageView android:id="@+id/battery" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:clickable="true" android:layout_marginTop="1dp" android:contentDescription="@string/todo" android:src="@xml/level_icons" /> <TextView android:id="@+id/capacity_center" android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@null" android:layout_gravity="center" android:textColor="#FF6EB4" android:textSize="16sp" android:textStyle="bold" /> <ImageView android:id="@+id/lightning" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="top|right" android:contentDescription="@string/todo" android:src="@drawable/ic_lightning" android:visibility="visible" /> </FrameLayout>
布局很简单,就是FrameLayout包裹着的三个控件,battery用于显示背景电量图片,capacity_center用于显示电量文本,lightning用于显示小闪电
指的注意的是,小电池的布局被单独抽取了出来,因为在两个地方用到了这个布局,一个是battery_widget.xml(这个是Widght在桌面的显示布局),一个是configuration.xml(这个是点击Widght之后的显示首页面,效果图中左上角的小电池用到了小电池的布局)。
下面,我们重点看一下,到底是如何修改Widght的布局显示的。负责修改布局的是BatteryWidget类,下面是代码
/** * 电量小部件类 * * @Time 2014-6-27 下午3:52:30 */ public class BatteryWidget { //用于在sharedpreferences中存放配置信息 public static final String PREFS = "common"; public static final String PREF_PACKAGE_NAME = "package"; public static final String PREF_CLASS_NAME = "class"; public static final String PREF_ACTIVITY_NAME = "name"; private static PendingIntent pendingIntent; private static final String MIME = "com.bwx.qs.battery/widget"; // 根据chargeLevel,改变显示的图片 public static void updateWidgets(Context context, int chargeLevel, boolean chargerConnected) { // 当chargeLevel<10,组拼成0X的形式 String level = chargeLevel < 10 ? "0" + chargeLevel : String .valueOf(chargeLevel); // 创建RemoteViews控件 RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.battery_widget); pendingIntent = getPendingIntent(context); views.setOnClickPendingIntent(R.id.battery, pendingIntent); //设置显示图片 views.setInt(R.id.battery, "setImageLevel", chargeLevel); views.setTextViewText(R.id.capacity_center, level); // 当电量是100时,隐藏小闪电和电量文字 views.setViewVisibility(R.id.capacity_center, chargeLevel < 100 ? View.VISIBLE : View.GONE); views.setViewVisibility(R.id.lightning, chargerConnected ? View.VISIBLE : View.GONE); AppWidgetManager widgetManager = AppWidgetManager.getInstance(context); ComponentName componentName = new ComponentName(context, BatteryWidgetProvider.class); widgetManager.updateAppWidget(componentName, views); } private static PendingIntent getPendingIntent(Context context) { SharedPreferences prefs = context.getSharedPreferences(PREFS, Context.MODE_PRIVATE); String name = prefs.getString(PREF_ACTIVITY_NAME, null); if (name == null) { Intent intent = new Intent(Intent.ACTION_VIEW); intent.setType(MIME); return PendingIntent.getActivity(context, 0, intent, 0); } else { String className = prefs.getString(PREF_CLASS_NAME, null); String packageName = prefs.getString(PREF_PACKAGE_NAME, null); Intent intent = new Intent(); intent.setClassName(packageName, className); return PendingIntent.getActivity(context, 0, intent, 0); } } }
在这里更改Widght的显示,需要用到RemoteViews类,在这段代码里面,有两个地方需要说一下,一个是
views.setOnClickPendingIntent(R.id.battery, pendingIntent);
这句话就是给battery对应的Imageview设置点击跳转事件,但是这里用的不是Intent对象,而是一个PendingIntent对象。PendingIntent对象是对intent的包装,是延迟的intent,在getPendingIntent方法中,根据配置文件中是否有已有的配置信息来判断到底是往哪里跳转。若有,则跳转到对应的软件界面,若没有,则发送一个隐式意图,将默认的界面打开。
另外一个,则是
views.setInt(R.id.battery, "setImageLevel", chargeLevel);
这句代码的意思就是,根据chargeLevel的大小,给battery对应的Imageview设置对应的图片。在这里有个参数 "setImageLevel",这里又使用到了反射,setImageLevel是Imageview的方法,下面是源代码中的解释
/** * Sets the image level, when it is constructed from a * {@link android.graphics.drawable.LevelListDrawable}. * * @param level The new level for the image. */ @android.view.RemotableViewMethod public void setImageLevel(int level) { mLevel = level; if (mDrawable != null) { mDrawable.setLevel(level); resizeFromDrawable(); } }
这个方法应该只是在RemoteViews才可以使用,布局文件中,battery是这样设置的
<ImageView android:id="@+id/battery" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:clickable="true" android:layout_marginTop="1dp" android:contentDescription="@string/todo" android:src="@xml/level_icons" />
src属性直接设置的level_icons文件,那么在这个文件里面,是怎么实现的呢?
level_icons.xml
<?xml version="1.0" encoding="utf-8"?> <level-list xmlns:android="http://schemas.android.com/apk/res/android" > <item android:drawable="@drawable/ic_2_8" android:maxLevel="1" android:minLevel="0"/> <item android:drawable="@drawable/ic_2_7" android:maxLevel="15" android:minLevel="2"/> <item android:drawable="@drawable/ic_2_6" android:maxLevel="20" android:minLevel="16"/> <item android:drawable="@drawable/ic_2_5" android:maxLevel="30" android:minLevel="21"/> <item android:drawable="@drawable/ic_2_4" android:maxLevel="40" android:minLevel="31"/> <item android:drawable="@drawable/ic_2_3" android:maxLevel="60" android:minLevel="41"/> <item android:drawable="@drawable/ic_2_2" android:maxLevel="80" android:minLevel="61"/> <item android:drawable="@drawable/ic_2_1" android:maxLevel="100" android:minLevel="81"/> </level-list>
在这个文件里面,定义了传入不同数值范围时,显示的图片,因此,使用这种方式,就完成了桌面Widght的图片背景的替换,我可是第一次见到这样使用。
背景图片如下
现在终于实现了背景图片的更换了,那么下面,我们就介绍最后一个功能的实现了,那就是设置启动软件的实现。
实现这个功能的类是SettingActivityList,下面是代码实现
/** * 设置启动应用 * */ public class SettingsActivityList extends ExpandableListActivity implements OnClickListener { private PackageManager mPackageManager; private ExpandableListAdapter mAdapter; private ArrayList<FeaturedActivity> mFeaturedActivities = new ArrayList<FeaturedActivity>(); private String[] mFeaturedClassNames; protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mAdapter = new ExpandableListAdapter(getLayoutInflater()); setListAdapter(mAdapter); mPackageManager = getPackageManager(); new CollectActivitiesTask().execute(); getExpandableListView().setItemsCanFocus(true); } // 应用分组的内部类 private class Group { private int titleTextId; private ArrayList<ResolveInfo> children; Group(int _titleTextId) { titleTextId = _titleTextId; children = new ArrayList<ResolveInfo>(); } } // 精选应用的内部类 private class FeaturedActivity { private String className; private String packageName; FeaturedActivity(String _className, String _packageName) { className = _className; packageName = _packageName; } } // ExpandableListView的适配器 class ExpandableListAdapter extends BaseExpandableListAdapter { private ArrayList<Group> mGroups; private LayoutInflater mInflater; public ExpandableListAdapter(LayoutInflater inflater) { mInflater = inflater; mGroups = new ArrayList<Group>(); init(); } //将xml文件中的精选应用的信息加载进来 private void init() { String[] params; FeaturedActivity activity; ArrayList<String> classNames = new ArrayList<String>(); String[] activities = SettingsActivityList.this.getResources() .getStringArray(R.array.featured_activities); for (int i = 0; i < activities.length; i++) { params = TextUtils.split(activities[i], "/"); mFeaturedActivities.add(activity = new FeaturedActivity( params[1], params[0])); classNames.add(activity.className); } mFeaturedClassNames = classNames.toArray(new String[classNames .size()]); Arrays.sort(mFeaturedClassNames); } public Object getChild(int groupPosition, int childPosition) { return mGroups.get(groupPosition).children.get(childPosition); } public long getChildId(int groupPosition, int childPosition) { return childPosition; } public View getChildView(int groupPosition, int childPosition, boolean isLastChild, View convertView, ViewGroup parent) { if (convertView == null) { convertView = mInflater.inflate(R.layout.activity_item, parent, false); } Group group = mGroups.get(groupPosition); ActivityInfo activityInfo = group.children.get(childPosition).activityInfo; ImageView icon = (ImageView) convertView.findViewById(R.id.icon); TextView text1 = (TextView) convertView.findViewById(R.id.text1); Button button = (Button) convertView.findViewById(R.id.button1); button.setEnabled(true); button.setTag(activityInfo); button.setOnClickListener(SettingsActivityList.this); View item = convertView.findViewById(R.id.item); item.setTag(activityInfo); item.setOnClickListener(SettingsActivityList.this); icon.setImageDrawable(activityInfo.loadIcon(mPackageManager)); text1.setText(activityInfo.loadLabel(mPackageManager)); return convertView; } public int getChildrenCount(int groupPosition) { return mGroups.get(groupPosition).children.size(); } public Object getGroup(int groupPosition) { return mGroups.get(groupPosition); } public int getGroupCount() { return mGroups.size(); } public long getGroupId(int groupPosition) { return groupPosition; } public View getGroupView(int groupPosition, boolean isExpanded, View convertView, ViewGroup parent) { if (convertView == null) { convertView = mInflater.inflate( android.R.layout.simple_expandable_list_item_1, parent, false); } Group group = mGroups.get(groupPosition); ((TextView) convertView.findViewById(android.R.id.text1)) .setText(group.titleTextId); return convertView; } public boolean hasStableIds() { return true; } public boolean isChildSelectable(int groupPosition, int childPosition) { return true; } } // 异步任务,用于获取手机的应用信息 class CollectActivitiesTask extends AsyncTask<Void, Group, Void> { @Override protected Void doInBackground(Void... params) { // 获取精选应用组信息 Group group = new Group(R.string.txt_recommended); Intent intent = new Intent(); FeaturedActivity activity; for (int i = 0; i < mFeaturedActivities.size(); i++) { activity = mFeaturedActivities.get(i); intent.setClassName(activity.packageName, activity.className); List<ResolveInfo> infos = mPackageManager .queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY); for (ResolveInfo info : infos) { group.children.add(info); } } // 添加数据并更新 if (group.children.size() > 0) { mAdapter.mGroups.add(group); mAdapter.notifyDataSetChanged(); } // 获取其他应用组信息 group = new Group(R.string.txt_other); Intent queryIntent = new Intent(Intent.ACTION_MAIN); queryIntent.addCategory(Intent.CATEGORY_LAUNCHER); List<ResolveInfo> list = mPackageManager.queryIntentActivities( queryIntent, 0); Collections.sort(list, new ResolveInfo.DisplayNameComparator( mPackageManager)); String className; for (ResolveInfo item : list) { className = item.activityInfo.name; int index = Arrays.binarySearch(mFeaturedClassNames, className); if (index < 0) { group.children.add(item); } } // 添加数据并更新 if (group.children.size() > 0) { mAdapter.mGroups.add(group); mAdapter.notifyDataSetChanged(); } return null; } } // 点击事件 public void onClick(View view) { // 如果选择的是item条目,则将选择的启动软件的配置信息保存 if (view.getId() == R.id.item) { ActivityInfo activityInfo = (ActivityInfo) view.getTag(); SharedPreferences prefs = getApplication().getSharedPreferences( PREFS, MODE_PRIVATE); prefs.edit() .putString(PREF_CLASS_NAME, activityInfo.name) .putString(PREF_PACKAGE_NAME, activityInfo.packageName) .putString(PREF_ACTIVITY_NAME, activityInfo.loadLabel(mPackageManager).toString()) .commit(); finish(); } else { // 如果选择是预览按钮,就打开对应软件 ActivityInfo activityInfo = (ActivityInfo) view.getTag(); String packageName = activityInfo.packageName; String className = activityInfo.name; Intent intent = new Intent(); intent.setClassName(packageName, className); startActivity(intent); } } }
这些代码应该不算很难懂,因为经过我整理了 = =。。之前的代码看的我都蛋疼,代码格式各种混乱!
这个界面内容分了两个部分,一个是精选应用,一个是其他应用,精选应用是固定的,有一个单独的xml文件负责存储这些应用的信息,arrays.xml,代码如下
<?xml version="1.0" encoding="utf-8"?> <resources> <string-array name="featured_activities"> <item>com.bwx.bequick/com.bwx.bequick.ShowSettingsActivity</item> <item>com.android.settings/com.android.settings.BatteryInfo</item> <item>com.android.settings/com.android.settings.fuelgauge.PowerUsageSummary</item> <item>com.android.settings/com.android.settings.battery_history.BatteryHistory</item> <item>com.android.settings/com.android.settings.UsageStats</item> <item>com.android.settings/com.android.settings.TestingSettings</item> <item>com.android.settings/com.android.settings.RadioInfos</item> </string-array> </resources>
这里面存放的这些页面的包名和类名,然后在代码中用/进行了分解。
到此为止,我们就完成了整个小软件的介绍。这个软件虽然不大,不过确实有很多的地方是第一次接触,扩展眼界了。
明天就要考试了,我的专业课还没着落。昨天苦逼的大四学长都离校了,明年的今天我们也要滚了。还有一年的时间,我相信我能成长的非常强大,为了毕业的那天能成为众多学弟学妹的榜样和偶像,我还要加油呀!
AppWidght全面学习之电量监控小部件的实现详解,布布扣,bubuko.com