2016年3月1日 星期二

Create widget(With list view) with remote view service and remote view Factory

Create widget with remote view service and remote view Factory
the remote View factor is a cursor adapter for remote view


create the click

now start
step1 create a Detail widget Layout


detail_widget.xml


example code:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   android:padding="@dimen/widget_margin">


   <LinearLayout
       android:orientation="vertical"
       android:layout_width="match_parent"
       android:layout_height="match_parent">


       <LinearLayout
           android:layout_width="match_parent"
           android:id="@+id/detail_widget_bar"
           android:layout_height="@dimen/detail_widget_bar_height"
           android:background="@color/primary_light"
           android:orientation="horizontal">


           <ImageView
               android:layout_width="wrap_content"
               android:layout_height="wrap_content"
               android:src="@drawable/ic_clear"/>


           <ImageView
               android:layout_width="wrap_content"
               android:layout_height="wrap_content"
               android:src="@drawable/ic_logo"/>
       </LinearLayout>


       <ListView
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:id="@+id/detail_widget_list" />


   </LinearLayout>


</FrameLayout>

detail_widget_list_iteam.xml


example code:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   android:padding="@dimen/widget_margin">


   <LinearLayout
       android:orientation="vertical"
       android:layout_width="match_parent"
       android:layout_height="match_parent">


       <LinearLayout
           android:layout_width="match_parent"
           android:id="@+id/detail_widget_bar"
           android:layout_height="@dimen/detail_widget_bar_height"
           android:background="@color/primary_light"
           android:orientation="horizontal">


           <ImageView
               android:layout_width="wrap_content"
               android:layout_height="wrap_content"
               android:src="@drawable/ic_clear"/>


           <ImageView
               android:layout_width="wrap_content"
               android:layout_height="wrap_content"
               android:src="@drawable/ic_logo"/>
       </LinearLayout>


       <ListView
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:id="@+id/detail_widget_list" />


   </LinearLayout>


</FrameLayout>




step2 AppwidgetProvider info


example code:


<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
   android:initialKeyguardLayout="@layout/detail_widget"
   android:initialLayout="@layout/detail_widget"
   android:minHeight="250dp"
   android:minWidth="180dp"
   android:resizeMode="vertical"
   android:updatePeriodMillis="0"
   android:widgetCategory="home_screen"/>


step3 Appwidget provioder


public class DetailWidget extends AppWidgetProvider {


   @Override
   public void onReceive(Context context, Intent intent) {
       super.onReceive(context, intent);
       if (SunshineSyncAdapter.ACTION_DATA_UPDATED.equals(intent.getAction())) {
           Intent startintent = new Intent(context, DetailWidgetIntentService.class);
           context.startService(startintent);
       }
   }


   @Override
   public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
       Intent intent = new Intent(context, DetailWidgetIntentService.class);
       context.startService(intent);


   }


   @Override
   public void onEnabled(Context context) {
       // Enter relevant functionality for when the first widget is created
   }


   @Override
   public void onDisabled(Context context) {
       // Enter relevant functionality for when the last widget is disabled
   }
}


DetailWidgetIntentService

public class DetailWidgetIntentService extends IntentService {


   /**
    * Creates an IntentService.  Invoked by your subclass's constructor.
    *
    * @param name Used to name the worker thread, important only for debugging.
    */
   public DetailWidgetIntentService(String name) {
       super(name);
   }


   static void updateAppWidget(Context context, AppWidgetManager appWidgetManager,
                               int appWidgetId) {

       // Construct the RemoteViews object
       RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.detail_widget);

       // Instruct the widget manager to update the widget
       appWidgetManager.updateAppWidget(appWidgetId, views);
   }


   @Override
   protected void onHandleIntent(Intent intent) {
       Context context=this;
       AppWidgetManager appWidgetManager=AppWidgetManager.getInstance(this);
       int[] appWidgetIds = appWidgetManager.getAppWidgetIds(new ComponentName(this, DetailWidget.class));
       for (int appWidgetId : appWidgetIds) {
           updateAppWidget(context, appWidgetManager, appWidgetId);
       }
   }

}





