2016年3月1日 星期二

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

2016年2月28日 星期日

No divider in List view



<ListView
            android:id="@+id/detail_widget_list2"
            android:divider="@null"
            android:dividerHeight="0dp"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />

Create a Widget for a app

Create a Widget for a app


a Widget  depend on a application
it make by two part:
appwidgetprovider and appwidgetprovider xml
appwidgetprovider xml:
describe your widget attribute:size,can reszie?,preview image


appwidgetprovider :
hold all the logic for create the widget view
fill the view with correct data
set the click listener


we can have not only one widget instance


image that,sunschine can have many widget,that support different location


now,start to code





the widget we can think that it is a remote view of the app,it put on the launcger.
In the remote view,it only can content the view original in the android,can not have custom view













if need to click


in manifest xml


now ,we want to create a widget with  static data


1.Layout
important***In the remote view,it only can content the view original in the android,can not have custom view


we crate a layout call widget_today_small
there is a problem,it is because
<4.0the mergian need to make by urself
>=4.0,auto have
how to make the widget show the same in different system?


we know that the auto margin is 8dp
so we can add widget_margin in dimens.xml:,if>=v14 widget_margin =0
if <v-14,widget_margin =8dp


like this


res/values/dimens.xml:
<dimen name="widget_margin">8dp</dimen>
res/values-v14/dimens.xml:
<dimen name="widget_margin">0dp</dimen>


1.now we can make the layout:


<?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:padding="@dimen/widget_margin">


   <LinearLayout
       android:id="@+id/widget"
       android:layout_width="match_parent"
       android:layout_height="match_parent"
       android:background="@color/primary"


       android:orientation="vertical">


       <ImageView
           android:id="@+id/widget_icon"
           android:layout_width="wrap_content"
           android:layout_height="0dp"
           android:layout_gravity="center_horizontal"
           android:layout_weight="1"
           android:src="@drawable/art_clear" />


       <TextView
           android:id="@+id/widget_high_temperature"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:layout_gravity="center_horizontal"
           android:text="25"
           android:textAppearance="?android:textAppearanceLarge"
           android:textColor="@color/primary_text" />
   </LinearLayout>


</FrameLayout>


2.AppwidgetProvider info
n is the number of the box


set the preview image :
android:previewImage="@drawable/preview"


we create widget_info_today.xml in rex/xml


<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" />


How often, in milliseconds, that this AppWidget wants to be updated. will call onUpdate,
it is because now the widget is static 0,or the app will updateit,show is 0.


<dimen name="widget_today_default_width">40dp</dimen>
<dimen name="widget_today_default_height">40dp</dimen>
it is because n=1,so 40dp


3.Appwidget provioder
step1,create a class call   TodayWidgetProvider extends AppWidgetProvider


step 2 override onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds)


Called in response to the {@link AppWidgetManager#ACTION_APPWIDGET_UPDATE} and
    * {@link AppWidgetManager#ACTION_APPWIDGET_RESTORED} broadcasts


it is because a widget may have different instance,each instance have a id,so the int[] appWidgetIds hold all the id




4.for all the weidget,give a view to them, and add onClick to them
this is the code example:


public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
       super.onUpdate(context, appWidgetManager, appWidgetIds);
       for (int appWidgetId : appWidgetIds) {
           RemoteViews remoteV = new RemoteViews(context.getPackageName(), R.layout.widget_today_small);


           Intent intent = new Intent(context, MainActivity.class);
           PendingIntent pendintent = PendingIntent.getActivity(context, 0, intent, 0);
           remoteV.setOnClickPendingIntent(R.id.widget, pendintent);
           appWidgetManager.updateAppWidget(appWidgetId, remoteV);


       }
   }


how to set the text and img src?
need to use the remote view specical method:
//            set the text
           remoteV.setTextViewText(R.id.widget_high_temperature, Utility.formatTemperature(context,10));
//            set the image
           remoteV.setImageViewResource(R.id.widget_icon,R.drawable.art_clear);

5.change the Manifest file


<receiver
           android:name="TodayWidgetProvider"
           android:label="@string/title_widget_today">
           <intent-filter>
               <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
           </intent-filter>
           <meta-data
               android:name="android.appwidget.provider"
               android:resource="@xml/widget_info_today" />
       </receiver>


TodayWidgetProvider is a Brodcast receiver, when it receive a brodcast have action(android.appwidget.action.APPWIDGET_UPDATE),
The AppWidgetManager automatically sends all other App Widget broadcasts to the AppWidgetProvider as necessary.
the OnUpdat will run,so we need to declare these.


the project code:



but,now,it can only show the static data,How can it show the update data form the content provider?Next we will talk.