Collection Widget实现举例
App Widget可以使用RemoteViewsService来展示集合数据,下面在Android App Widget实现举例的基础上,创建一个电影海报列表的Widget,效果如图1所示。
Contents
1. 创建布局
App Widget可用的Collection View有一下几种:
- ListView
- GridView
- StackView
- AdapterViewFlipper
这里使用StackView,创建布局widget_stack.xml如下:
<?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/widget_stack_container" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent" android:padding="@dimen/widget_margin" > <StackView android:id="@+id/widget_stack" android:layout_width="match_parent" android:layout_height="match_parent" tools:listitem="@layout/widget_stack_item" > </StackView> <TextView android:id="@+id/widget_empty" android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center" android:fontFamily="sans-serif-condensed" android:textAppearance="?android:textAppearanceLarge" android:text="@string/empty_list"/> </FrameLayout>
【完整代码】
使用StackView展示一组海报,这里的widget_empty用于在没有数据时显示提示信息。海报布局widget_stack_item.xml如下,只有一个ImageView用于显示海报:
<?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/widget_stack_item" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="wrap_content" android:padding="@dimen/widget_margin" > <ImageView android:id="@+id/widget_movie_poster" android:layout_width="match_parent" android:layout_height="wrap_content" android:scaleType="centerCrop" android:adjustViewBounds="true" android:src="@drawable/placeholder_poster_white" tools:src="@drawable/placeholder_poster" /> </FrameLayout>
【完整代码】
2. 创建AppWidgetProviderInfo
和Android App Widget实现举例的流程类似,在/res/xml/下创建stack_widget_info.xml如这里所示。
3. 创建AppWidgetProvider
创建StackWidgetProvider继承AppWidgetProvider,在onUpdate() 中,为RemotesViews设置RemoteAdapter:
public class StackWidgetProvider extends AppWidgetProvider { public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { for (int appWidgetId : appWidgetIds) { RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.widget_stack); Intent intent = new Intent(context, StackWidgetRemoteViewsService.class); intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId); intent.setData(Uri.parse(intent.toUri(Intent.URI_INTENT_SCHEME))); views.setRemoteAdapter(R.id.widget_stack, intent); Intent clickIntentTemplate = new Intent(context, MovieActivity.class); PendingIntent clickPendingIntentTemplate = TaskStackBuilder.create(context) .addNextIntentWithParentStack(clickIntentTemplate) .getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT); views.setPendingIntentTemplate(R.id.widget_stack, clickPendingIntentTemplate); views.setEmptyView(R.id.widget_stack, R.id.widget_empty); appWidgetManager.updateAppWidget(appWidgetId, views); } } }
【完整代码】
这里的intent 包含了下面要介绍的RemoteViewsService,即StackWidgetRemoteViewsService ,通过setRemoteAdapter() 配置到RemoteViews。setEmptyView() 数据为空时显示的元素,即R.id.widget_empty 。clickIntentTemplate 用于配置Collection Widget中每一个元素的点击事件,点击将会打开显示对应电影海报的MovieActivity,但在这里还不知道是那个电影被点击了,这里只是配置了一个Intent Template,详细信息在创建RemoteViews时追加。
4. 创建RemoteViewsService
Remote Adapter通过RemoteViewsService来请求RemoteViews。RemoteViewsService通过onGetViewFactory() 返回一个RemoteViewsFactory,它类似一个ListView的Adapter:
public class StackWidgetRemoteViewsService extends RemoteViewsService { private static final String LOG_TAG = StackWidgetRemoteViewsService.class.getSimpleName(); @Override public RemoteViewsFactory onGetViewFactory(Intent intent) { return new RemoteViewsFactory() { private List<Movie> mMovies; @Override public void onCreate() { } @Override public void onDataSetChanged() { MovieService movieService = App.getRestClient().getMovieService(); Call<MovieResponse> call = movieService.getMovies(MovieService.SORT_BY_POPULARITY_DESC, 1); try { MovieResponse response = call.execute().body(); List<Movie> movies = response.getMovies(); mMovies = movies; } catch (IOException e) { Log.e(LOG_TAG, "ERROR: ", e); } } @Override public void onDestroy() { } @Override public int getCount() { return mMovies == null ? 0 : mMovies.size(); } @Override public RemoteViews getViewAt(int position) { RemoteViews views = new RemoteViews(getPackageName(), R.layout.widget_stack_item); String posterUrl = ImageUtility .getImageUrl(mMovies.get(position).getPosterPath()); try { Bitmap poster = Picasso.with(StackWidgetRemoteViewsService.this) .load(posterUrl) .get(); views.setImageViewBitmap(R.id.widget_movie_poster, poster); }catch (IOException e) { Log.e(LOG_TAG, "ERROR: ", e); } final Intent fillInIntent = new Intent(); fillInIntent.putExtra(MovieActivity.EXTRA_URL, posterUrl); views.setOnClickFillInIntent(R.id.widget_stack_item, fillInIntent); return views; } @Override public RemoteViews getLoadingView() { return new RemoteViews(getPackageName(), R.layout.widget_stack_item); } @Override public int getViewTypeCount() { return 1; } @Override public long getItemId(int position) { if (position < mMovies.size()) { return mMovies.get(position).getId(); } return position; } @Override public boolean hasStableIds() { return true; } }; } }
【完整代码】
大多数方法都类似用于如ListView之类的的Adapter。注意这里getViewAt() 返回的是RemoteViews。在getViewAt() 里面,我们已经知道了当前RemoteViews所对应的是哪一部电影,通过fillInIntent.putExtra(MovieActivity.EXTRA_URL, posterUrl) 把海报图片链接放入Intent,结合之前在StackWidgetProvider加入的Intent Template,当点击App Widget时,会打开MovieActivity,同时MovieActivity会收到带有海报图片链接的Intent。MovieActivity的代码见这里。
5. 在Manifest中声明App Widget
最后不要忘了在Manifest中声明App Widget:
<receiver android:name=".widget.StackWidgetProvider" android:label="@string/stack_widget_name"> <intent-filter> <action android:name="android.appwidget.action.APPWIDGET_UPDATE" /> </intent-filter> <meta-data android:name="android.appwidget.provider" android:resource="@xml/stack_widget_info" /> </receiver> <service android:name=".widget.StackWidgetRemoteViewsService" android:exported="false" android:permission="android.permission.BIND_REMOTEVIEWS" />
除了AppWidgetProvider的声明,还要加入StackWidgetRemoteViewsService的声明,带有BIND_REMOTEVIEWS权限。
6. 完整代码
完整代码可以参考这里。