step4 change the Manifest file


<receiver android:name=".widget.DetailWidget">
           <intent-filter>
               <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
               <action android:name="com.example.android.sunshine.app.ACTION_DATA_UPDATED" />
           </intent-filter>


           <meta-data
               android:name="android.appwidget.provider"
               android:resource="@xml/detail_widget_info" />
       </receiver>

step5 How to make the ListView in the widget show the data?



Normally,we use the ListView,we need the Adapter.In the widget,we use RemoteViewsFactory.
so we create a 1.class extends RemoteViewsFactory first

public class DetailWidgetListAdapter implements RemoteViewsService.RemoteViewsFactory {
   private Context context;
   public DetailWidgetListAdapter(Context context) {
       this.context=context;
   }


   private int[] data;
   @Override
   public void onCreate() {
       data=new int[]{1,2,3,4,5,6};


   }


   @Override
   public void onDataSetChanged() {


   }


   @Override
   public void onDestroy() {


   }


   @Override
   public int getCount() {
       return data.length;
   }


   @Override
   public RemoteViews getViewAt(int position) {
       RemoteViews rview=new RemoteViews(context.getPackageName(), R.layout.detail_widget_list_iteam);
       rview.setTextViewText(R.id.detail_widget_list_item_date_textview,data[position]+"");
       return rview;
   }


   @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 false;
   }
}


the getView at cretae the remote view for the ListView iteam




how we set the RemoteViewsFactory(adapter) to the ListView in the wiget?we need RemoteViewsService first.
2.create a class implements RemoteViewsService


public class MyWidgetService extends RemoteViewsService {
   @Override
   public RemoteViewsFactory onGetViewFactory(Intent intent) {
       Log.d("DetailonUpdate","onGetViewFactory");
       return new DetailWidgetListAdapter(getApplicationContext());
   }
}

it is simple now,just return,it is not running in the UI thread,so we can do some long work at here.e.e. get data from content provider

how to 3.set a adapter to the list view?


RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.detail_widget);


//set the array adapter
           Intent intent = new Intent(context, MyWidgetService.class);
           // Add the app widget ID to the intent extras.
           intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
           intent.setData(Uri.parse(intent.toUri(Intent.URI_INTENT_SCHEME)));

           // Instantiate the RemoteViews object for the App Widget layout.


           // Set up the RemoteViews object to use a RemoteViews adapter.
           // This adapter connects
           // to a RemoteViewsService  through the specified intent.
           // This is how you populate the data.
           if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
               Log.d("DetailonUpdate", "ice");
               views.setRemoteAdapter( R.id.detail_widget_list2, intent);
//                rv.setRemoteAdapter(appWidgetId, R.id.detail_widget_list, intent);
           }
           // Instruct the widget manager to update the widget
           appWidgetManager.updateAppWidget(appWidgetId, views);

The import ant thing is we need t add 4.the promise to the WidgetService


<service
           android:name=".widget.MyWidgetService"
           android:exported="false"
           android:permission="android.permission.BIND_REMOTEVIEWS" >
       </service>

it can show 1-6 no!!!!


step 6 make the widget to how the content provider data
now,we wnat the list in the widget to show the content provider data
so,we need to use the content resolver at the RemoteViewsService(because not in the UI thread),to get the data


1.get the cursor


