Android(安卓)-- AppWidget 高级篇
3.1AppWidget到底支持哪些view
在Android2.2SDK中我们首次启动模拟器可以看到和以前不一样的是多出了一个绿色的小机器人提示信息,Google给我们了演示了Android中如何通过RemoteView和简单的图片轮换方式实现动画效果在桌面小工具中,appWidget的基类时AppWidgetProvider类,不过Widget本身的生命周期管理并非Activity,相对于的而是BroadcastReceiver广播方式处理的。一直想知道如何在AppWidget里面添加ListView,EditText这些复杂的View.我们知道要在AppWidget里添加View都是通过RemoteView来做到了,然而RemoteView本身功能很弱,支持的操作很少,而且支持RemoteView的Widget很少:
ARemoteViewsobject(and,consequently,anAppWidget)cansupportthefollowinglayoutclasses:
FrameLayout
LinearLayout
RelativeLayout
Andthefollowingwidgetclasses:
AnalogClock
Button
Chronometer
ImageButton
ImageView
ProgressBar
TextView
Descendantsoftheseclassesarenotsupported.
从这里可以知道,为什么在AppWidget里添加EditText会显示LoadError了,因为本身它就不支持这些复杂的Widget.
但我们又会有疑问了,为什么GoogleSearch会有EditText呢?其实这些都是假象,并不是AppWidget支持EditText。细心的你应该会发现,AnalogClock也不是如Button,TextView的简单Widget,其实AnalogClock也是Google自定义的RemoteViews。
在网上可以看到,AppWidget很多特效,它确实支持了复杂Widget,比如:ListView/GridView,EditText.这些确实是我们可以看到的,但它是怎么做到的呢?我也很想知道,AppWidget支持到那么强大,甚至超过了本身AP的功能,很抢眼。但不管是怎么实现的,我想人家肯定是花了大力气去做到了,我猜想可能是将Google提供的AppWidget进行了比较大的改动。我们查看一下framework下的appwidget:
:lsframeworks/base/core/java/android/appwidget/-lh
total60K
-rw-r--r--1pjqusers7.9K2009-09-2921:49AppWidgetHost.java
-rw-r--r--1pjqusers12K2009-09-2921:49AppWidgetHostView.java
-rw-r--r--1pjqusers14K2009-09-2921:49AppWidgetManager.java
-rw-r--r--1pjqusers6912009-09-2921:49AppWidgetProviderInfo.aidl
-rw-r--r--1pjqusers5.6K2009-09-2921:49AppWidgetProviderInfo.java
-rwxr-xr-x1pjqusers6.3K2009-09-2921:49AppWidgetProvider.java
-rw-r--r--1pjqusers1.5K2009-09-2921:49package.html
可以看到,appwidget的文件很少,虽然不能说明什么,但按照正常的推理,文件少功能一般也强大不到哪里去,这种想法虽然有些牵强,但暂且就这样认为吧。
3.2如何自定义RemoteViews
要知道RemoteView的功能很少,特别是对事件处理的能力,都需要通过PendingIntent,传到BroadcastReceiver去处理。所以这里对一些事件处理也仅限于比较简单事件:比如说:ButtonClicked,其它的我好像还没怎么用过,对复杂的View:比如!ListView(当然这里还不支持,打个比方),!ListView里面那么多Item,要设置Listener,要传值,这些RemoteView都不能像一个单纯的Activity那样处理,如果要实现,则需要更加复杂的手段,通过广播实现。
由于日历小部件需要实现onClick事件,显示日历,动画效果等复杂的操作和效果,AppWidget支持的操作远远不能满足,这就需要修改framework里的代码,目前我已经在AppWidget里显示CalendarView(日历)、Viewflipper复杂的Widget,同时实现了如何让这些自定义的RemoteViews与AppWidget进行交互。现在让我详细介绍如何在AppWidget里自定义CalendarView(当然是继承自view咯^_^)和Viewflipper(只是在原基础上做了修改)这些复杂的Widget.
我们知道AppWidget只支持RemoteView,哪些Widget是RemoteView呢,我来教你搜一下:
frameworks/base/core/java/android/widget $ grep -i -n -A 1 @remoteview *.javaAbsoluteLayout.java:40:@RemoteViewAbsoluteLayout.java-41-public class AbsoluteLayout extends ViewGroup {--AnalogClock.java:39:@RemoteViewAnalogClock.java-40-public class AnalogClock extends View {--Button.java:58:@RemoteViewButton.java-59-public class Button extends TextView {--Chronometer.java:45:@RemoteViewChronometer.java-46-public class Chronometer extends TextView {--FrameLayout.java:47:@RemoteViewFrameLayout.java-48-public class FrameLayout extends ViewGroup {--ImageButton.java:66:@RemoteViewImageButton.java-67-public class ImageButton extends ImageView {--ImageView.java:55:@RemoteViewImageView.java-56-public class ImageView extends View {--LinearLayout.java:44:@RemoteViewLinearLayout.java-45-public class LinearLayout extends ViewGroup {--ProgressBar.java:122:@RemoteViewProgressBar.java-123-public class ProgressBar extends View {--RelativeLayout.java:66:@RemoteViewRelativeLayout.java-67-public class RelativeLayout extends ViewGroup {--TextView.java:186:@RemoteViewTextView.java-187-public class TextView extends View implements ViewTreeObserver.OnPreDrawListener {
就是这些了,类名前面加了"@RemoteView",和我前面列出的那些是不是一样的呢?所以,如果你需要自定义一个(或者是一个已定义的复杂View如listview)作为RemoteViews使用,你就必须在其类名前加"@RemoteView"标识。
关于如何自定义一个Widget你完全可以参照frameworks/base/core/java/android/widget已有的这些Widget,照样写一个。
其实如果你需要自定义一个Widget,比如说支持ListView,你可以先在一个activity里实现它,然后将它移到framework下面去。
这里说一下可能需要注意的地方:
1.如果有多个文件,需要Package的时候,名字最好按照这样的形式:android.widget.CalendarView
其中CalendarView就是你要添加一个Widget存放的地方,这样的话你就可以在frameworks/base/core/java/android/widget目录下新增CalendarView文件夹,将java文件放在这个目录下。
如果你新增的Widget只有一个java文件就可以不用这样了,可以完全按照已经存在的Widget的样子,直接将java文件放到frameworks/base/core/java/android/widget目录下。
2.资源文件存放:
frameworks/base/core/res/res
资源文件都放到这个目录下。
3.资源的引用:
要用这样的方式引用:com.android.internal.R.drawable.
- 记着在这个CustomerWidget类名前加上"@RemoteView"标记.
这些都做完了,你就已经将一个自定义的Widget添加到framework了。之后要做的工作就是编译整个工程了(在这里教一个比较懒的方法,直接编译frameworks就OK,命令:
:mmmframeworks/base/
:adbpushout/target/product/msm7627_ffa/system/framework/
framework.jar/system/framework/)。
最后你就可以在AppWidget引用你自定义的这个Widget了:
com.widget.CalendarView。
3.3AppWidget访问RemoteViews的方法
至此,你已经用上了你自定义的这个Widget,并且可以加到AppWidget。如果你想在自定义的RemoteViews上显示像日历的内容,你只需要在自定义的RemoteViews的onDraw()方法里实现就OK。但是并不是所有内容都是在RemoteViews预先设置好的,很多内容是由用户自己设置的。如,在AppWdiget显示”HelloWord”,我们是这样实现的
RemoteViewsviews=newRemoteViews(context.getPackageName(),R.layout.appwidget_provider);
views.setTextViewText(R.id.appwidget_text,"HelloWord");
RemoteViews为外部提供的方法非常有限,查看API文档:
PublicMethodsViewapply(Contextcontext,ViewGroupparent)
Inflatestheviewhierarchyrepresentedbythisobjectandappliesalloftheactions.
intdescribeContents()
DescribethekindsofspecialobjectscontainedinthisParcelable'smarshalledrepresentation.
intgetLayoutId()StringgetPackage()booleanonLoadClass(Classclazz)
HooktoallowclientsoftheLayoutInflatertorestrictthesetofViewsthatareallowedtobeinflated.
voidreapply(Contextcontext,Viewv)
Appliesalloftheactionstotheprovidedview.
voidsetBitmap(intviewId,StringmethodName,Bitmapvalue)
CallamethodtakingoneBitmaponaviewinthelayoutforthisRemoteViews.
voidsetBoolean(intviewId,StringmethodName,booleanvalue)
CallamethodtakingonebooleanonaviewinthelayoutforthisRemoteViews.
voidsetByte(intviewId,StringmethodName,bytevalue)
CallamethodtakingonebyteonaviewinthelayoutforthisRemoteViews.
voidsetChar(intviewId,StringmethodName,charvalue)
CallamethodtakingonecharonaviewinthelayoutforthisRemoteViews.
voidsetCharSequence(intviewId,StringmethodName,CharSequencevalue)
CallamethodtakingoneCharSequenceonaviewinthelayoutforthisRemoteViews.
voidsetChronometer(intviewId,longbase,Stringformat,booleanstarted)
EquivalenttocallingChronometer.setBase,Chronometer.setFormat,andChronometer.start()orChronometer.stop().
voidsetDouble(intviewId,StringmethodName,doublevalue)
CallamethodtakingonedoubleonaviewinthelayoutforthisRemoteViews.
voidsetFloat(intviewId,StringmethodName,floatvalue)
CallamethodtakingonefloatonaviewinthelayoutforthisRemoteViews.
voidsetImageViewBitmap(intviewId,Bitmapbitmap)
等同于调用ImageView.setImageBitmap方法,从Bitmap对象中设置一个图片
voidsetImageViewResource(intviewId,intsrcId)
等同于调用ImageView.setImageResource,从一个资源中设置图片
voidsetImageViewUri(intviewId,Uriuri)
等同于调用ImageView.setImageURI,从URI中设置图像
voidsetInt(intviewId,StringmethodName,intvalue)
CallamethodtakingoneintonaviewinthelayoutforthisRemoteViews.
voidsetLong(intviewId,StringmethodName,longvalue)
CallamethodtakingonelongonaviewinthelayoutforthisRemoteViews.
voidsetOnClickPendingIntent(intviewId,PendingIntentpendingIntent)
EquivalenttocallingsetOnClickListener(android.view.View.OnClickListener)tolaunchtheprovidedPendingIntent.
voidsetProgressBar(intviewId,intmax,intprogress,booleanindeterminate)
等同于调用ProgressBar.setMax,ProgressBar.setProgress,andProgressBar.如果indeterminate为true则进度条的最大和最小进度将会忽略
voidsetShort(intviewId,StringmethodName,shortvalue)
CallamethodtakingoneshortonaviewinthelayoutforthisRemoteViews.
voidsetString(intviewId,StringmethodName,Stringvalue)
CallamethodtakingoneStringonaviewinthelayoutforthisRemoteViews.
voidsetTextColor(intviewId,intcolor)
等同于setTextColor(int).,设置文本的颜色
voidsetTextViewText(intviewId,CharSequencetext)
等同于TextView.setText,设置文本内容
voidsetUri(intviewId,StringmethodName,Urivalue)
CallamethodtakingoneUrionaviewinthelayoutforthisRemoteViews.
voidsetViewVisibility(intviewId,intvisibility)
等同于调用View.setVisibility,设置该ID控件的可见性
voidwriteToParcel(Parceldest,intflags)
FlattenthisobjectintoaParcel.
无论你自定义的RemoteView有多少方法,但它终究是一个RemoteViews,所以在AppWidget中使用RemoteViews(无论是否是自定义的)只能调用以上提供的方法。现在你一定很疑惑,那么是不是AppWidget不能调用自定义RemoteViews里的方法呢?能,当然能,并且还可以将RemoteViews里的结果反馈到AppWidget,实现AppWidget和RemoteViews的交互,只不过这种方法实现起来比较复杂,容我慢慢道来。
以上方法中,有几个特殊的方法
voidjava.lang.String, android.graphics.Bitmap) setBitmap(intviewId,StringmethodName,Bitmapvalue)
voidjava.lang.String, boolean) setBoolean(intviewId,StringmethodName,booleanvalue)
voidjava.lang.String, byte) setByte(intviewId,StringmethodName,bytevalue)
voidjava.lang.String, char) setChar(intviewId,StringmethodName,charvalue)
voidjava.lang.String, java.lang.CharSequence) setCharSequence(intviewId,StringmethodName,CharSequencevalue)
voidjava.lang.String, double) setDouble(intviewId,StringmethodName,doublevalue)
voidjava.lang.String, float) setFloat(intviewId,StringmethodName,floatvalue)
voidjava.lang.String, android.os.Bundle) setBundle(intviewId,StringmethodName,Bundlevalue)
这些方法是通往自定义RemoteViews的接口。它们都包含3个参数,第一个参数viewId是你所要调用RemoteViews在布局文件里定义的Id,第二个参数methodName是你要调用的RemoteViews里的方法名,如你要调用RemoteViews里的一个setName(Stringname),第二个参数你就可以设置为“setName”;第三个参数是你所要调用的方法的参数,以setName(Stringname)方法为例,你必须传入一个String的参数,则在这里调用voidjava.lang.String, java.lang.CharSequence) setCharSequence(intviewId,StringmethodName,CharSequencevalue),因为只有这个方法的第三个参数是String类型。下面看一个实例:
/*自定义的RemoteView*/@RemoteViewpublic class CalendarView extends View {... /@hide*/ @android.view.RemotableViewMethod public void setTextSize(Bundle bundle){ mLineSize = bundle.getInt("LineSize", 1); mDigitalSize = bundle.getInt("DigitalSize", 2); }...}/* AppWidget 如何调用*/...RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.agenda_appwidget);Bundle bundle = new Bundle();......views. java.lang.String, android.os.Bundle) setBundle(R.id.preview,"setTextSize", bundle);
这样就完成了一次调用,当在AppWidget调用views.java.lang.String, android.os.Bundle) setBundle(...)时,就相当与调用
Calendar里的setTextSize方法。如此就实现了AppWidget对RemoteViews的访问。
注意:设为能在AppWidget被调用的方法setTextSize前必须加@…
3.4AppWidget接收来自RemoteViews的信息
接下来说说你对自定义的RemoteViews的操作,如何让AppWidget响应反馈?
似乎是没有其它更好的方法,幸好Android提供了一个非常有效的让不同进程传递数据和事件的媒介,即广播机制。当对自定义RemoteViews的操作需要告知AppWidget时,就需要用广播机制,发送一个广播,并在AppWidget注册这个广播的监听,AppWidget就能响应了。广播机制大家都很清楚,这里就不班门弄斧了。
3.5让AppWidget实现翻页动画效果
那么如何实现viewflipper翻页动画呢?
如果对上面的介绍都了解的话,翻页动画就简单了。同样,将在viewflipper类前加RemoteView标识@RemoteView,让AppWidget支持viewflipper。因为上面我们自定义的 CalendarView是被支持的,所以可以在AppWidget布局文件加上viewflipper和CalendarView(不被AppWidget支持的widget是不能被加到AppWidget布局文件的,否则运行出错)。那么如何实现翻页动画效果呢,其实和一般的viewflipper使用差别不大,先看布局文件:
<ViewFlipper android:id="@+id/flipper" android:layout_width="fill_parent" android:layout_height="fill_parent" android:gravity="center" android:persistentDrawingCache="animation" android:flipInterval="1000" <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"><CalendarView android:id="@+id/preview" android:layout_width="match_parent" android:layout_height="match_parent"/></LinearLayout><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:orientation="vertical" android:layout_width="match_parent"android:layout_height="match_parent"><CalendarView android:id="@+id/nextview" android:layout_width="match_parent" android:layout_height="match_parent"/></LinearLayout></ViewFlipper>ViewFlipper里有2个CalendarView。如何实现翻页呢?如果只需要一个方向的翻页,则在配置文件写好就好了,但是如果需要左右翻,或者上下翻,则需要在ViewFlipper里添加2个如showNext,showPrevious的方法(因为在AppWidget无法用RemoteView向ViewFlipper传动画对象),并将动画文件放到framework里,示例如下:
/@hide*/ @android.view.RemotableViewMethod public void showPreviousAppWidget(String str) { setInAnimation(AnimationUtils.loadAnimation(this.getContext(), com.android.internal.R.anim.slide_in_right)); setOutAnimation(AnimationUtils.loadAnimation(this.getContext(), com.android.internal.R.anim.slide_out_left)); setDisplayedChild(mWhichChild - 1); } /@hide*/ @android.view.RemotableViewMethod public void showNextAppWidget(String str) { setInAnimation(AnimationUtils.loadAnimation(this.getContext(), com.android.internal.R.anim.slide_in_left)); setOutAnimation(AnimationUtils.loadAnimation(this.getContext(), com.android.internal.R.anim.slide_out_right)); setDisplayedChild(mWhichChild + 1); }
同样,这个两个方法将在AppWidget里被调用,所以在方面前加@android.view.RemotableViewMethod标识。参数str其实只是为了匹配
voidjava.lang.String, java.lang.CharSequence) setCharSequence(intviewId,StringmethodName,CharSequencevalue)的第三个参数。在AppWidget里调用java.lang.String, java.lang.CharSequence) setCharSequence方法时第三个参数传入“”。
那么,又是如何在Appwidget实现onTouch事件呢?其实同样简单,就是在自定义的RemoteView里加上GestureDetector,用来识别手势,并发送相应的广播给AppWidget,让Appwidget作相应的处理,如上滑下滑的手势。
3.6总结
然后我说说遇到的一些问题和使用AppWidget的建议。
1、查看AppWidget源码知道,AppWidget是继承BroadcastReceiver,并且AppWidget的onUpdate,onDeleted,onEnabled,onDisabled四个方法都是从onReceive分化出来的,如何让AppWidget接收广播呢?重写onReceive即可,只是最后一定要调用AppWidgetManager.updateAppWidget(appWidgetIds,views)这个方法。
2、BroadcastReceiver的生命周期是短暂的,接收到消息很快就销毁了,如何在接收到消息后需要做大量计算的,最好在接收到消息后启动一个service,并将数据传给service,让service帮我们去计算。当然,这里还有一个关键,就是要把int[]appWidgetIds通过一个share类传到service,当service执行玩任务以后,调用AppWidgetManager.updateAppWidget(appWidgetIds,views)刷新AppWidget.
3、AppWidget是定期或定期刷新的,所以我们有必要将静态变量放在一个share类里,也可利用share类传appWidgetIds值,这样方便在service对AppWidget做刷新。
更多相关文章
- android sdk 各个版本的区别
- Android调用系统关机与重启功能
- Android创建和使用数据库
- Linux Kernel and Android(安卓)休眠与唤醒(中文版)
- 浅析 Android(安卓)的窗口
- Android中的横竖屏
- Android智能指针SP WP使用方法介绍
- 浅谈Java中Collections.sort对List排序的两种方法
- Python list sort方法的具体使用