Widget

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中 最后一个参数的理解:
Widget
setOnClickPendingIntent传入两个参数(被点击的View , intent)

      //设置按下Widget中文本框发送此PendingIntent
            rv.setOnClickPendingIntent(R.id.TextView01, pendingIntent);
     

setTextViewText传入两个参数(被点击的View , intent所带来的String数据)

     rv.setTextViewText(R.id.TextView02,intent.getStringExtra("time"));

RemoteViews 的一些其他方法
Widget

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();
							}
						}
				);
	}
}