widget中使用Listview (下)代码实现

来源:互联网 发布:php培训怎么样 编辑:程序博客网 时间:2024/05/29 12:46

1. widget的布局文件

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:orientation="vertical" >    <TextView         android:id="@+id/tv_list_widget_title"        android:layout_width="match_parent"        android:layout_height="45dp"        android:gravity="center"        android:textAppearance="?android:attr/textAppearanceMedium"        />    <!-- 通过代码控制,在ListView为空时,可以显示TextView -->    <FrameLayout         android:layout_width="match_parent"        android:layout_height="match_parent"        >        <ListView             android:id="@+id/list"            android:layout_width="match_parent"            android:layout_height="match_parent"            ></ListView>        <TextView             android:id="@+id/list_empty"            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:layout_gravity="center"            android:text="No Items Available"            android:textSize="22sp"            />    </FrameLayout></LinearLayout>

在widget中会使用一个ListView。所以,还需要提供Listview的Item布局文件

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="match_parent"    android:layout_height="?android:attr/listPreferredItemHeight"    android:orientation="vertical"     android:id="@+id/ll_widget_item"    android:paddingLeft="10dp"    android:gravity="center_vertical"    >    <TextView         android:id="@+id/line1"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        />    <TextView         android:id="@+id/line2"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        /></LinearLayout>

需要注意的是,虽然item的布局文件与android.R.layout.simple_list_item_2很相似,但是如果ListView是用在widget中,最好不要使用系统提供的那些布局文件,因为在形成widget的视图时要先将其包装为RemoteViews,而RemoteViews中支持的视图组件是非常有限的,不能保证系统写的布局文件中用到的视图组件都可以被支持。

2. 提供一个元数据文件,里面声明该widget会需要一个配置界面(Configure Activity)

<appwidget-provider  xmlns:android="http://schemas.android.com/apk/res/android"android:minWidth="110dp"android:minHeight="110dp"android:updatePeriodMillis="86400000"android:initialLayout="@layout/list_widget_layout"android:configure="com.example.widgettest.ListWidgetConfigureActivity"android:resizeMode="horizontal|vertical"   />

configure属性声明了widget需要一个ConfigureActivity。在把widget拖放到桌面松手时的一刹那,会优先显示作为configure activity的ListWidgetConfigureActivity,当ListWidgetConfigureActivity以setResult(result_code,data)返回时,widget才会出现在界面上。

3. 修改AndroidManifest文件,声明为widget服务的AppWidgetProvider,声明作为widget的configure activity的ListWidgetConfigureActivity。同时,为widget中的ListView提供数据时,是不能使用Adapter的,需要利用RemoteViewsService,并提供一个RemoteViewsFactory实例来填充ListView的数据。

<application        android:allowBackup="true"        android:icon="@drawable/ic_launcher"        android:label="@string/app_name"        android:theme="@style/AppTheme" >        <activity            android:name="com.example.widgettest.MainActivity"            android:label="@string/app_name" >            <intent-filter>                <action android:name="android.intent.action.MAIN" />                <category android:name="android.intent.category.LAUNCHER" />            </intent-filter>        </activity>        <activity             android:name=".ListWidgetConfigureActivity"            >            <intent-filter>                <action android:name="android.appwidget.action.APPWIDGET_CONFIGURE"/>            </intent-filter>                    </activity>        <receiver             android:name=".ListAppWidget"            >            <intent-filter>                <action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>            </intent-filter><meta-data android:name="android.appwidget.provider"     android:resource="@xml/list_appwidget"    />                    </receiver>        <service             android:name=".ListWidgetService"            android:permission="android.permission.BIND_REMOTEVIEWS"            ></service>        <service            android:name=".MediaService"             ></service>    </application>

这里特别需要注意的是,凡是为widget提供帮助的receiver,activity和service,都需要添加一些额外的说明。

receiver标签中需要添加<intent-filter>和<meta-data>标签。名字是ListAppWidget的AppWidgetProvider类为该widget(s)服务。一个widget可以被反复拖拽到界面上,每拖拽一个在界面上显示,就意味着AppWidgetProvider需要多一个widget进行管理和通知。因此,一般情况下,AppWidgetProvider内的代码都需要以widget数组的形式来设计代码。

作为configure activity的activity中,必须要设置

           <intent-filter>                <action android:name="android.appwidget.action.APPWIDGET_CONFIGURE"/>            </intent-filter>
