AppWidght全面学习之电量监控小部件的实现详解

前几天翻看之前下载的各种资料,无意中发现了一款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

时间: 2024-10-21 01:47:55

AppWidght全面学习之电量监控小部件的实现详解的相关文章

php学习之道:php中iconv函数 详解

iconv函数库能够完成各种字符集间的转换,是php编程中不可缺少的基础函数库. 用法如下: $string = "亲爱的朋友欢迎访问胡文芳的博客,希望给您带来一点点的帮助!"; iconv("utf8","gbk",$string)//将字符串string  编码由utf8转变成gbk: 扩展如下: echo $str= '你好,欢迎访问胡文芳的博客,该博客记录一个程序员的成长过程!'; echo ' '; echo iconv('GB2312

JavaWeb学习(三)----JSP内置对象详解

[声明] 欢迎转载,但请保留文章原始出处→_→ 生命壹号:http://www.cnblogs.com/smyhvae/ 文章来源:http://www.cnblogs.com/smyhvae/p/4065790.html 联系方式:[email protected] [系列]JSP学习系列文章:(持续更新) JavaWeb学习(一)----JSP简介及入门(含Tomcat的使用) JavaWeb学习(二)----JSP脚本元素.指令元素.动作元素 JavaWeb学习(三)----JSP内置对象

Ext.Net学习笔记22:Ext.Net Tree 用法详解

Ext.Net学习笔记22:Ext.Net Tree 用法详解 上面的图片是一个简单的树,使用Ext.Net来创建这样的树结构非常简单,代码如下: <ext:TreePanel runat="server"> <Root> <ext:Node Text="根节点" Expanded="true"> <Children> <ext:Node Text="节点1" Expand

使用系统存储过程来监控SQLServer进程和会话详解

 承接上文,本文讲述如何使用系统存储过程来监控系统. SQLServer同样也提供了一系列系统存储过程用于监控SQLServer,获取当前进程.会话.请求以及锁定的详细信息.本文将演示系统存储过程来实现这些监控. 情景: 有时候你会发现应用程序突然变得很慢,经常需要等待数据库响应,此时你需要快速查看是否请求被阻塞或者挂起. 准备工作: 在本文中,将使用以下存储过程来获取当前进程的信息: Sp_who Sp_who2 步骤: 1.  打开SSMS连到SQLServer实例并打开新查询窗口. 2

Ext.Net学习笔记07:Ext.Net DirectMethods用法详解

前面两篇内容中,我们看到了DirectEvents方便调用服务器端方法.DirectEvents调用WebService方法的使用方法,今天我们来看看DirectMethods,这家伙可比DirectEvents更加灵活了,它可以像调用JS方法一样来异步调用服务器端的方法. 使用DirectMethods在JS中调用C#方法 我承认,这个标题有点噱头,其实应该是通过DirectMethods,在JS中通过异步调用的方式执行服务器端的方法. 来看一个例子吧: [DirectMethod] publ

微信小程序 授权登录详解(附完整源码)

一.前言 由于微信官方修改了 getUserInfo 接口,所以现在无法实现一进入微信小程序就弹出授权窗口,只能通过 button 去触发. 官方连接:https://developers.weixin.qq.com/community/develop/doc/0000a26e1aca6012e896a517556c01 二.实现思路 自己写一个微信授权登录页面让用户实现点击的功能,也就是实现了通过 button 组件去触发 getUserInof 接口.在用户进入微信小程序的时候,判断用户是否

微信小程序的配置详解

1.配置详解: 使用app.json文件来对微信小程序进行全局配置,决定页面文件的路径.窗口表现.设置网络超时时间.设置多 tab 等. 1>pages 接受一个数组,每一项都是字符串,来指定小程序由哪些页面组成.每一项代表对应页面的[路径+文件名]信息,数组的第一项代表小程序的初始页面.小程序中新增/减少页面,都需要对 pages 数组进行修改. 文件名不需要写文件后缀,因为框架会自动去寻找路径.json,.js,.wxml,.wxss的四个文件进行整合. 2>window 用于设置小程序的

hadoop学习WordCount+Block+Split+Shuffle+Map+Reduce技术详解

转自:http://blog.csdn.net/yczws1/article/details/21899007 纯干货:通过WourdCount程序示例:详细讲解MapReduce之Block+Split+Shuffle+Map+Reduce的区别及数据处理流程. Shuffle过程是MapReduce的核心,集中了MR过程最关键的部分.要想了解MR,Shuffle是必须要理解的.了解Shuffle的过程,更有利于我们在对MapReduce job性能调优的工作有帮助,以及进一步加深我们对MR内

常用 JavaScript 小技巧及原理详解

善于利用JS中的小知识的利用,可以很简洁的编写代码 1. 使用!!模拟Boolean()函数 原理:逻辑非操作一个数据对象时,会先将数据对象转换为布尔值,然后取反,两个!!重复取反,就实现了转换为布尔值的效果. 2. 使用一元加(+)模拟Number()函数 原理:对非数值类型的数据使用一元加(+),会起到与Number()函数相同的效果. null转换为0 undefined转换为NaN false转换为0,true转换为1 对于字符串: 空字串转换为0 含有数字或者浮点数或者十六进制格式的数