Android Launcher之AppWidgets(一)

来源:互联网 发布:百年树人网络研修平台 编辑:程序博客网 时间:2024/06/08 03:21

了解AppWidget

学习资料:http://developer.android.com/guide/topics/appwidgets/index.html


基础

创建一个AppWidget,需要:

AppWidgetProviderInfo 对象 用于描述AppWidget的元数据,例如AppWidget的layout(布局),更新频率(update frequency),以及AppWidgetProvider类的,这些都应该定义在一个XML中.

AppWidgetProvider 类的实现 该类定义了AppWidget的一些基于广播事件的基本方法,用于提供AppWidget的编程接口,当App Widgets进行updated、enabled、disabled和deleted操作时可以通过这些基本方法来接收广播(Broadcasts)

View Layout 视图布局 视图布局用于定义AppWidget的初始布局,布局文件应建在工程文件夹的/res/layout目录下

此外,还可以定义一个App Widget configuration Activity,当用户创建Widget时可通过该Activity对App Widget 进行一些设置


在Android Manifest文件中声明一个App Widget

谷歌的开发文档中给的一个例子:

        <receiver android:name="ExampleAppWidgetProvider" >            <intent-filter>                <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />            </intent-filter>            <meta-data android:name="android.appwidget.provider"               android:resource="@xml/example_appwidget_info" />        </receiver>        <!--<receiver android:name="ExampleAppWidgetProvider">:        <receiver>必须要声明android:name属性,该属性用来说明AppWidget使用的AppWidgetProvider.class            <intent-filter>                <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />            </intent-filter>:            <intent-filter>元素必须包含一个带android:name属性的<action>,例子中的这个属性表明                            AppWidgetProvider可以接受 ACTION_APPWIDGET_UPDATE 广播            <meta-data android:name="android.appwidget.provider"                     android:resource="@xml/example_appwidget_info" />:            <meat-data>用于描述AppWidgetProviderInfo的resource,该标签需要定义两个属性:            android:name="android.appwidget.provider"用于将该数据识别成对AppWidgetProviderInfo的描述            android:resource="@xml/example_appwidget_info" 用于声明AppWidgetProviderInfo的本地资源        </receiver>-->

添加AppWidgetProviderInfo的Metadata(元数据)

在res/xml目录下创建一个XML文件,该文件用于定义AppWidget的一些基本属性:

<!--<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"    android:minWidth="40dp":最小宽度    android:minHeight="40dp":最小高度    android:updatePeriodMillis="86400000":更新频率    android:previewImage="@drawable/preview":定义AppWidget的预览图片    android:initialLayout="@layout/example_appwidget":定义AppWidget使用的初始布局    android:configure="com.example.android.ExampleAppWidgetConfigure" :定义AppWidget的设置文件    android:resizeMode="horizontal|vertical":设置可以让用户调整插件大小,horizontal|vertical表示宽和高都可以调整    android:widgetCategory="home_screen":设置AppWidget是否可以显示在home_screen上或者lock_screen上    ></appwidget-provider>-->

创建AppWidget的布局

在res/layout目录下创建一个布局文件,用于设计AppWidget.由于AppWidget layout是基于RemoteViews类,只支持四个布局方式:

FrameLayout//框架布局LinearLayout//线性布局RelativeLayout//相对布局GridLayout//网格布局

只支持以下的AppWidget classes:

AnalogClockButtonChronometerImageButtonImageViewProgressBarTextViewViewFlipperListViewGridViewStackViewAdapterViewFlipper

AppWidget的箱模型:
AppWidget的箱模型

给AppWidget添加Margins(边距)

1.设置app的targetSdkVersion版本,至少要14;可能是因为版本低的不支持.

2.创建一个引用了dimension resource来设置margins的布局:

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:orientation="vertical"    android:padding="@dimen/widget_margin"     >        <ImageButton            android:id="@+id/ImageButtonClick"            android:layout_width="match_parent"            android:layout_height="wrap_content"            android:src="@drawable/ic_launcher" >        </ImageButton></LinearLayout>

引用dimension resource: android:padding=”@dimen/widget_margin”

3.在两个地方创建dimensions resources,一个在res/values目录下,另一个在res/values-14目录下

res/values/dimens.xml用于自定义AppWidget的margins(边距):

<dimen name="widget_margin">8dp</dimen>

res/values-14/dimens.xml用于设置值为0 的margins的AppWidget:

<dimen name="widget_margin">0dp</dimen>

使用AppWidgetProvider类

AppWidgetProvider类继承自BroadcastReceiver类,使用该类便于处理AppWidget broadcasts,AppWidgetProvider只接收和AppWidget有关的广播:updated,deleted,enabled,和disabled等.当这些广播事件发生时,AppWidgetProvider会触发相应的回调方法.

