个人项目二: 中山大学智慧健康服务平台应用开发
实验代码:传送门:https://github.com/dick20/Android
在第七周任务的基础上,实现静态广播、动态广播两种改变widget内容的方法。
下图为Widget初始情况:
下图为点击widget可以启动应用,并在widget随机推荐一个食品:
下图为点击widget跳转到所推荐食品的详情界面:
点击收藏图标,widget相应更新:
根据实验要求来修改new_app_widget.xml,添加一个ImageView与一个TextView,利用Realative布局控制好边距排版。
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="@dimen/widget_margin">
<ImageView
android:id="@+id/appwidget_star"
android:layout_width="30dp"
android:layout_height="30dp"
android:src="@mipmap/full_star"
android:scaleType="fitXY"
/>
<TextView
android:id="@+id/appwidget_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="35dp"
android:layout_alignBottom="@id/appwidget_star"
android:contentDescription="当前没有任何信息"
android:text="当前没有任何信息"
android:textColor="#ffffff"
android:textSize="15sp"
android:textStyle="bold|italic" />
RelativeLayout>
修改new_app_widget_info.xml文件,这是对于widget的布局文件,调整它的最小宽度与最小高度,设置图片为full_star,初始布局为之前设置的new_app_widget.
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:initialKeyguardLayout="@layout/new_app_widget"
android:initialLayout="@layout/new_app_widget"
android:minHeight="50dp"
android:minWidth="300dp"
android:previewImage="@mipmap/full_star"
android:resizeMode="horizontal|vertical"
android:updatePeriodMillis="86400000"
android:widgetCategory="home_screen|keyguard">appwidget-provider>
重写 onUpdate 方法,为 Widget 添加事件,使得点击能够启动应用。这里用到的是上周使用过的PeddingIntent,该intent不马上执行,而是等待事件后执行。
除此之外,对于widget的更新需要用到RemoteView,为其设置监听事件,当点击后会执行peddingIntent。而appWidgetManager调用updateAppWidget来通知更新widget。
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
RemoteViews updateView = new RemoteViews(context.getPackageName(), R.layout.new_app_widget);//实例化RemoteView,其对应相应的Widget布局
Intent i = new Intent(context, FoodList.class);
PendingIntent pi = PendingIntent.getActivity(context, 0, i, PendingIntent.FLAG_UPDATE_CURRENT);
updateView.setOnClickPendingIntent(R.id.appwidget_star, pi); //设置点击事件
ComponentName me = new ComponentName(context, NewAppWidget.class);
appWidgetManager.updateAppWidget(me, updateView);
}
与上周作业一样需要先在 AndroidMainfest.xml 注册,而接收器不再写在一个新的接收器类,而是写在NewAppWidget的重构函数onRecieve中
接收器中要对于发送广播的action做判断,是否接受该广播。然后像上周通知一样更新RemoteViews即可,将数据传入到详情页面,包括食物的名字等等。只不过,这里是用appWidgetManager通知更新,而通知是用NotificationManager来更新
还要为remoteviews添加点击的监听事件,当点击widget时候跳转。
@Override
public void onReceive(Context context, Intent intent ){
super.onReceive(context, intent);
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
Log.i("receive","widget1");
if(intent.getAction().equals("android.appwidget.action.APPWIDGET_UPDATE")){
Bundle bundle = intent.getExtras();
RemoteViews remoteViews=new RemoteViews(context.getPackageName(),R.layout.new_app_widget);
if (bundle.getSerializable("collect") != null) {
Log.i("receive",((MyCollection)bundle.getSerializable("collect")).getName());
remoteViews.setTextViewText(R.id.appwidget_text, "今日推荐 " + ((MyCollection) bundle.getSerializable("collect")).getName());
}
//跳回主页面
Intent intent2 = new Intent(context,Details.class);
Bundle bundle2 = new Bundle();
String s[] = new String [5];
if (bundle.getSerializable("collect") != null) {
s[0] = ((MyCollection) bundle.getSerializable("collect")).getName();
s[1] = ((MyCollection) bundle.getSerializable("collect")).getMaterial();
s[2] = ((MyCollection) bundle.getSerializable("collect")).getType();
s[3] = ((MyCollection) bundle.getSerializable("collect")).getContent();
s[4] = ((MyCollection) bundle.getSerializable("collect")).getIs_star() ? "yes" : "no";
}
bundle2.putStringArray("msg",s);
intent2.putExtras(bundle2);
PendingIntent pendingIntent=PendingIntent.getActivity(context,0,intent2,PendingIntent.FLAG_UPDATE_CURRENT);
remoteViews.setOnClickPendingIntent(R.id.appwidget_text,pendingIntent);
ComponentName me=new ComponentName(context,NewAppWidget.class);
appWidgetManager.updateAppWidget(me, remoteViews);
}
}
而发送广播部分,与上次发送一样,只需要改变action的内容即可。
其中bundles的内容是随机生成的食物对象。
Intent widgetBroadcast=new Intent("android.appwidget.action.APPWIDGET_UPDATE");//widget广播
widgetBroadcast.putExtras(bundles);
sendBroadcast(widgetBroadcast);
动态广播的接受者与上次一样,写在DynamicReceiver类中,对不同的action做一下判断。
remoteViews.setTextViewText函数来修改对应id的widget文字内容。而跳回收藏列表,则也是用PeddingIntent来操作,注意加入点击事件的监听器。最后appWidgetManager更新widget。
else if(intent.getAction().equals("com.example.asus.health.WidgetDynamicFilter")){
Bundle bundle = intent.getExtras();
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
RemoteViews remoteViews=new RemoteViews(context.getPackageName(),R.layout.new_app_widget);
if (bundle.getSerializable("collect") != null) {
remoteViews.setTextViewText(R.id.appwidget_text, "已收藏 " + ((MyCollection) bundle.getSerializable("collect")).getName());
}
//跳回收藏夹
Intent intent2 = new Intent(context,FoodList.class);
Bundle bundle2 = new Bundle();
bundle2.putString("tag","collect");
intent2.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
intent2.putExtras(bundle2);
PendingIntent pendingIntent=PendingIntent.getActivity(context,0,intent2,PendingIntent.FLAG_UPDATE_CURRENT);
remoteViews.setOnClickPendingIntent(R.id.appwidget_text,pendingIntent);
ComponentName me=new ComponentName(context,NewAppWidget.class);
appWidgetManager.updateAppWidget(me, remoteViews);
}
发送广播也是在点击收藏按钮后进行发送,记得注册广播,也要在onDestroy中删除。这里不再详述,与上周的发送注册删除一致。
明明是一样的写法,为什么我新在widget类中的onRecieve函数中收不到呢?于是我在这个函数中设置了两个log消息,发现根本没有进入到这个函数。于是,我开始怀疑注册过程的错误。发现注册的action与发送的action不一样,所以收不到广播。
<receiver android:name=".NewAppWidget">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
<action android:name="com.example.asus.health.WidgetDynamicFilter" />
intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/new_app_widget_info" />
receiver>
这里要注意注册的action要与我发送广播的action一致,否则收不到广播。
Intent widgetBroadcast=new Intent("android.appwidget.action.APPWIDGET_UPDATE");//widget广播
widgetBroadcast.putExtras(bundles);
sendBroadcast(widgetBroadcast);
本次实验的内容是相对比较简单的,因为在上周已经解决了所有动态广播与静态广播的bug,只需注意widget的一些函数使用即可。接受器中的逻辑不需要改动,只是改变widget使用了PeddingIntent。
而关于widget的部分,AppWidgetProvider 继承自 BroadcastReceiver,它能接收 widget 相关的广播,例如 widget 的更新、删除、开启和禁用等。所以,我的NewAppWidget类继承AppWidgetProvider ,在里面可以重构多个函数来控制widget的一些操作,这次实验我只使用了onRecieve()以及onUpdate()。显然我们还可以对于它的删除开启来设置一些交互事件,完善widget的功能。例如,在创建widget的时候弹出使用的简要说明,在删除widget的时候弹出消息框询问用户是否确定删除,等等。
通过实验了解到了关于广播的第二个作用,可以用于生成app的widget。而动态更新widget则需要remoteviews这一部件的参与,使得app的内容更加完善。RemoteViews就是远程的View,也就是可以运行在其他进程中的View。RemoteViews常用在通知和桌面小组件中。我们可以利用它来更新不同活动中的view内容,不仅仅局限于widget。