否则configure activity不会显示,app widget也不能创建

作为RemoteViewsService,service标签里面必须要声明 BIND_REMOTEVIEWS许可

另外,例子中还声明了一个<service>,这个service是用来读取手机媒体库中的图片和视频的

4. 新建ListWidgetConfigureActivity

首先是该Activity的布局文件

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="match_parent"    android:layout_height="match_parent" >    <TextView         android:id="@+id/tv_configure_title"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:textAppearance="?android:attr/textAppearanceLarge"        android:text="Select Media Type:"        />    <RadioGroup         android:id="@+id/rg_configure_mode"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:layout_below="@+id/tv_configure_title"        android:orientation="vertical"        >                <RadioButton             android:id="@+id/rb_mode_image"            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:text="IMAGE"            />        <RadioButton             android:id="@+id/rb_mode_media"            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:text="VIDEOS"            />    </RadioGroup>    <Button         android:id="@+id/btn_configure"        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:text="Add Widget"        android:layout_alignParentBottom="true"        /></RelativeLayout>

然后是Activity的代码:

public class ListWidgetConfigureActivity extends Activity {private int mAppWidgetId;@ViewInject(R.id.rg_configure_mode)private RadioGroup mModeGroup;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.configure);ViewUtils.inject(this);//该Activity是被Intent启动的,//通过该Intent可以获得当前正要被创建的widget的IdmAppWidgetId = getIntent().getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID,AppWidgetManager.INVALID_APPWIDGET_ID);//如果用户在这个节目没有进行任何选择就直接按back退出该Activity的话//通过setResult(RESULT_CANCELED),widget就不会被创建setResult(RESULT_CANCELED);}@OnClick({ R.id.btn_configure })public void onAddClick(View v) {//通过当前widget的Id//为当前要被创建的widget设定一个Preference文件保存设置(显示图片还是视频)SharedPreferences sp = getSharedPreferences(String.valueOf(mAppWidgetId), MODE_PRIVATE);Editor editor = sp.edit();//将widget包装成一个RemoteViewsRemoteViews rv = new RemoteViews(getPackageName(),R.layout.list_widget_layout);switch (mModeGroup.getCheckedRadioButtonId()) {case R.id.rb_mode_image:editor.putString(ListWidgetService.KEY_MODE,ListWidgetService.MODE_IMAGE);editor.commit();rv.setTextViewText(R.id.tv_configure_title, "Image Collection");break;case R.id.rb_mode_media:editor.putString(ListWidgetService.KEY_MODE,ListWidgetService.MODE_MEDIA);editor.commit();rv.setTextViewText(R.id.tv_configure_title, "Media Collection");break;default:Toast.makeText(this, "please select a Media Type", 1).show();return;}Intent intent=new Intent(this, ListWidgetService.class);intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mAppWidgetId);intent.setData(Uri.parse(intent.toUri(Intent.URI_INTENT_SCHEME)));//这里就是为widget中的ListView设置Adapter//需要提供被绑定的widget,该widget中需要被填充的ListView和为这个ListView服务的//可以启动RemoteViewsService的Intentrv.setRemoteAdapter(mAppWidgetId, R.id.list, intent);//如果ListView的数据为空,用什么视图组件来代替显示rv.setEmptyView(R.id.list, R.id.list_empty);//设置一个PendingItent,在widget的ListView中进行点击的时候触发Intent viewIntent=new Intent(Intent.ACTION_VIEW);PendingIntent pi=PendingIntent.getActivity(this, 0, viewIntent, 0);//setPendingIntentTemplate与setOnItemClickListener是类似的//这里使用setPendingIntentTemplate直接为ListView中的每一个Item都设置上了PendingIntentrv.setPendingIntentTemplate(R.id.list, pi);//利用AppWidgetManager来更新RemoteViewsAppWidgetManager manager=AppWidgetManager.getInstance(this);manager.updateAppWidget(mAppWidgetId, rv);//一切都设置完毕后,调用setResult(RESULT_OK,data);并把自己关闭//widget随即会出现在桌面上Intent data=new Intent();data.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mAppWidgetId);setResult(RESULT_OK,data);finish();}}

5. AppWidgetProvider用来接收widget的相关广播

