Android -- AppWidget 進階篇

來源:互聯網
上載者:User

3.1 AppWidget到底支援哪些view
在Android 2.2 SDK中我們初次開機模擬器可以看到和以前不一樣的是多出了一個綠色的小機器人提示資訊,Google給我們了示範了Android中如何通過 RemoteView和簡單的圖片輪換方式實現動畫效果在案頭小工具中,appWidget的基類時AppWidgetProvider類,不過 Widget本身的生命週期管理並非Activity,相對於的而是BroadcastReceiver廣播方式處理的。一直想知道如何在AppWidget裡面添加 ListView,EditText 這些複雜的View.我們知道要在AppWidget裡添加 View都是通過RemoteView來做到了,然而RemoteView本身功能很弱,支援的操作很少,而且支援RemoteView的Widget很 少:
A RemoteViews object (and, consequently, an App Widget) can support the following layout classes:
 FrameLayout
 LinearLayout
 RelativeLayout
And the following widget classes:
 AnalogClock
 Button
 Chronometer
 ImageButton
 ImageView
 ProgressBar
 TextView
Descendants of these classes are not supported.
從這裡可以知道,為什麼在AppWidget裡添加 EditText會顯示LoadError了,因為本身它就不支援這些複雜的 Widget.
但我們又會有疑問了, 為什麼Google Search會有EditText呢?其實這些都是假象,並不是AppWidget支援EditText。細心的你應該會發現, AnalogClock也不是如Button,TextView的簡單Widget ,其實 AnalogClock也是Google自訂的RemoteViews。
在網上可以看到,AppWidget很多特效,它確實支援了複雜Widget,比如:ListView/GridView,EditText. 這些確實是我們可以看到的,但它是怎麼做到的呢?我也很想知道,AppWidget支援到那麼強大,甚至超過了本身AP的功能,很搶眼。但不管是怎麼實現 的,我想人家肯定是花了大力氣去做到了,我猜想可能是將Google 提供的AppWidget進行了比較大的改動。我們查看一下framework下的appwidget:
:ls frameworks/base/core/java/android/appwidget/ -lh
total 60K
-rw-r--r-- 1 pjq users 7.9K 2009-09-29 21:49 AppWidgetHost.java
-rw-r--r-- 1 pjq users  12K 2009-09-29 21:49 AppWidgetHostView.java
-rw-r--r-- 1 pjq users  14K 2009-09-29 21:49 AppWidgetManager.java
-rw-r--r-- 1 pjq users  691 2009-09-29 21:49 AppWidgetProviderInfo.aidl
-rw-r--r-- 1 pjq users 5.6K 2009-09-29 21:49 AppWidgetProviderInfo.java
-rwxr-xr-x 1 pjq users 6.3K 2009-09-29 21:49 AppWidgetProvider.java
-rw-r--r-- 1 pjq users 1.5K 2009-09-29 21:49 package.html
可以看 到,appwidget的檔案很少,雖然不能說明什麼,但按照正常的推理,檔案少功能一般也強大不到哪裡去,這種想法雖然有些牽強,但暫且就這樣認為吧。