private static final String[] FORECAST_COLUMNS = {
           // In this case the id needs to be fully qualified with a table name, since
           // the content provider joins the location & weather tables in the background
           // (both have an _id column)
           // On the one hand, that's annoying.  On the other, you can search the weather table
           // using the location set by the user, which is only in the Location table.
           // So the convenience is worth it.
           WeatherContract.WeatherEntry.TABLE_NAME + "." + WeatherContract.WeatherEntry._ID,
           WeatherContract.WeatherEntry.COLUMN_DATE,
           WeatherContract.WeatherEntry.COLUMN_SHORT_DESC,
           WeatherContract.WeatherEntry.COLUMN_MAX_TEMP,
           WeatherContract.WeatherEntry.COLUMN_MIN_TEMP,
           WeatherContract.LocationEntry.COLUMN_LOCATION_SETTING,
           WeatherContract.WeatherEntry.COLUMN_WEATHER_ID,
           WeatherContract.LocationEntry.COLUMN_COORD_LAT,
           WeatherContract.LocationEntry.COLUMN_COORD_LONG
   };



String sortOrder = WeatherContract.WeatherEntry.COLUMN_DATE + " ASC";
       String locationSetting = Utility.getPreferredLocation(this);
       Uri weatherForLocationUri = WeatherContract.WeatherEntry.buildWeatherLocationWithStartDate(
               locationSetting, System.currentTimeMillis());


       ContentResolver resolver = getContentResolver();
       Cursor cursor = resolver.query(weatherForLocationUri, FORECAST_COLUMNS, null, null, sortOrder);

2. give the cursor to the DetailWidgetListAdapter(RemoteViewsFactory)


in  public class DetailWidgetListAdapter implements RemoteViewsService.RemoteViewsFactory,change this:

private Context mcontext;
   private Cursor mcursor;
   private int[] data;


   public DetailWidgetListAdapter(Context context, Cursor cursor) {
       this.mcontext = context;
       this.mcursor = cursor;
   }


   public Cursor getMcursor() {
       return mcursor;
   }


also it is because when onDataSetChanged and  onDestroy we will use other or will not use the same cursor object again,so need to set the mcursor=null;


   @Override
   public void onDestroy() {
       if(mcursor!=null)
           mcursor=null;
   }

important!!!!!!however,we can not do this:


public void onDataSetChanged() {
       Log.d("DetailonUpdate","onDataSetChanged");
       if (mcursor != null)
           mcursor = null;


   }
this make the cursor input become 0,because:
RemoteViewsFactory create,RemoteViewsFactory run first




3. make the list view in the widget show the data in cursor
that mean we need to change the getViewAt method.


we nee to get the datathat we need in the cursor


/*because  private static final String[] FORECAST_COLUMNS = {
           // In this case the id needs to be fully qualified with a table name, since
           // the content provider joins the location & weather tables in the background
           // (both have an _id column)
           // On the one hand, that's annoying.  On the other, you can search the weather table
           // using the location set by the user, which is only in the Location table.
           // So the convenience is worth it.
           WeatherContract.WeatherEntry.TABLE_NAME + "." + WeatherContract.WeatherEntry._ID,
           WeatherContract.WeatherEntry.COLUMN_DATE,
           WeatherContract.WeatherEntry.COLUMN_SHORT_DESC,
           WeatherContract.WeatherEntry.COLUMN_MAX_TEMP,
           WeatherContract.WeatherEntry.COLUMN_MIN_TEMP,
           WeatherContract.LocationEntry.COLUMN_LOCATION_SETTING,
           WeatherContract.WeatherEntry.COLUMN_WEATHER_ID,
           WeatherContract.LocationEntry.COLUMN_COORD_LAT,
           WeatherContract.LocationEntry.COLUMN_COORD_LONG
   };*/
   static final int COL_WEATHER_ID = 0;
   static final int COL_WEATHER_DATE = 1;
   static final int COL_WEATHER_DESC = 2;
   static final int COL_WEATHER_MAX_TEMP = 3;
   static final int COL_WEATHER_MIN_TEMP = 4;
   static final int COL_LOCATION_SETTING = 5;
   static final int COL_WEATHER_CONDITION_ID = 6;
   static final int COL_COORD_LAT = 7;
   static final int COL_COORD_LONG = 8;

