赞
踩
原文:https://blog.csdn.net/harvic880925/article/details/41445407
http://www.cnblogs.com/AndyGe/archive/2013/11/26/3442833.html
AppWidget是应用程序窗口小部件(Widget)是微型的应用程序视图,它可以被嵌入到其它应用程序中(比如桌面)并接收周期性的更新。你可以通过一个App Widget Provider来发布一个Widget。官方文档地址:《App Widgets》 这里涉及到两个方面的内容:AppWidgetProvider类和appwidget-provider标签;
1、appwidget-provider标签: 用来定义桌面widget的大小,初始状态等等信息的,它的位置应该放在res/xml文件夹下,具体的xml参数如下: android:minWidth: 最小宽度 android:minHeight: 最小高度 android:updatePeriodMillis: 更新widget的时间间隔(ms),"86400000"为1个小时 android:previewImage: 预览图片 android:initialLayout: 加载到桌面时对应的布局文件 android:resizeMode: widget可以被拉伸的方向。horizontal表示可以水平拉伸,vertical表示可以竖直拉伸 android:widgetCategory: widget可以被显示的位置。home_screen表示可以将widget添加到桌面,keyguard表示widget可以被添加到锁屏界面。 android:initialKeyguardLayout: 加载到锁屏界面时对应的布局文件 2、AppWidgetProvider类: 当widget要实时更新,要响应用户操作时,就需要额外的类来辅助处理了,这个类就是AppWidgetProvider。 由于AppWidgetProvider要接收到当前widget的状态(是否被添加,是否被删除等),所以要接收通知,必然是派生自BroadcastReceiver。 AppWidgetProvider中的广播处理函数如下:(根据不同的使用情况,重写不同的函数) onUpdate(): 在3种情况下会调用OnUpdate()。onUpdate()是在main线程中进行,因此如果处理需要花费时间多于10秒,处理应在service中完成。 (1)在时间间隔到时调用,时间间隔在widget定义的android:updatePeriodMillis中设置; (2)用户拖拽到主页,widget实例生成。无论有没有设置Configureactivity,我们在Android4.4的测试中,当用户拖拽图片至主页时,widget实例生 成,会触发onUpdate(),然后再显示activity(如果有)。这点和资料说的不一样,资料认为如果设置了Configure acitivity,就不会在一开始调 用onUpdate(),而实验显示当实例生成(包括创建和重启时恢复),都会先调用onUpate()。在本例,由于此时在preference尚未有相关数据,创建 实例时不能有效进行数据设置。 (3)机器重启,实例在主页上显示,会再次调用onUpdate() onDeleted(Context,int[]): 当 widget 被删除时被触发。 onEnabled(Context): 当第1个 widget 的实例被创建时触发。也就是说,如果用户对同一个 widget 增加了两次(两个实例),那么onEnabled()只会在第一次增加widget时触发。 onDisabled(Context): 当最后1个 widget 的实例被删除时触发。 onReceive(Context,Intent): 在接收到广播时,调用。 3,清单配置 <receiver android:name=".LedClockWidget" android:label="@string/app_name"> <intent-filter> <actionandroid:name="android.appwidget.action.APPWIDGET_UPDATE" /> </intent-filter> <meta-data android:name="android.appwidget.provider" android:resource="@xml/my_lock"/> </receiver>
com/example/administrator/LedClockWidget.java
packagecom.example.administrator; import android.annotation.SuppressLint; import android.appwidget.AppWidgetManager; import android.appwidget.AppWidgetProvider; import android.content.ComponentName; import android.content.Context; import android.os.Handler; import android.os.Message; import android.widget.RemoteViews; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Timer; import java.util.TimerTask; public class LedClockWidgetextends AppWidgetProvider { private Timer mTimer = new Timer(); private AppWidgetManager mAppWidgerManager; private Context mContext; //将0-9的液晶数字图片定义为数组 private int[] digits = new int[]{R.drawable.p0, R.drawable.p1, R.drawable.p2, R.drawable.p3, R.drawable.p4, R.drawable.p5, R.drawable.p6, R.drawable.p7, R.drawable.p8, R.drawable.p9,}; //将显示小时、分钟、秒钟的ImageView定义为数组 private int[] digitViews = new int[]{R.id.img01, R.id.img02, R.id.img04, R.id.img05, R.id.img07, R.id.img08,}; @Override public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds){ this.mAppWidgerManager =appWidgetManager; this.mContext =context; //定义计时器 mTimer = new Timer(); //启动周期性调度 mTimer.schedule(new TimerTask() { @Override public void run() { //发送空消息,通知界面更新 handler.sendEmptyMessage(0x123); } }, 0, 1000); super.onUpdate(context, appWidgetManager, appWidgetIds); } @SuppressLint("HandlerLeak") private Handler handler = new Handler() { @Override public void handleMessage(Message msg) { if (msg.what == 0x123) { RemoteViews views = new RemoteViews(mContext.getPackageName(), R.layout.main); //定义SimpleDateFormat对象 SimpleDateFormat df = new SimpleDateFormat("HHmmss"); //将当前时间格式化为HHmmss的形式 String timeStr = df.format(new Date()); for (int i = 0; i <timeStr.length(); i++) { //将第i个数字字符zh转换为对应的数字 int num = timeStr.charAt(i) - 48; //将第i个图片设为对应的液晶数字图片 views.setImageViewResource(digitViews[i], digits[num]); } //将APPWidgetProvider子类实例包装成ComponentName对象 ComponentName componentName = new ComponentName(mContext, LedClockWidget.class); //调用APPWidgetManager将RemoteViews添加到ComponentName中 mAppWidgerManager.updateAppWidget(componentName, views); } super.handleMessage(msg); } };
}
layout/main.xml
<?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="horizontal"> <ImageView android:id="@+id/img01" android:layout_width="30dp" android:layout_height="30dp" /> <ImageView android:id="@+id/img02" android:layout_width="30dp" android:layout_height="30dp" /> <ImageView android:layout_width="10dp" android:layout_height="30dp" android:background="@drawable/maohao" /> <ImageView android:id="@+id/img04" android:layout_width="30dp" android:layout_height="30dp" /> <ImageView android:id="@+id/img05" android:layout_width="30dp" android:layout_height="30dp" /> <ImageView android:layout_width="10dp" android:layout_height="30dp" android:background="@drawable/maohao" /> <ImageView android:id="@+id/img07" android:layout_width="30dp" android:layout_height="30dp" /> <ImageView android:id="@+id/img08" android:layout_width="30dp" android:layout_height="30dp" /> </LinearLayout>
xml/my_lock.xml
<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:initialLayout="@layout/main"
android:minHeight="70dp"
android:minWidth="150dp"
android:previewImage="@drawable/bbb"
android:resizeMode="horizontal|vertical"
android:updatePeriodMillis="1000" />
app/src/main/AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.administrator"> <uses-permission android:name="android.permission.INTERNET" /> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:supportsRtl="true"> <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <receiver android:name=".LedClockWidget" android:label="@string/app_name"> <intent-filter> <action android:name="android.appwidget.action.APPWIDGET_UPDATE" /> </intent-filter> <meta-data android:name="android.appwidget.provider" android:resource="@xml/my_lock" /> </receiver> </application> </manifest>
setRemoteAdapter(intviewId,Intentintent):该方法可以使用Intent更新RemoteViews中viewId对应的组件。
上面方法的Intent参数应该封装一个RemoteViewsService参数,RemoteViewsService虽然继承了Service组件,但它的主要作用是为RemoteViews中viewId对应的组件提供列表项。
由于Intent参数负责提供列表项,因此viewId参数对应的组件可以是ListView、GridView、StackView和AdapterViewFlipper等,这些组件都是AdapterView的子类,由此可见RemoteViewsService负责提供的对象,应该是一个类似于Adapter的对象。
RemoteViewsService通常用于被继承,继承该基类时需要重写它的onGetViewFactory()方法,该方法就需要返回一个类似于Adapterr对象——但不是Adapter,而是RemoteViewsFactory对象,RemoteViewsFactory的功能完全类似于Adapter。
实例
StackWidgetService.java
package org.crazyit.desktop; import android.content.Context; import android.content.Intent; import android.widget.RemoteViews; import android.widget.RemoteViewsService; public class StackWidgetService extends RemoteViewsService{ // 重写该方法,该方法返回一个RemoteViewsFactory对象。 // RemoteViewsFactory对象的的作用类似于Adapter, // 它负责为RemoteView中指定组件提供多个列表项。 @Override public RemoteViewsFactory onGetViewFactory(Intent intent){ return new StackRemoteViewsFactory(this.getApplicationContext(), intent); //① } class StackRemoteViewsFactory implements RemoteViewsService.RemoteViewsFactory{ // 定义一个数组来保存该组件生成的多个列表项 private int[] items = null; private Context mContext; public StackRemoteViewsFactory(Context context, Intent intent) { mContext = context; } @Override public void onCreate() { // 初始化items数组 items = new int[] { R.drawable.bomb5, R.drawable.bomb6, R.drawable.bomb7, R.drawable.bomb8, R.drawable.bomb9, R.drawable.bomb10, R.drawable.bomb11, R.drawable.bomb12, R.drawable.bomb13, R.drawable.bomb14, R.drawable.bomb15, R.drawable.bomb16 }; } @Override public void onDestroy() { items = null; } // 该方法的返回值控制该对象包含多少个列表项 @Override public int getCount() { return items.length; } // 该方法的返回值控制各位置所显示的RemoteViews @Override public RemoteViews getViewAt(int position) { // 创建RemoteViews对象,加载/res/layout目录下widget_item.xml文件 RemoteViews rv = new RemoteViews(mContext.getPackageName(), R.layout.widget_item); // 更新widget_item.xml布局文件中的widget_item组件 rv.setImageViewResource(R.id.widget_item, items[position]); // 创建Intent、用于传递数据 Intent fillInIntent = new Intent(); fillInIntent.putExtra(StackWidgetProvider.EXTRA_ITEM, position); // 设置当单击该RemoteViews时传递fillInIntent包含的数据 rv.setOnClickFillInIntent(R.id.widget_item, fillInIntent); // 此处使用让线程暂停0.5秒来模拟加载该组件 try { System.out.println("加载【" + position + "】位置的组件"); Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } return rv; } @Override public RemoteViews getLoadingView() { return null; } @Override public int getViewTypeCount() { return 1; } @Override public long getItemId(int position) { return position; } @Override public boolean hasStableIds() { return true; } @Override public void onDataSetChanged() { } } }
widget_item.xml
<?xml version="1.0" encoding="utf-8"?>
<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/widget_item"
android:layout_width="120dp"
android:layout_height="120dp"
android:gravity="center"/>
StackWidgetProvider.java
package org.crazyit.desktop; import android.app.PendingIntent; import android.appwidget.AppWidgetManager; import android.appwidget.AppWidgetProvider; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.widget.RemoteViews; import android.widget.Toast; public class StackWidgetProvider extends AppWidgetProvider { public static final String TOAST_ACTION = "org.crazyit.desktop.TOAST_ACTION"; public static final String EXTRA_ITEM = "org.crazyit.desktop.EXTRA_ITEM"; @Override public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { // 创建RemoteViews对象,加载/res/layout目录下的widget_layout.xml文件 RemoteViews rv = new RemoteViews(context.getPackageName(), R.layout.widget_layout); Intent intent = new Intent(context, StackWidgetService.class); // 使用intent更新rv中stack_view组件(StackView) rv.setRemoteAdapter(R.id.stack_view, intent); //① // 设置当StackWidgetService提供的列表项为空时,直接显示empty_view组件 rv.setEmptyView(R.id.stack_view, R.id.empty_view); // 创建启动StackWidgetProvider组件(作为BroadcastReceiver)的Intent Intent toastIntent = new Intent(context, StackWidgetProvider.class); // 为该Intent设置Action属性 toastIntent.setAction(StackWidgetProvider.TOAST_ACTION); // 将Intent包装成PendingIntent PendingIntent toastPendingIntent = PendingIntent .getBroadcast(context, 0, toastIntent, PendingIntent.FLAG_UPDATE_CURRENT); // 将PendingIntent与stack_view进行关联 rv.setPendingIntentTemplate(R.id.stack_view, toastPendingIntent); // 使用AppWidgetManager通过RemteViews更新AppWidgetProvider appWidgetManager.updateAppWidget( new ComponentName(context, StackWidgetProvider.class), rv); //② super.onUpdate(context, appWidgetManager, appWidgetIds); } @Override public void onDeleted(Context context, int[] appWidgetIds) { super.onDeleted(context, appWidgetIds); } @Override public void onDisabled(Context context) { super.onDisabled(context); } @Override public void onEnabled(Context context) { super.onEnabled(context); } // 重写该方法,将该组件当成BroadcastReceiver使用 @Override public void onReceive(Context context, Intent intent) { if (intent.getAction().equals(TOAST_ACTION)) { // 获取Intent中的数据 int viewIndex = intent.getIntExtra(EXTRA_ITEM, 0); // 显示Toast提示 Toast.makeText(context, "点击第【" + viewIndex + "】个列表项", Toast.LENGTH_SHORT).show(); } super.onReceive(context, intent); } }
widget_layout.xml
<?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_margin="8dp"> <StackView android:id="@+id/stack_view" android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center" android:loopViews="true" /> <TextView android:id="@+id/empty_view" android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center" android:background="#ff0f" android:textColor="#ffffff" android:textStyle="bold" android:text="@string/no_item" android:textSize="20sp" /> </FrameLayout>
Manifest.xml
<?xml version="1.0" encoding="utf-8" ?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="org.crazyit.desktop" android:versionCode="1" android:versionName="1.0"> <uses-sdk android:minSdkVersion="14" android:targetSdkVersion="17" /> <application android:allowBackup="true" android:label="@string/app_name"> <!-- 配置AppWidgetProvider,即配置桌面控件 --> <receiver android:name=".StackWidgetProvider"> <!-- 通过该intent-filter指定该Receiver作为桌面控件 --> <intent-filter> <action android:name="android.appwidget.action.APPWIDGET_UPDATE" /> </intent-filter> <!-- 为桌面控件指定meta-data --> <meta-data android:name="android.appwidget.provider" android:resource="@xml/stackwidgetinfo" /> </receiver> <!-- 配置RemoteViewsService 必须指定权限为android.permission.BIND_REMOTEVIEWS --> <service android:name=".StackWidgetService" android:permission="android.permission.BIND_REMOTEVIEWS" android:exported="false" /> </application> </manifest>
stackwidgetinfo.xml
<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider
xmlns:android="http://schemas.android.com/apk/res/android"
android:minWidth="110dp"
android:minHeight="110dp"
android:updatePeriodMillis="3600000"
android:previewImage="@drawable/ic_launcher"
android:initialLayout="@layout/widget_layout"
android:resizeMode="horizontal|vertical"
android:autoAdvanceViewId="@id/stack_view">
</appwidget-provider>
1、有关布局错误 在构造Widget布局时,AppWidget支持的布局和控件非常有限,有如下几个: AppWidget支持的布局: FrameLayout LinearLayout RelativeLayout GridLayout AppWidget支持的控件: AnalogClock Button Chronometer ImageButton ImageView ProgressBar TextView ViewFlipper ListView GridView StackView AdapterViewFlipper 除此之外的所有控件(包括自定义控件)都无法显示,无法显示时,添加出来的widget会显示“加载布局出错” 2、appwidget-provider出现错误 如果appwidget-provider页面出现错误提示:error:No resource identifier found for attribute 'widgetCategory' in package'android' 这是由于buildtarget应该在17以上
赞
踩
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。