3.2 如何自訂RemoteViews
要知道RemoteView的功能很少,特別是對事件處理的能力,都需要通過PendingIntent,傳到BroadcastReceiver去處理。所以這裡對一些事件處理也僅限於比較簡單事 件:比如說:Button Clicked,其它的我好像還沒怎麼用過,對複雜的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 呢,我來教你搜一下:
[python]
frameworks/base/core/java/android/widget $ grep -i -n -A 1  @remoteview *.java 
AbsoluteLayout.java:40:@RemoteView 
AbsoluteLayout.java-41-public class AbsoluteLayout extends ViewGroup { 
-- 
AnalogClock.java:39:@RemoteView 
AnalogClock.java-40-public class AnalogClock extends View { 
-- 
Button.java:58:@RemoteView 
Button.java-59-public class Button extends TextView { 
-- 
Chronometer.java:45:@RemoteView 
Chronometer.java-46-public class Chronometer extends TextView { 
-- 
FrameLayout.java:47:@RemoteView 
FrameLayout.java-48-public class FrameLayout extends ViewGroup { 
-- 
ImageButton.java:66:@RemoteView 
ImageButton.java-67-public class ImageButton extends ImageView { 
-- 
ImageView.java:55:@RemoteView 
ImageView.java-56-public class ImageView extends View { 
-- 
LinearLayout.java:44:@RemoteView 
LinearLayout.java-45-public class LinearLayout extends ViewGroup { 
-- 
ProgressBar.java:122:@RemoteView 
ProgressBar.java-123-public class ProgressBar extends View { 
-- 
RelativeLayout.java:66:@RemoteView 
RelativeLayout.java-67-public class RelativeLayout extends ViewGroup { 
-- 
TextView.java:186:@RemoteView 
TextView.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.
記著在這個Customer Widget類名前加上"@RemoteView"標記.
這 些都做完了,你就已經將一個自訂的Widget添加到framework了。之後要做的工作就是編譯整個工程了(在這裡教一個比較懶的方法,直接編譯frameworks 就OK,命令:
:mmm frameworks/base/
:adb push out/target/product/msm7627_ffa/system/framework/
framework.jar /system/framework/)。
最後你就可以在AppWidget引用你自訂的這個Widget了:
com.widget.CalendarView。

3.3  AppWidget訪問RemoteViews的方法
至 此,你已經用上了你自訂的這個Widget,並且可以加到AppWidget。如果你想在自訂的RemoteViews上顯示像日曆的內容,你只需要在自訂的RemoteViews的onDraw()方法裡實現就OK。但是並不是所有內容都是在RemoteViews預先設定好的,很多內容是由使用者自己設定的。如,在AppWdiget顯示”HelloWord”,我們是這樣實現的
RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.appwidget_provider);
views.setTextViewText(R.id.appwidget_text, "HelloWord"); 
RemoteViews為外部提供的方法非常有限,查看API文檔:
Public Methods View apply(Context context, ViewGroup parent)
Inflates the view hierarchy represented by this object and applies all of the actions.
int describeContents()
Describe the kinds of special objects contained in this Parcelable's marshalled representation.
int getLayoutId() String getPackage() boolean onLoadClass(Class clazz)
Hook to allow clients of the LayoutInflater to restrict the set of Views that are allowed to be inflated.
void reapply(Context context, View v)
Applies all of the actions to the provided view.
void setBitmap(int viewId, String methodName, Bitmap value)
Call a method taking one Bitmap on a view in the layout for this RemoteViews.
void setBoolean(int viewId, String methodName, boolean value)
Call a method taking one boolean on a view in the layout for this RemoteViews.
void setByte(int viewId, String methodName, byte value)
Call a method taking one byte on a view in the layout for this RemoteViews.
void setChar(int viewId, String methodName, char value)
Call a method taking one char on a view in the layout for this RemoteViews.
void setCharSequence(int viewId, String methodName, CharSequence value)
Call a method taking one CharSequence on a view in the layout for this RemoteViews.
void setChronometer(int viewId, long base, String format, boolean started)
Equivalent to calling Chronometer.setBase, Chronometer.setFormat, and Chronometer.start() or Chronometer.stop().
void setDouble(int viewId, String methodName, double value)
Call a method taking one double on a view in the layout for this RemoteViews.
void setFloat(int viewId, String methodName, float value)
Call a method taking one float on a view in the layout for this RemoteViews.
void setImageViewBitmap(int viewId, Bitmap bitmap)
等同於調用ImageView.setImageBitmap方法,從Bitmap對象中設定一個圖片
void setImageViewResource(int viewId, int srcId)
等同於調用ImageView.setImageResource,從一個資源中設定圖片
void setImageViewUri(int viewId, Uri uri)
等同於調用ImageView.setImageURI,從URI中設定映像
void setInt(int viewId, String methodName, int value)
Call a method taking one int on a view in the layout for this RemoteViews.
void setLong(int viewId, String methodName, long value)
Call a method taking one long on a view in the layout for this RemoteViews.
void setOnClickPendingIntent(int viewId, PendingIntent pendingIntent)
Equivalent to calling setOnClickListener(android.view.View.OnClickListener) to launch the provided PendingIntent.
void setProgressBar(int viewId, int max, int progress, boolean indeterminate)
等同於調用ProgressBar.setMax, ProgressBar.setProgress, and ProgressBar.如果indeterminate為true則進度條的最大和最小進度將會忽略
void setShort(int viewId, String methodName, short value)
Call a method taking one short on a view in the layout for this RemoteViews.
void setString(int viewId, String methodName, String value)
Call a method taking one String on a view in the layout for this RemoteViews.
void setTextColor(int viewId, int color)
等同於setTextColor(int).,設定文本的顏色
void setTextViewText(int viewId, CharSequence text)
等同於TextView.setText,設定常值內容
void setUri(int viewId, String methodName, Uri value)
Call a method taking one Uri on a view in the layout for this RemoteViews.
void setViewVisibility(int viewId, int visibility)
等同於調用View.setVisibility,設定該ID控制項的可見度
void writeToParcel(Parcel dest, int flags)
Flatten this object in to a Parcel.
無論你自訂的RemoteView有多少方法,但它終究是一個RemoteViews,所以在AppWidget中使用RemoteViews(無論是否是自訂的)只能調用以上提供的方法。現在你一定很疑惑,那麼是不是AppWidget不能調用自訂RemoteViews裡的方法呢?能,當然能,並且還可以將RemoteViews裡的結果反饋到AppWidget,實現AppWidget和RemoteViews的互動,只不過這種方法實現起來比較複雜,容我慢慢道來。
以上方法中,有幾個特殊的方法
void  java.lang.String, android.graphics.Bitmap) setBitmap(int viewId,  String methodName,  Bitmap value)
void  java.lang.String, boolean) setBoolean(int viewId,  String methodName, boolean value)
void  java.lang.String, byte) setByte(int viewId,  String methodName, byte value)
void  java.lang.String, char) setChar(int viewId,  String methodName, char value)
void  java.lang.String, java.lang.CharSequence) setCharSequence(int viewId,  String methodName,  CharSequence value)
void  java.lang.String, double) setDouble(int viewId,  String methodName, double value)
void  java.lang.String, float) setFloat(int viewId,  String methodName, float value)
void  java.lang.String, android.os.Bundle) setBundle(int viewId,  String methodName,  Bundle value)
這些方法是通往自訂RemoteViews的介面。它們都包含3個參數,第一個參數 viewId是你所要調用RemoteViews在布局檔案裡定義的Id,第二個參數 methodName是你要調用的RemoteViews裡的方法名,如你要調用RemoteViews裡的一個setName(String name),第二個參數你就可以設定為“setName”;第三個參數是你所要調用的方法的參數,以setName(String name)方法為例,你必須傳入一個String的參數,則在這裡調用void  java.lang.String, java.lang.CharSequence) setCharSequence(int viewId,  String methodName,  CharSequence value) ,因為只有這個方法的第三個參數是String類型。下面看一個執行個體:
[java]
/*自訂的RemoteView*/ 
 
@RemoteView 
 
public class CalendarView extends View { 
 
... 
 
    /@hide*/ 
 
    @android.view.RemotableViewMethod 
    public void setTextSize(Bundle bundle){ 
    mLineSize = bundle.getInt("LineSize", 1); 
    mDigitalSize = bundle.getInt("DigitalSize", 2); 
    } 
... 
 
} 
 
/* AppWidget 如何調用*/  www.2cto.com
 
... 
 
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.4  AppWidget接收來自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使用差別不大,先看布局檔案:
[java] 
<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裡,樣本如下:
[java] 
/@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其實只是為了匹配
void  java.lang.String, java.lang.CharSequence) setCharSequence(int viewId,  String methodName,  CharSequence value)的第三個參數。在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做重新整理。


作者:fangchongbory

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.