1. 声明一个widget的布局文件(.xml格式)置于res/layout文件夹下
2. 声明一个widget的元数据文件(.xml格式)置于res/xml文件夹下。在这个元数据文件中至少要声明如下几个属性:minHeight,minWidth,updatePeriodMillis和initialLayout。其中initalLayout的属性值就是第一步中建立的widget布局文件
3. 在AndroidManifest文件中“注册”widget。注册的时候使用的是<receiver>标签。为了体现与一般的receiver的区别,在<receiver>中必须有如下子节点:
<receiver android:name=”.自己的AppWidgetProvider实现类”>
<intent-filter>
<action android:name=”android.appwidget.action.APPWIDGET_UPDATE”>
</intent-filter>
<meta-dataandroid:name=”android.appwidget.provider”
android:resource=”@xml/第2步中声明的widget元数据文件”
/>
</receiver>
4. 声明一个自己的AppWidgetProvider实现类。widget的新建,删除等动作都会触发相应的widget广播,这些广播需要通过继承AppWidgetProvider来接收。通过重写AppWidgetProvider的相应回调方法(onUpdate,onDelete,onEnable等),来处理事件发生时的行为。
注意:1. 因为AppWidgetProvider继承自BroadcastReceiver,那么在写相应的回调方法时必须要考虑ANR问题,不能超时。
2. 所有widget的UI操作相关操作(例如UI的更新,数据的输入等等)都是不能直接在widget本身上面进行的,必须借助于PendingIntent、RemoteViews和AppWidgetManager来辅助完成
3. 一个AppWidgetProvider可以对应多个widget。因为一个widget可以设定不同的大小,可以被反复添加到页面上,这样一个AppWidgetProvider就可能要在onUpdate等回调方法中去同时更新多个widget了
一个完整的例子:
本例提供一个widget,widget中有一个刷新按钮,每次点击时,都可以生成一个随机数字。点击widget的文本区域,可以打开一个activity,通过activity中的按钮也可以对widget中的内容进行刷新。
本例需要如下文件:
1)res/layout/simple_widget_layout.xml widget的布局文件
2) res/xml/simple_widet.xml widget的元数据文件
3)AndroidManifest.xml 注册相关activity,receiver,service
4)MyAppWidgetProvider.java 继承自AppWidgetProvider,监听widget的各种动作
5)RandomService.java 继承自Service,完成widget的刷新,以及与MainActivity的通信
6)MainActivity.java 继承自Activiy,提供一个与widget“绑定”组件,通过该Activity利用RandomService,也能进行widget的更新
1)res/layout/simple_widget_layout.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
android:padding="10dp"
android:background="@drawable/widget_background"
>
<LinearLayout
android:id="@+id/container"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_weight="1"
android:orientation="vertical" >
<TextView
android:id="@+id/tv_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="Random Number"
android:textAppearance="?android:attr/textAppearanceMedium" />
<TextView
android:id="@+id/tv_number"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:textStyle="bold"
android:textAppearance="?android:attr/textAppearanceMedium" />
</LinearLayout>
<ImageButton
android:id="@+id/btn_refresh"
android:layout_width="55dp"
android:layout_height="55dp"
android:layout_gravity="center_vertical"
android:background="@null"
android:contentDescription="@null"
android:src="@android:drawable/ic_menu_rotate"
/>
</LinearLayout>widget布局中有两个文本框,tv_title显示提示信息,tv_number显示随机数字,btn_refresh是一个刷新按钮,每次点击,都会生成一个随机数字显示在tv_number中。
2)res/xml/simple_widget.xml
<appwidget-provider
xmlns:android="http://schemas.android.com/apk/res/android"
android:minWidth="180dp"
android:minHeight="40dp"
android:updatePeriodMillis="86400000"
android:initialLayout="@layout/simple_widget_layout"
/>updatePeriodMillis的属性值官方建议不易短于30分钟。如果需求是频繁刷新widget,不要依赖于widget自身的这个时间刷新设定,而应该使用AlarmManager或者Intent等来完成刷新。3)AndroidManifest.xml (局部)
<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>
<receiver android:name=".MyAppWidgetProvider">
<intent-filter >
<action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/simple_widget"
/>
</receiver>
<service android:name="RandomService"></service>一个activity,一个receiver(AppWidgetProvider是BroadcastReceiver的继承者),一个service
4)MyAppWidgetProvider
public class MyAppWidgetProvider extends AppWidgetProvider{
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager,int[] appWidgetIds) {
Intent intent=new Intent(context,RandomService.class);
context.startService(intent);
}
}这里只监听onUpdate事件,该事件在widget被新建的时候会被回调。这里不做任何直接处理而是启动一个RandomService来进行数据的生成和widget的刷新
5)RandomService
public class RandomService extends Service{
public static final String ACTION="com.example.widgettest.ACTION";
public static int sRandomNumber;
public static int getRandomNumber(){
return sRandomNumber;
}
@Override
public void onCreate() {
super.onCreate();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
sRandomNumber=(int) (Math.random()*100);
RemoteViews rv=new RemoteViews(getPackageName(), R.layout.simple_widget_layout);
rv.setTextViewText(R.id.tv_number, String.valueOf(sRandomNumber));
PendingIntent pi1=PendingIntent.getService(this, 0, new Intent(this, RandomService.class), 0);
rv.setOnClickPendingIntent(R.id.btn_refresh, pi1);
PendingIntent pi2=PendingIntent.getActivity(this, 0, new Intent(this,MainActivity.class), 0);
rv.setOnClickPendingIntent(R.id.container, pi2);
AppWidgetManager manager=AppWidgetManager.getInstance(this);
ComponentName widget=new ComponentName(this, MyAppWidgetProvider.class);
manager.updateAppWidget(widget, rv);
Intent broad=new Intent(ACTION);
sendBroadcast(broad);
return START_NOT_STICKY;
}
@Override
public IBinder onBind(Intent intent) {
// TODO Auto-generated method stub
return null;
}
}这里onStartCommand一共做了如下几件事情:
i. 生成一个随机数
ii. 将widget包装成一个RemoteViews,利用RemoteViews提供的API设置组件应该显示的内容。这里的API设计非常讲究,签名是SetTextViewText而不是setTextView,意在明确RemoteViews是不能直接设置相关组件的内容的,只能提供相关相关组件应该显示的内容是什么
iii. 继续利用RemoteViews提供的API,使用PendingIntent为widget里面的相关组件绑定“监听器”
iv. 获得AppWidgetManager,利用AppWidgetManager来做widget的具体更新工作
v. AppWidgetManager更新完毕后,Service发送一个广播(这个广播的主要作用是在MainActivity中更新widget时会用到的)
通过pi1,为widget的刷新按钮绑定了一个单击响应。每次在widget中单击刷新按钮,RandomService都会被启动,并生成一个随机数,然后通过RemoteViews、AppWidgetManager将结果刷新到widget。pi2为widget单击面板绑定了一个单击响应,当单击widget面板的时候会启动MainActivity。
非常重要的是,任何针对RemoteView的改动(比如显示内容的变化)或者设置(比如为按钮添加单击事件)必须调用AppWidgetManager的 updateAppwidget方法才会让这些改动或者设置生效。
6)MainActivity
public class MainActivity extends Activity {
@ViewInject(R.id.tv_main_number)
private TextView tv;
@ViewInject(R.id.btn_main_generate)
private Button btn;
private BroadcastReceiver mReceiver=new BroadcastReceiver(){
@Override
public void onReceive(Context context, Intent intent) {
updateView();
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ViewUtils.inject(this);
updateView();
}
protected void updateView() {
tv.setText(String.valueOf(RandomService.getRandomNumber()));
}
@OnClick({R.id.btn_main_generate})
public void doClick(View view){
startService(new Intent(this,RandomService.class));
}
@Override
protected void onResume() {
super.onResume();
IntentFilter filter=new IntentFilter(RandomService.ACTION);
registerReceiver(mReceiver, filter);
}
@Override
protected void onPause() {
unregisterReceiver(mReceiver);
super.onPause();
}
}R.layout.activity_main的内容如下:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<Button
android:id="@+id/btn_main_generate"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Generate Number" >
</Button>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="Current Random Number"
android:textAppearance="?android:attr/textAppearanceLarge" />
<TextView
android:id="@+id/tv_main_number"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:textSize="55sp"
android:textStyle="bold" />
</LinearLayout>当MainActivity新建的时候,tv_main_number会从RandomService中拿数据显示出当前widget显示的数字。当点击btn_main_generate的时候,会启动RandomService,此时如果MainActivity保持在前台,那么它会收到RandomService更新完widget界面后发送的广播。在onReceive方法中,会去更新MainActivity的tv_main_number的内容,该数字显然与widget中的数字是一样的,都是RandomService的sRandomNumber。
本文详细介绍了如何在Android中创建并注册自定义小部件,包括布局文件、元数据文件的声明,以及在AndroidManifest文件中的注册步骤。此外,还提供了完整的例子,演示了如何实现小部件的交互与数据更新。
52

被折叠的 条评论
为什么被折叠?