public class ListAppWidget extends AppWidgetProvider {/** * widget中的内容需要更新的时候,会回调这个方法 */@Overridepublic void onUpdate(Context context, AppWidgetManager appWidgetManager,int[] appWidgetIds) {AppWidgetManager manager = AppWidgetManager.getInstance(context);//因为同一个widget可以被反复拖拽到页面,每拖拽一次就生成一个新的widget,会有一个新的id//所以,AppWidgetProvider大多数情况下都应该是面向数组进行操作for (int i = 0; i < appWidgetIds.length; i++) {Intent intent = new Intent(context, ListWidgetService.class);intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID,appWidgetIds[i]);intent.setData(Uri.parse(intent.toUri(Intent.URI_INTENT_SCHEME)));RemoteViews rv = new RemoteViews(context.getPackageName(),R.layout.list_widget_layout);//获取以widget的Id为名称的SharedPreferences文件SharedPreferences sp = context.getSharedPreferences(String.valueOf(appWidgetIds[i]), context.MODE_PRIVATE);String mode = sp.getString(ListWidgetService.KEY_MODE,ListWidgetService.MODE_IMAGE);if (ListWidgetService.MODE_MEDIA.equals(mode)) {rv.setTextViewText(R.id.tv_list_widget_title, "视频列表");} else {rv.setTextViewText(R.id.tv_list_widget_title, "图片列表");}//每一个widget都可以从ListWidgetService中获得数据rv.setRemoteAdapter(appWidgetIds[i], R.id.list, intent);//如果ListView中没有数据填充,那么就显示TextViewrv.setEmptyView(R.id.list, R.id.list_empty);Intent viewIntent = new Intent(Intent.ACTION_VIEW);PendingIntent pi = PendingIntent.getActivity(context, 0,viewIntent, 0);rv.setPendingIntentTemplate(R.id.list, pi);manager.updateAppWidget(appWidgetIds[i], rv);}}/** * 与该AppWidgetProvider绑定的若干个widget中,只要有一个widget被删除了 * AppWidgetProvider的 onDeleted会被回调 */@Overridepublic void onDeleted(Context context, int[] appWidgetIds) {for(int i=0;i<appWidgetIds.length;i++){SharedPreferences sp=context.getSharedPreferences(String.valueOf(appWidgetIds[i]), context.MODE_PRIVATE);Editor editor=sp.edit();editor.clear();editor.commit();}}/** * 第一个与该AppWidgetProvider绑定的widget被创建的时候,该方法会被回调 */@Overridepublic void onEnabled(Context context) {//启动用来读取媒体库中读取图片和视频的服务context.startService(new Intent(context, MediaService.class));}/** * 最后一个与该AppWidgetProvider绑定的widget被删除的时候,该方法会被回调 */@Overridepublic void onDisabled(Context context) {//停止用来读取媒体库中读取图片和视频的服务context.stopService(new Intent(context, MediaService.class));}}

6. 为widget中的ListView提供数据填充的RemoteViewsService

public class ListWidgetService extends RemoteViewsService{public static final String KEY_MODE="mode";public static final String MODE_IMAGE="image";public static final String MODE_MEDIA="media";//实现RemoteViewService时要实现的抽象方法,获得一个RemoteViewsFactory实例@Overridepublic RemoteViewsFactory onGetViewFactory(Intent intent) {return new ListRemoteViewFactory(this,intent);}/** * 内部类,实现RemoteViewsFatory接口 * 这个RemoteViewsFactory就相当于BaseAdatper  */private class ListRemoteViewFactory implements RemoteViewsFactory{private Context mContext;private int mAppWidgetId;private Cursor mDataCursor;public ListRemoteViewFactory(Context context,Intent intent) {mContext=context;mAppWidgetId=intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID);}@Overridepublic void onCreate() {//获得一个widget对应的SharedPerferences文件SharedPreferences sp=mContext.getSharedPreferences(String.valueOf(mAppWidgetId), Context.MODE_PRIVATE);//获得该widget的Mode类型String mode=sp.getString(KEY_MODE, MODE_IMAGE);//如果mode是视频类型,则去query系统的视频数据表获得对应的cursorif(MODE_MEDIA.equals(mode)){String[] projection={MediaStore.Video.Media.TITLE,MediaStore.Video.Media.DATE_TAKEN,MediaStore.Video.Media.DATA};mDataCursor = MediaStore.Images.Media.query(getContentResolver(), MediaStore.Video.Media.EXTERNAL_CONTENT_URI, projection);}else{//如果mode是图片类型,则去query系统的图片数据表获得对应的cursorString[] projection={MediaStore.Images.Media.TITLE,MediaStore.Images.Media.DATE_TAKEN,MediaStore.Images.Media.DATA};mDataCursor = MediaStore.Images.Media.query(getContentResolver(), MediaStore.Images.Media.EXTERNAL_CONTENT_URI, projection);}}@Overridepublic void onDataSetChanged() {mDataCursor.requery();}@Overridepublic void onDestroy() {mDataCursor.close();mDataCursor=null;}@Overridepublic int getCount() {return mDataCursor.getCount();}/** * 与BaseAdapter中的getView方法作用是一样的 */@Overridepublic RemoteViews getViewAt(int position) {mDataCursor.moveToPosition(position);//widget中的ListView中的Item也要包装为RemoteView//该代码与BaseAdapter的getView中获取Item布局的代码类似RemoteViews rv=new RemoteViews(getPackageName(),R.layout.list_widget_item_layout);rv.setTextViewText(R.id.line1, mDataCursor.getString(0));//DateFormat是一个安卓提供的工具类,可以省去用SimpleDateFormat去parse的步骤了rv.setTextViewText(R.id.line2, DateFormat.format("MM/dd/yyyy", mDataCursor.getLong(1)));SharedPreferences sp=mContext.getSharedPreferences(String.valueOf(mAppWidgetId), MODE_PRIVATE);String mode=sp.getString(KEY_MODE, MODE_IMAGE);String type;if(MODE_MEDIA.equals(mode)){type="video/*";}else{type="image/*";}Uri data=Uri.fromFile(new File(mDataCursor.getString(2)));Intent intent=new Intent();intent.setDataAndType(data, type);//setOnClickFillInIntent与 setPendingIntentTemplate可以联合使用//当在widgets中使用集合(比如说ListView, StackView等等),为一个个单独的Item设置PendingIntents是非常麻烦的,//通过设置PendingIntentsTemplate可以一次性给集合里面的Item设置相同的点击时动作,//如果希望某一个Item点击后的动作有所不同,就为这个Item单独使用fillInIntent来设置它点击后执行的Intent。rv.setOnClickFillInIntent(R.id.ll_widget_item, intent);return rv;}/** * 在getView方法执行获得View的过程中,该方法的返回值会作为等待加载画面一直显示 * 当getView方法返回时,等待加载画面会自动消失 */@Overridepublic RemoteViews getLoadingView() {return null;}/** * 该方法与BaseAdapter中的getViewTypeCount的意思是一样的 */@Overridepublic int getViewTypeCount() {return 1;}@Overridepublic long getItemId(int position) {return position;}@Overridepublic boolean hasStableIds() {return false;}}}

7. 最后是一个服务类型的service MediaService。它的启动和中止是在AppWidgetProvider的onEnable和onDisable中进行的。这个服务的作用就是当你添加或者删除了图片或视频的时候,这个MediaService上有一个ContentObserver,会即时把这个变化反应到widget的Listview上。及可以实时保持widget的ListView中的内容与手机媒体库中的内容保持一致

public class MediaService extends Service{private ContentObserver mMediaStoreObserver;@Overridepublic void onCreate() {super.onCreate();mMediaStoreObserver=new ContentObserver(new Handler()) {@Overridepublic void onChange(boolean selfChange) {Context _context=MediaService.this;AppWidgetManager manager=AppWidgetManager.getInstance(_context);ComponentName provider=new ComponentName(_context, ListAppWidget.class);//获得,所有利用AppWidgetProvider作为广播接收器的那些widget的idint[] ids=manager.getAppWidgetIds(provider);//这样当有数据发生变化时,这种变化会反映到所有桌面上的widget的ListView列表中manager.notifyAppWidgetViewDataChanged(ids, R.id.list);}};getContentResolver().registerContentObserver(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, true, mMediaStoreObserver);getContentResolver().registerContentObserver(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, true, mMediaStoreObserver);}@Overridepublic void onDestroy() {super.onDestroy();getContentResolver().unregisterContentObserver(mMediaStoreObserver);}@Overridepublic IBinder onBind(Intent intent) {return null;}}

所有的代码和配置文件都书写完毕。




0 0
原创粉丝点击