private RemoteViews binData(RemoteViews rview, Cursor mCursor) {
       Log.d("DetailonUpdate","binData");
       long dateInMillis = mCursor.getLong(COL_WEATHER_DATE);
       double high = mCursor.getDouble(COL_WEATHER_MAX_TEMP);
       double low = mCursor.getDouble(COL_WEATHER_MIN_TEMP);
       int weatherId = mCursor.getInt(COL_WEATHER_CONDITION_ID);
       int defaultImage = Utility.getIconResourceForWeatherCondition(weatherId);


       //set the icon
       rview.setImageViewResource(R.id.detail_widget_list_item_icon, defaultImage);
       //set the date
       rview.setTextViewText(R.id.detail_widget_list_item_date_textview, Utility.getFriendlyDayString(mcontext, dateInMillis));


       //set the weather desc
       String description = Utility.getStringForWeatherCondition(mcontext, weatherId);
       rview.setTextViewText(R.id.detail_widget_list_item_forecast_textview, description);
       //set the high temp
       String highString = Utility.formatTemperature(mcontext, high);
       rview.setTextViewText(R.id.detail_widget_list_item_high_textview, highString);


       //set the low temp
       String lowString = Utility.formatTemperature(mcontext, low);
       rview.setTextViewText(R.id.detail_widget_list_item_low_textview, lowString);


       return rview;
   }


@Override
   public RemoteViews getViewAt(int position) {
       Log.d("DetailonUpdate","getViewAt");
       RemoteViews rview = new RemoteViews(mcontext.getPackageName(), R.layout.detail_widget_list_iteam);
       mcursor.moveToPosition(position);
       return binData(rview, mcursor);
   }

now,it can show the iteam




however,it can not click.

3.2 it can't update correctly when the sql database 
update:see:http://pomprogrammer.blogspot.com/2016/03/list-view-in-widgetwhen-data.html


step7 handle the click


1.we want click the bar ,go to main activity
in DetailWidget onUpdate method:


RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.detail_widget);
Intent startMainintent=new Intent(context, MainActivity.class);
           PendingIntent pendingIntent=PendingIntent.getActivity(context,0,startMainintent,0);
           views.setOnClickPendingIntent(R.id.detail_widget_bar,pendingIntent);


2.we want click the iteam ,go to the right detail
2.1先用setPendingIntentTemplate方法为collection整体的点击设置一个处理的PendingIntent


in AppWidgetProvider >onUpdate


Intent clickIntentTemple=new Intent(context, DetailActivity.class);
           PendingIntent pendingIntentTemplate= TaskStackBuilder.create(context).addNextIntentWithParentStack(clickIntentTemple).getPendingIntent(0,PendingIntent.FLAG_UPDATE_CURRENT);
           views.setPendingIntentTemplate(R.id.detail_widget_list2,pendingIntentTemplate);


because we want to start DetailActivity.class,so DetailActivity.class in the intent


The meaninig of PendingIntent.FLAG_UPDATE_CURRENT
這個FLAG 的意思就是:如果系統中已存在該PendingIntent對象,那麼系統將保留該PendingIntent對象,但是會使用新的Intent來更新之前PendingIntent中的Intent對像數據,例如更新Intent中的Extras。

2.2然后通过RemoteViewsFactory使用setOnClickFillInIntent为collection视图中的每一项传入一个与该项相关的Intent。该Intent会被合入处理时接收到Intent中。

in DetailWidgetListAdapter implements RemoteViewsService.RemoteViewsFactory>getViewat

//set the iteam click intent
       Intent fillintent = new Intent();
       String locationSetting = Utility.getPreferredLocation(mcontext);
       Uri contentUri = WeatherContract.WeatherEntry.buildWeatherLocationWithDate(locationSetting, dateInMillis);
       fillintent.setData(contentUri);
       rview.setOnClickFillInIntent(R.id.detail_widget_list_iteam,fillintent);