onUpdate()

onUpdate()会根据设置的updatePeriodMillis属性来间歇性更新AppWidget.当用户添加AppWidget时也会调用该方法,所以onUpdate()应该用来完成一些AppWidget的基本设置,例如定义Views的event handlers(事件处理器)、当需要时开启一个暂时性的服务.但是如果已经声明了一个configuration Activity,当用户添加AppWidget时候系统不会立即调用onUpdate(),而是通过调用创建的configuration Activity来执行第一次更新,而onUpdate()则在AppWidget的后续更新时才会被调用.

onAppWidgetOptionsChanged()

onAppWidgetOptionsChanged()方法在两个情况下会被调用,一个是AppWidget第一次被添加的时候,另一个是当AppWidget大小被调整后.可以通过该方法显示或者隐藏AppWidget尺寸范围内的内容.可以调用getAppWidgetOptions()方法来获取AppWidget的size rangs(大小范围),getAppWidgetOptions()方法会返回一个Bundle(包),这个Bundle包含以下内容:

OPTION_APPWIDGET_MIN_WIDTH :包含AppWidget当前的最小宽度,单位为dp

OPTION_APPWIDGET_MIN_HEIGHT :包含AppWidget当前的最小高度,单位为dp

OPTION_APPWIDGET_MAX_WIDTH :包含AppWidget当前的最大宽度,单位为dp

OPTION_APPWIDGET_MAX_HEIGHT :包含AppWidget当前的最大高度,单位为dp

ps:这个方法在Android4.1版本以上才有

onDeleted(Context context, int[] appWidgetIds)

当AppWidget被删除时会被调用.

onEnabled(Context context)

用户在桌面添加第一个AppWidget调用该方法.第一个是指从无到有的过程,也就是说不管之前是否添加过,然后又删了.只要用户添加AppWidget的时候桌面上没有这个AppWidget,就会调用该方法.

onDisabled(Context context)

最后一个AppWidget被删除时调用该方法.可以重写这个方法以处理一些事情,比如清除使用痕迹,删除临时database.

onReceive(Context context, Intent intent)

上述几个方法调用之后都会调用这个方法,这个方法可以不用自己实现.AppWidgetProvider已经封装好.

之前写的一个例子:

