Android Launcher之AppWidgets(一)

了解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:

AnalogClock
Button
Chronometer
ImageButton
ImageView
ProgressBar
TextView
ViewFlipper
ListView
GridView
StackView
AdapterViewFlipper

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
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值