it is because we already at 2.1 set the destination activity Detail activity,so we do not need to set it again.we just set the data that we want ot send to the activity.

now! when we click the iteam,we can go the the relevent detail


nut here is a problem,because our app have tablet mode and phone mode,in tablet mode ,we do not have detail activity,we only have one page,so, have error.


ste8 solve the tablet mode problrm
we can set always go to main activity in tablet mode
how?
in normal
<?xml version="1.0" encoding="utf-8"?>
<resources>
   <bool name="tabletmode">false</bool>
</resources>


in sw 600dp
<?xml version="1.0" encoding="utf-8"?>
<resources>
   <bool name="tabletmode">true</bool>
</resources>

in stpe7 step 1


change to
if(isTabletmode){
               //in tablety mode
               Intent liststartMainintent=new Intent(context, MainActivity.class);
               PendingIntent listpendingIntent=PendingIntent.getActivity(context,0,liststartMainintent,0);
               views.setOnClickPendingIntent(R.id.detail_widget_list2,listpendingIntent);
           }else{
               //int phone mode
               Intent clickIntentTemple=new Intent(context, DetailActivity.class);
               PendingIntent pendingIntentTemplate= TaskStackBuilder.create(context).addNextIntentWithParentStack(clickIntentTemple).getPendingIntent(0,PendingIntent.FLAG_UPDATE_CURRENT);
               views.setPendingIntentTemplate(R.id.detail_widget_list2,pendingIntentTemplate);
           }


in stpe7 step 2


change to


if(!isTabletmode){
           Intent fillintent = new Intent();
           String locationSetting = Utility.getPreferredLocation(mcontext);
           Uri contentUri = WeatherContract.WeatherEntry.buildWeatherLocationWithDate(locationSetting, dateInMillis);
           fillintent.setData(contentUri);
           rview.setOnClickFillInIntent(R.id.detail_widget_list_iteam,fillintent);

       }

Make the widget show the update data from the content provider

Make the widget show the update data from the content provider


Cncept:
WidgetProvider’s onUpdate() method use to uppdate the view of the widget
WidgetProvider is a brodcastreciver.
it receive the brodcast from the system,than run onReceive,the onReceive run onUpdate().
These are run in the UI thread.
So,we should not run some heavy task(need long time) in this two method.
We can use Intent Service.top get the data


the data flow is
.


we only know how  to get the data after the WidgetProvider receive a brodcast message from thesystem.but!!! how to make the system to send a brodcast when the data change?


we use sync adapter in this app.
we can make,when the syncadapter performsync,it can send a data changed brodcast.
than the WidgetProvider receive the brodcast,perform onReceive->onUpdate()->Intent service get the data from content provider.


to start the intent service, to get the data


This is the whole concept:
now,start to code!!!!
step1 create a IntentService
override the onHandleIntent method


step2 mevoe the code in onUpdate in TodayWidgetProvider to onHandleIntent to WidgetIntentService.
problem:
how to get AppWidgetManager in a intentservice?


AppWidgetManager appWidgetManager=AppWidgetManager.getInstance(this);


how to get appWidgetIds in a intentservice?
int[] appWidgetIds=appWidgetManager.getAppWidgetIds(new ComponentName(this, TodayWidgetProvider.class));


step3 in onUpdate in TodayWidgetProvider,start the service.


public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
       super.onUpdate(context, appWidgetManager, appWidgetIds);
       Intent serviceIntent=new Intent(context,WidgetIntentService.class);
      context.startService(serviceIntent);
   }


step4 because we want the sysnc adapter send brodcast when has new data


basic concept :how to send brodcast?


Intent i = new Intent("com.hmkcode.android.USER_ACTION");
sendBroadcast(i);


we want to send “com.example.android.sunshine.app.ACTION_DATA_UPDATED"”


so,in the SunshineSyncAdapter,under the notifyWeather(),because here the new weather data is in the SQL database now,call a method updateWidgets();


