Widget
一种桌面组件
Widget开发有如下几个部分
1.Widget布局的XML文件
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="@drawable/dialog"
>
<TextView
android:text="请点击输入心情"
android:id="@+id/TextView01"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:textColor="#FFFFFF"
android:textSize="24dip"
android:clickable="true"
>
</TextView>
<TextView
android:id="@+id/TextView02"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:textColor="#FFFFFF"
android:textSize="24dip"
>
</TextView>
</LinearLayout>
2.Widget描述的XML文件
<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:minWidth="280dip"
android:minHeight="60dip"
android:initialLayout="@layout/wmain">
</appwidget-provider>
<!-- android:initialLayout 给出桌面布局文件 -->
<!-- android:updatePeriodMillis 给出更新的时延 -->
3.Widget的内容更新需要接受广播intent
需要继承 AppWidgetProvider 的 MyWidgetProvider
完成后 最后在AndroidManifest.xml中完成对 AppWidgetProvider的注册
注册成广播接收器
<receiver
android:name=".MyWidgetProvider"
android:label="时间心情"
android:icon="@drawable/heart"
>
<meta-data android:name="android.appwidget.provider" <!--名称不能变--
android:resource="@xml/appwidgetprovder"></meta-data>
<intent-filter>
<action android:name="wyf.action.time_upadte"></action>
<action android:name="wyf.action.update_xq"></action>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" /><!--这个action不能少-->
</intent-filter>
</receiver>
public class MyWidgetProvider extends AppWidgetProvider
{
RemoteViews rv;
public MyWidgetProvider()
{
Log.d("MyWidgetProvider","============");
}
@Override
public void onDisabled(Context context)
{//若为最后一个实例
//删除时停止后台定时更新Widget时间的Service
context.stopService(new Intent(context,TimeService.class));
}
@Override
public void onEnabled (Context context)
{//若为第一个实例则打开服务
//启动后台定时更新时间的Service
context.startService(new Intent(context,TimeService.class));
}
//onUpdate为组件在桌面上生成时调用,并更新组件UI
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager,int[] appWidgetIds)
{
//创建RemoteViews
rv = new RemoteViews(context.getPackageName(), R.layout.wmain);
//创建启动修改心情的Activity的Intent
Intent intent = new Intent(context,AppWidgetDemo1Activity.class);
//创建包裹此Intent的PendingIntent
PendingIntent pendingIntent=PendingIntent.getActivity
(
context,
0,
intent,
PendingIntent.FLAG_UPDATE_CURRENT
);
//设置按下Widget中文本框发送此PendingIntent
rv.setOnClickPendingIntent(R.id.TextView01, pendingIntent);
//获取SharedPreferences
SharedPreferences sp=context.getSharedPreferences("xqsj", Context.MODE_PRIVATE);
//从SharedPreferences中读取上次的心情
String xqStr=sp.getString
(
"xq", //键值
null //默认值
);
if(xqStr!=null)
{//若上次心情存在则更新心情
rv.setTextViewText(R.id.TextView01, xqStr);
}
//更新Widget
appWidgetManager.updateAppWidget(appWidgetIds, rv);
}
@Override //onReceiver 为接收广播时调用更新UI
public void onReceive(Context context, Intent intent)
{
super.onReceive(context, intent);
if (rv == null)
{
//创建RemoteViews
rv = new RemoteViews(context.getPackageName(), R.layout.wmain);
}
if (intent.getAction().equals("wyf.action.update_xq"))
{//收到的是更新心情的 Action则更新心情
//更新心情
rv.setTextViewText(R.id.TextView01, intent.getStringExtra("xxq"));
//向Preferences中写入心情
SharedPreferences sp=context.getSharedPreferences("xqsj", Context.MODE_PRIVATE);
SharedPreferences.Editor editor=sp.edit();
editor.putString("xq",intent.getStringExtra("xxq"));
editor.commit();
}
else if (intent.getAction().equals("wyf.action.time_upadte"))
{//收到的是更新时间的 Action则更新时间
rv.setTextViewText(R.id.TextView02,intent.getStringExtra("time"));
}
//真正更新Widget
AppWidgetManager appWidgetManger = AppWidgetManager.getInstance(context);
int[] appIds = appWidgetManger.getAppWidgetIds
(
new ComponentName
(
context,
MyWidgetProvider.class
)
);
appWidgetManger.updateAppWidget(appIds, rv);
}
}
下面来逐步分析java代码
RemoteViews
远程操控 也就是显示桌面组件的那块
此Demo中就是显示 文本 以及 时间
接受两个参数(上下文的包名 以及widget的布局xml文件)
//创建RemoteViews
rv = new RemoteViews(context.getPackageName(), R.layout.wmain);
关于pendingIntent的理解 (延时的Intent)
pendingIntent字面意义:等待的,未决定的Intent。 pendingIntent对象,使用方法类的静态方法 :
getActivity(Context, int, Intent, int)------->跳转到一个activity组件、
getBroadcast(Context, int, Intent, int)------>打开一个广播组件
getService(Context, int, Intent, int)-------->打开一个服务组件。
分别对应着Intent的3个行为和参数有4个,比较重要的事第三个和第一个,其次是第四个和第二个。可以看到,要得到这个对象,必须传入一个Intent作为参数,必须有context作为参数。
pendingIntent是一种特殊的Intent。主要的区别在于Intent的执行立刻的,而pendingIntent的执行不是立刻的。pendingIntent执行的操作实质上是参数传进来的Intent的操作,但是使用pendingIntent的目的在于它所包含的Intent的操作的执行是需要满足某些条件的。
主要的使用的地方和例子:通知Notificatio的发送,短消息SmsManager的发送和警报器AlarmManager的执行等等。
intent英文意思是意图,pending表示即将发生或来临的事情。
PendingIntent这个类用于处理即将发生的事情。比如在通知Notification中用于跳转页面,但不是马上跳转。Intent 是及时启动,intent 随所在的activity 消失而消失。 PendingIntent
可以看作是对intent的包装,通常通过getActivity,getBroadcast
,getService来得到pendingintent的实例,当前activity并不能马上启动它所包含的intent,而是在外部执行
pendingintent时,调用intent的。正由于pendingintent中
保存有当前App的Context,使它赋予外部App一种能力,使得外部App可以如同当前App一样的执行pendingintent里的
Intent,
就算在执行时当前App已经不存在了,也能通过存在pendingintent里的Context照样执行Intent。另外还可以处理intent执行后的操作。常和alermanger
和notificationmanager一起使用。
Intent一般是用作Activity、Sercvice、BroadcastReceiver之间传递数据,而Pendingintent,一般用在
Notification上,可以理解为延迟执行的intent,PendingIntent是对Intent一个包装。
关于PendingIntent中 最后一个参数的理解:
setOnClickPendingIntent传入两个参数(被点击的View , intent)
//设置按下Widget中文本框发送此PendingIntent
rv.setOnClickPendingIntent(R.id.TextView01, pendingIntent);
setTextViewText传入两个参数(被点击的View , intent所带来的String数据)
rv.setTextViewText(R.id.TextView02,intent.getStringExtra("time"));
RemoteViews 的一些其他方法
SharedPreferences(轻量级存储)
很多时候我们开发的软件需要向用户提供软件参数设置功能,例如我们常用的QQ,用户可以设置是否允许陌生人添加自己为好友。对于软件配置参数的保存,如果是window软件通常我们会采用ini文件进行保存,如果是j2se应用,我们会采用properties属性文件进行保存。如果是Android应用,我们最适合采用什么方式保存软件配置参数呢?Android平台给我们提供了一个SharedPreferences类,它是一个轻量级的存储类,特别适合用于保存软件配置参数。使用SharedPreferences保存数据,其背后是用xml文件存放数据,文件存放在/data/data/<package name>/shared_prefs目录下:
SharedPreferences sharedPreferences = getSharedPreferences("itcast", Context.MODE_PRIVATE);
Editor editor = sharedPreferences.edit();//获取编辑器
editor.putString("name", "传智播客");
editor.putInt("age", 4);
editor.commit();//提交修改
生成的itcast.xml文件内容如下:
<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<map>
<string name="name">传智播客</string>
<int name="age" value="4" />
</map>
因为SharedPreferences背后是使用xml文件保存数据,getSharedPreferences(name,mode)方法的第一个参数用于指定该文件的名称,名称不用带后缀,后缀会由Android自动加上。
方法的第二个参数指定文件的操作模式,共有四种操作模式,这四种模式前面介绍使用文件方式保存数据时已经讲解过。
如果希望SharedPreferences背后使用的xml文件能被其他应用读和写,
可以指定Context.MODE_WORLD_READABLE和Context.MODE_WORLD_WRITEABLE权限。
(不过上述两种方法由于安全问题 在安卓4.0以后被剔除了)
另外Activity还提供了另一个getPreferences(mode)方法操作SharedPreferences,
这个方法默认使用当前类不带包名的类名作为文件的名称。
Widget中必须要实现的onUpdate代码
//onUpdate为组件在桌面上生成时调用,并更新组件UI
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager,int[] appWidgetIds)
{
//创建RemoteViews
rv = new RemoteViews(context.getPackageName(), R.layout.wmain);
//创建启动修改心情的Activity的Intent
Intent intent = new Intent(context,AppWidgetDemo1Activity.class);
//创建包裹此Intent的PendingIntent
PendingIntent pendingIntent=PendingIntent.getActivity
(
context,
0,
intent,
PendingIntent.FLAG_UPDATE_CURRENT
);
//设置按下Widget中文本框发送此PendingIntent
rv.setOnClickPendingIntent(R.id.TextView01, pendingIntent);
//获取SharedPreferences
SharedPreferences sp=context.getSharedPreferences("xqsj", Context.MODE_PRIVATE);
//从SharedPreferences中读取上次的心情
String xqStr=sp.getString
(
"xq", //键值
null //默认值
);
if(xqStr!=null)
{//若上次心情存在则更新心情
rv.setTextViewText(R.id.TextView01, xqStr);
}
//更新Widget
appWidgetManager.updateAppWidget(appWidgetIds, rv);
}
身为广播 必须要onReceive
只有这一段值得去说 剩下的前面都说过了
两个参数一个数组id值 一个RemoteView (由于onReceive参数中没有接受到Widget的管理类以及数组所以只能按照这一套去创建了)
appWidgetManger.updateAppWidget(appIds, rv);
//真正更新Widget
AppWidgetManager appWidgetManger = AppWidgetManager.getInstance(context);
int[] appIds = appWidgetManger.getAppWidgetIds
(
new ComponentName
(
context,
MyWidgetProvider.class //AppWidgetProvider子类的类名
)
);
appWidgetManger.updateAppWidget(appIds, rv);
@Override //onReceiver 为接收广播时调用更新UI
public void onReceive(Context context, Intent intent)
{
super.onReceive(context, intent);
if (rv == null)
{
//创建RemoteViews
rv = new RemoteViews(context.getPackageName(), R.layout.wmain);
}
if (intent.getAction().equals("wyf.action.update_xq"))
{//收到的是更新心情的 Action则更新心情
//更新心情
rv.setTextViewText(R.id.TextView01, intent.getStringExtra("xxq"));
//向Preferences中写入心情
SharedPreferences sp=context.getSharedPreferences("xqsj", Context.MODE_PRIVATE);
SharedPreferences.Editor editor=sp.edit();
editor.putString("xq",intent.getStringExtra("xxq"));
editor.commit();
}
else if (intent.getAction().equals("wyf.action.time_upadte"))
{//收到的是更新时间的 Action则更新时间
rv.setTextViewText(R.id.TextView02,intent.getStringExtra("time"));
}
//真正更新Widget
AppWidgetManager appWidgetManger = AppWidgetManager.getInstance(context);
int[] appIds = appWidgetManger.getAppWidgetIds
(
new ComponentName
(
context,
MyWidgetProvider.class
)
);
appWidgetManger.updateAppWidget(appIds, rv);
}
下面来看
Activity
主要是用来获取主界面 以及发送广播
public class AppWidgetDemo1Activity extends Activity
{
EditText et;
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
//初始化指向输入心情的文本区的引用
et=(EditText)this.findViewById(R.id.EditText01);
//获取确定按钮
Button b=(Button)this.findViewById(R.id.Button01);
//给确定按钮添加监听器
b.setOnClickListener
(
new OnClickListener()
{
@Override
public void onClick(View v)
{
//获取文本框中输入的心情
String msg=et.getText().toString();
if(msg.trim().length()==0)
{//若输入的心情为空则提示并返回
Toast.makeText
(
AppWidgetDemo1Activity.this,
"心情不能为空!!!",
Toast.LENGTH_SHORT
).show();
return;
}
else if(msg.length()>12)
{//若输入的心情超过长度则提示并返回
Toast.makeText
(
AppWidgetDemo1Activity.this,
"心情不能大于12个字!!!",
Toast.LENGTH_LONG
).show();
return;
}
//若输入合法则发送Intent修改widget中的内容
Intent intent = new Intent("wyf.action.update_xq");
intent.putExtra("xxq", msg);
AppWidgetDemo1Activity.this.sendBroadcast(intent);
//发送完Intent结束Activity
AppWidgetDemo1Activity.this.finish();
}
}
);
}
}