package com.example.mylauncher;import android.annotation.SuppressLint;import android.annotation.TargetApi;import android.app.PendingIntent;import android.appwidget.AppWidgetManager;import android.appwidget.AppWidgetProvider;import android.content.Context;import android.content.Intent;import android.os.Build;import android.os.Bundle;import android.util.Log;import android.widget.RemoteViews;import android.widget.RemoteViews.RemoteView;public class myAppWidgetProvider extends AppWidgetProvider{    @Override    public void onAppWidgetOptionsChanged(Context context, AppWidgetManager appWidgetManager, int appWidgetId, Bundle newOptions)    {        // TODO Auto-generated method stub        super.onAppWidgetOptionsChanged(context, appWidgetManager, appWidgetId, newOptions);    }    @Override    public void onRestored(Context context, int[] oldWidgetIds, int[] newWidgetIds)    {        // TODO Auto-generated method stub        super.onRestored(context, oldWidgetIds, newWidgetIds);    }    // receiver默认的消息接收重载    @Override    public void onReceive(Context context, Intent intent)    {        // TODO Auto-generated method stub        super.onReceive(context, intent);        Log.i("myLog", "onReceive");    }    // 到了时间间隔的时候接收到的消息,appwidgetservice发过来的消息    @Override    public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds)    {        // TODO Auto-generated method stub        super.onUpdate(context, appWidgetManager, appWidgetIds);        Log.i("myLog", "onUpdate");        // 更新远程的界面(launcher里的我的"所有的"appwidget)        for (int i = 0; i < appWidgetIds.length; i++)        {            Log.i("myLog", i + "");// i+""这样子写会先把i转换成String然后再和后面的""拼接            RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.layout_mywidget);// 远程视图            // views.setTextViewText(R.id.ImageButtonClick,            // "myClick");//这行代码会造成widget不能正常更新而显示"Problem Loading Widget"            views.setImageViewResource(R.id.ImageButtonClick, R.drawable.preview);// 设置Widget中ImageButton的图片            Log.i("myLog", "更新了图片");            // 加入事件:当点击widget时产生的 事件,这里设置的是跳转到mainActivity            views.setOnClickPendingIntent(R.id.ImageButtonClick, PendingIntent.getActivity(context, 0, new Intent(context, MainActivity.class), 0));            appWidgetManager.updateAppWidget(appWidgetIds[i], views);// 更新视图        }    }    // 删除一个appwidget得到消息    @Override    public void onDeleted(Context context, int[] appWidgetIds)    {        // TODO Auto-generated method stub        super.onDeleted(context, appWidgetIds);        Log.i("myLog", "onDeleted");        Log.i("myLog","context:"+ context.toString());    }    // 第一次add widget 的时候接收的消息    @Override    public void onEnabled(Context context)    {        // TODO Auto-generated method stub        super.onEnabled(context);        Log.i("myLog", "onEnabled");    }    // 最后一个app widget被删除的时候接收到的消息    @Override    public void onDisabled(Context context)    {        // TODO Auto-generated method stub        super.onDisabled(context);        Log.i("myLog", "onDisabled");    }}
>ps:所有的AppWidget实例的更新频率只能由一个updatePeriodMills的控制,即被用户添加到桌面的AppWidget实例只能同时更新.原因是AppWidgetProvider继承自BroadcastReceiver,不能保证app的进程在上述的回调函数返回结果之后保持运行(可以看一下BroadcastReceiver的生命周期).解决此问题的途径是,考虑在onUpdate()中开启一个Service,在这个Service中实现AppWidget自己的update.而且不用担心AppWidgetProvider会因为ANR错误(Application Not Responding)而关闭.

在AppWidget中运行Service的例子:http://code.google.com/p/wiktionary-android/source/browse/trunk/Wiktionary/src/com/example/android/wiktionary/WordWidget.java

接收AppWidget广播的Intents

AppWidgetProvider只是一个便利的类,如果想要直接接收AppWidget广播,可以自己写一个BroadcastReceiver或者重写onReceive(Context,Intent)方法.其中需要注意的Intent Action有:

ACTION_APPWIDGET_UPDATE

ACTION_APPWIDGET_DELETED

ACTION_APPWIDGET_ENABLED

ACTION_APPWIDGET_DISABLED

ACTION_APPWIDGET_OPTIONS_CHANGED


创建AppWidget Configuration Activity

在Android Manifest文件中声明

      <!-- 注册一个AppWidgetConfiguration Activity -->      <activity android:name=".myAppWidgetConfig" >          <intent-filter >              <action android:name="android.appwidget.action.APPWIDGET_CONFIGURE"/>          </intent-filter>      </activity>

在AppWidgetProviderInfo xml文件中声明android:configure属性:

这里写图片描述

注意两点:

1.AppWidget host每次调用configuration Activity时,configuration Activity要通过Intent extras返回一个包含AppWidget ID(EXTRA_APPWIDGET_ID)的结果;

2.AppWidget被创建的时候,如果configuration Activity打开了的话onUpdate()是不会被调用的,AppWidget第一次的更新将由configuration Activity去完成,而第二次以后才会调用onUpdate()来更新AppWidget.

从configuration Activity中更新AppWidget

直接从AppWidgetManager请求更新:

1.从发起configuration Activity的Intent获取AppWidget ID

public class myAppWidgetConfig extends Activity{    private int mAppWidgetId=0;    ......    @Override    protected void onCreate(Bundle savedInstanceState)    {        ......        //从启动AppWidgetConfiguration的intent获取AppWidget ID        Intent intent=getIntent();        Bundle extras=intent.getExtras();        if (extras!=null)        {            mAppWidgetId=extras.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID);            Log.i("myLog", "获取到ID:"+mAppWidgetId);        }        ......    }}

2.执行 AppWidget configuration

3.执行完AppWidget configuration之后,调用getInstance(Context)方法获取一个AppWidgetManager实例

AppWidgetManager appWidgetManager=AppWidgetManager.getInstance(getBaseContext());//获取一个AppWidgetManager实例

4.调用updateAppWidget(int,Remoteviews)用RemoteViews布局来更新AppWidget

    RemoteViews views=new RemoteViews(getBaseContext().getPackageName(), R.layout.layout_mywidget);//通过RemoteViews视图更新AppWidget    views.setOnClickPendingIntent(R.id.btnDrumToTestActivity, PendingIntent.getActivity(getBaseContext(), 0, new Intent(getBaseContext(), Test2Activity.class), 0));appWidgetManager.updateAppWidget(mAppWidgetId, views);

5.创建return Intent用于返回结果,关闭configuration Activity

Intent resultValue=new Intent();resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mAppWidgetId);setResult(RESULT_OK, resultValue);finish();

configuration Activity的一个例子:
http://wptrafficanalyzer.in/blog/android-app-widget-with-configuration-activity/


设置Preview Image

<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"  ...  android:previewImage="@drawable/preview"></appwidget-provider>

20150726 By Xiong

0 0