updateWidgets():
in this method,we send the bordcast


private void updateWidgets() {
       Context context=getContext();


       Intent intent=new Intent(INTENT_ACTION);
       context.sendBroadcast(intent);
   }

however,we only want our app can receive the brodcast:


只不過 Share Intent 會自動幫我們撈出目前安裝的 App 中,有哪些 App 是能夠支援我們所要發送的請求的;而透過指定特定 App 發送資料,就得必須要先知道該 App 的 Package Name 為何,才有辦法在建立 Intent 的時候指派進去,系統才知道屆時直接呼叫起這個 App 來發送,另外,如此一來也能夠利用


our pacakage name is


com.example.android.sunshine.app

private void updateWidgets() {
       Context context=getContext();


       Intent intent=new Intent(INTENT_ACTION);
//getPackageName Return the name of this application's package.
       intent.setPackage(context.getPackageName());
       context.sendBroadcast(intent);
   }

now, only our app can receive the brodcast.

step5:register the TodayWidgetProvider with "com.example.android.sunshine.app.ACTION_DATA_UPDATED"


now the app send brodcast ,but TodayWidgetProvider  can’t receive it,we need t o register it first in Manifest.xml


<receiver
           android:name=".widget.TodayWidgetProvider"
           android:label="@string/title_widget_today">
           <intent-filter>
               <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
               <action android:name="com.example.android.sunshine.app.ACTION_DATA_UPDATED" />
           </intent-filter>


step5 override the onReceive in TodayWidgetProvider


first code onUpdate first,:


Intent serviceIntent = new Intent(context, WidgetIntentService.class);
       context.startService(serviceIntent);


explain :start the intent service to give the view to the widget.


how ever,the onUpdate  only call onw time, because in widget_info_today.xml ,we set  android:updatePeriodMillis="0", that mean the system will not send again the android.appwidget.action.APPWIDGET_UPDATE to make the onUpdate(),unless the first time the widget created.


brodcast with android.appwidget.action.APPWIDGET_UPDATE will call onReceive ."com.example.android.sunshine.app.ACTION_DATA_UPDATED" will call onReceive too.


now ,code the onReceive :

  @Override
   public void onReceive(Context context, Intent intent) {
       super.onReceive(context, intent);
       if(intent.getAction().equals(SunshineSyncAdapter.INTENT_ACTION)){
           Intent serviceIntent = new Intent(context, WidgetIntentService.class);
           context.startService(serviceIntent);
       }
       


   }

brodcast with com.example.android.sunshine.app.ACTION_DATA_UPDATED will call onReceive ."android.appwidget.action.APPWIDGET_UPDATE" will call onReceive too.,sho we need to ensure is android.appwidget.action.APPWIDGET_UPDATE


now,the intent service can call correctly,however,we don’t query the data in it,now do it:


/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*      http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.android.sunshine.app.widget;


import android.annotation.TargetApi;
import android.app.IntentService;
import android.app.PendingIntent;
import android.appwidget.AppWidgetManager;
import android.content.ComponentName;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.os.Build;
import android.widget.RemoteViews;


import com.example.android.sunshine.app.MainActivity;
import com.example.android.sunshine.app.R;
import com.example.android.sunshine.app.Utility;
import com.example.android.sunshine.app.data.WeatherContract;


/**
* IntentService which handles updating all Today widgets with the latest data
*/
public class WidgetIntentService extends IntentService {
   private static final String[] FORECAST_COLUMNS = {
           WeatherContract.WeatherEntry.COLUMN_WEATHER_ID,
           WeatherContract.WeatherEntry.COLUMN_SHORT_DESC,
           WeatherContract.WeatherEntry.COLUMN_MAX_TEMP
   };
   // these indices must match the projection
   private static final int INDEX_WEATHER_ID = 0;
   private static final int INDEX_SHORT_DESC = 1;
   private static final int INDEX_MAX_TEMP = 2;


