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

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;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.configure);
		ViewUtils.inject(this);
		//该Activity是被Intent启动的,
		//通过该Intent可以获得当前正要被创建的widget的Id
		mAppWidgetId = 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包装成一个RemoteViews
		RemoteViews 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的Intent
		rv.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都设置上了PendingIntent
		rv.setPendingIntentTemplate(R.id.list, pi);
		//利用AppWidgetManager来更新RemoteViews
		AppWidgetManager 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中的内容需要更新的时候,会回调这个方法
	 */
	@Override
	public 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中没有数据填充,那么就显示TextView
			rv.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会被回调
	 */
	@Override
	public 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被创建的时候,该方法会被回调
	 */
	@Override
	public void onEnabled(Context context) {
		//启动用来读取媒体库中读取图片和视频的服务
		context.startService(new Intent(context, MediaService.class));
	}
	/**
	 * 最后一个与该AppWidgetProvider绑定的widget被删除的时候,该方法会被回调
	 */
	@Override
	public 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实例
	@Override
	public 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);
		}

		@Override
		public 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系统的视频数据表获得对应的cursor
			if(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系统的图片数据表获得对应的cursor
				String[] 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);
			}
		}

		@Override
		public void onDataSetChanged() {
			mDataCursor.requery();
		}

		@Override
		public void onDestroy() {
			mDataCursor.close();
			mDataCursor=null;
		}

		@Override
		public int getCount() {
			return mDataCursor.getCount();
		}
		/**
		 * 与BaseAdapter中的getView方法作用是一样的
		 */
		@Override
		public 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方法返回时,等待加载画面会自动消失
		 */
		@Override
		public RemoteViews getLoadingView() {
			return null;
		}
		/**
		 * 该方法与BaseAdapter中的getViewTypeCount的意思是一样的
		 */
		@Override
		public int getViewTypeCount() {
			return 1;
		}

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

		@Override
		public boolean hasStableIds() {
			return false;
		}
	}

}

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

public class MediaService extends Service{
	private ContentObserver mMediaStoreObserver;
	
	@Override
	public void onCreate() {
		super.onCreate();
		mMediaStoreObserver=new ContentObserver(new Handler()) {
			
			@Override
			public void onChange(boolean selfChange) {
				Context _context=MediaService.this;
				AppWidgetManager manager=AppWidgetManager.getInstance(_context);
				ComponentName provider=new ComponentName(_context, ListAppWidget.class);
				//获得,所有利用AppWidgetProvider作为广播接收器的那些widget的id
				int[] 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);
	}
	@Override
	public void onDestroy() {
		super.onDestroy();
		getContentResolver().unregisterContentObserver(mMediaStoreObserver);
	}
	
	@Override
	public IBinder onBind(Intent intent) {
		return null;
	}

}

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




  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值