   public WidgetIntentService() {
       super("TodayWidgetIntentService");
   }


   @Override
   protected void onHandleIntent(Intent intent) {
       // Retrieve all of the Today widget ids: these are the widgets we need to update
       AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(this);
       int[] appWidgetIds = appWidgetManager.getAppWidgetIds(new ComponentName(this,
               TodayWidgetProvider.class));


       // Get today's data from the ContentProvider
       String location = Utility.getPreferredLocation(this);
       Uri weatherForLocationUri = WeatherContract.WeatherEntry.buildWeatherLocationWithStartDate(
               location, System.currentTimeMillis());
       Cursor data = getContentResolver().query(weatherForLocationUri, FORECAST_COLUMNS, null,
               null, WeatherContract.WeatherEntry.COLUMN_DATE + " ASC");
       if (data == null) {
           return;
       }
       if (!data.moveToFirst()) {
           data.close();
           return;
       }


       // Extract the weather data from the Cursor
       int weatherId = data.getInt(INDEX_WEATHER_ID);
       int weatherArtResourceId = Utility.getArtResourceForWeatherCondition(weatherId);
       String description = data.getString(INDEX_SHORT_DESC);
       double maxTemp = data.getDouble(INDEX_MAX_TEMP);
       String formattedMaxTemperature = Utility.formatTemperature(this, maxTemp);
       data.close();


       // Perform this loop procedure for each Today widget
       for (int appWidgetId : appWidgetIds) {
           int layoutId = R.layout.widget_today_small;
           RemoteViews views = new RemoteViews(getPackageName(), layoutId);


           // Add the data to the RemoteViews
           views.setImageViewResource(R.id.widget_icon, weatherArtResourceId);
           // Content Descriptions for RemoteViews were only added in ICS MR1
           if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) {
               setRemoteContentDescription(views, description);
           }
           views.setTextViewText(R.id.widget_high_temperature, formattedMaxTemperature);


           // Create an Intent to launch MainActivity
           Intent launchIntent = new Intent(this, MainActivity.class);
           PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, launchIntent, 0);
           views.setOnClickPendingIntent(R.id.widget, pendingIntent);


           // Tell the AppWidgetManager to perform an update on the current app widget
           appWidgetManager.updateAppWidget(appWidgetId, views);
       }
   }


   @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1)
   private void setRemoteContentDescription(RemoteViews views, String description) {
       views.setContentDescription(R.id.widget_icon, description);
   }
}




Choose the Size od widget
the widget can chang the size,and show differnt layout


how can we kown what layout do we use?



step1,we create three layout


step2 change the widget_info_today.xml
Although we create 3 layout,but we can’t chnage the size of the widget,how can  we make it can change the size?
the orginal content


<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
   android:initialLayout="@layout/widget_today_small"
   android:minHeight="@dimen/widget_today_default_height"
   android:minWidth="@dimen/widget_today_default_width"
   android:updatePeriodMillis="0" />



minWidth and minHeight is the default value of the widget


add android:resizeMode="horizontal"


now,it can resize horizontal


add:android:minResizeWidth="40dp"


and change android:minWidth="110dp"


so the default width is 2box(110dp)
the miniwidth after resize is 1 box(40dp)

finall code:


<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
   android:initialLayout="@layout/widget_today_small"
   android:minHeight="@dimen/widget_today_default_height"
   android:minWidth="110dp"
   android:resizeMode="horizontal"
   android:minResizeWidth="@dimen/widget_today_default_width"
   android:updatePeriodMillis="0" />

step3 load different layout from depend on the size of the width
override the method onAppWidgetOptionsChanged

4.0?
no way


4.1+:
int i = newOptions.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH); can get the width


so,now we can get different layout now.
the concept is use the Intent the width to the service,so,the servcie know the width,than it can use the width to load different data and different layout