terça-feira, 24 de janeiro de 2012

Criando um App Widget


Olá. Estou criando um widget para o Amor por SMS, então irei aproveitar e fazer este tutorial.
O que é um app widget? É uma funcionalidade de um aplicativo que pode ser adicionada na tela principal (home) e é atualizada periodicamente, exibindo informação e/ou recebendo interação com o usuário.

Iniciamente vamos declarar nosso AppWidgetProvider no AndroidManifest.xml entre a tag <application>:


<receiver android:name="MeuAppWidgetProvider" >
    <intent-filter>
        <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
    </intent-filter>
    <meta-data android:name="android.appwidget.provider"
               android:resource="@xml/appwidget_info" />
</receiver>

<receiver> especifica o nome do AppWidgetProvider, que iremos definir em breve. O <intent-filter> declara a ação que o widget irá receber por broadcast, no caso APPWIDGET_UPDATE, que é a atualização periodica do widget. O <meta-data> especifica o  AppWidgetProviderInfo que devemos fornecer e que atribui propriedades básicas do widget tais com largura e altura mínima, periodicidade do update, entre outros. Vamos então criar o arquivo appwidget_info.xml na pasta res/xml/ (se essa pasta não existir, crie-a):


<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
    android:minWidth="294dp"
    android:minHeight="72dp"
    android:updatePeriodMillis="86400000"
    android:previewImage="@drawable/preview"
    android:initialLayout="@layout/meu_appwidget"
    android:configure="com.example.android.MeuAppWidgetConfigure" 
    android:resizeMode="horizontal|vertical">
</appwidget-provider>


Ok, vamos por partes:

  • android:minWidthandroid:minHeight especificam o tamanho minimo necessário para o widget;
  • android:updatePeriodMillis é a periodicidade da atualização em milisegundos (86400000 = 24h);
  • android:previewImage é uma pre-visualização de como o widget ficará depois de configurado (acho que só é utilizado para versões 3.0+), use esse aplicativo para tirar a foto do widget depois de pronto;
  • android:initialLayout é o layout do seu widget;
  • android:configure é uma activity de configuração do widget que você pode, ou não, criar e será lançada toda vez que colocarem um widget na tela;
  • android:resizeMode especifica em quais direções o widget pode ser redimensionado: horizontal, vertical ou none.

Vamos então criar o layout do widget, que é basicamente a criação de um layout para activitys com algumas restrições: apenas FrameLayoutLinearLayout e RelativeLayout são suportados como layouts e apenas AnalogClockButtonChronometerImageButtonImageViewProgressBarTextViewViewFlipperListViewGridViewStackView AdapterViewFlipper são suportados (seus descendentes não são) como widget (não confundir esses widget - que são funcionalidades da view - com app widget que estamos contruindo aqui para ser exibido na home).
É uma boa pratica deixar um espaço na borda do widget, assim ele não fica colado com as bordas da tela nem com outros widgets. O Android 4.0 já faz isso automaticamente, mas as versões anteriores não, então vamos implementar meu_appwidget.xml em /res/layout/ dessa forma:


<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:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal"
    android:background="@drawable/my_widget_background">
    <TextView 
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:text="Testando."/>
  </LinearLayout>
</FrameLayout>

O que importa aqui é o android:padding="@dimen/widget_margin" o qual iremos definir um para versões anteriores ao 4.0 e outra para a 4.0. O LinearLayout pode ser trocado por outro da sua preferencia, dentro das especificações, e o TextView está a titulo de teste. Vamos então criar os recursos de dimensão (se o arquivo já existe apenas adicione o campo):

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

Pronto! Agora vamos finalmente implementar nossa AppWidgetProvider, que é onde ficará todo o código do nosso widget, assim como registro de Listeners. AppWidgetProvider é uma extensão do BroadcastReceiver, que - caso você não saiba - recebe as mensagens de  Broadcast que são Intents enviadas para todos os aplicativos no sistema, então se você não quiser usar o AppWidgetProvider, apenas estenda um BroadcastReceiver e implemente o onReceive para Intents com ações começadas por 'ACTION_APPWIDGET_'.
AppWidgetProvider pode implementar os seguintes métodos:

  • onUpdateExecutado periodicamente no tempo definido em updatePeriodMillis pelo AppWidgetProviderInfo, e caso você não tenha definido uma activity de configuração ele também será chamado ao adicionar um novo widget na tela;
  • onDeletedExecutado quando um widget é deletado da home;
  • onEnabledExecutado quando um widget é adicionado pela primeira vez na tela. Útil para criação de banco de dados especifico do widget, ou chamada de um Service;
  • onDisabledExecutado quando a ultima instância do widget é deletado da home. Útil para desfazer coisas provisórias feitas em onEnabled;
  • onReceiveChamada que todo broadcast faz e que no nosso caso será chamada sempre antes das citadas acima.
Vamos então definir MeuAppWidgetProvider:


public class MeuAppWidgetProvider extends AppWidgetProvider {

    public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
        final int N = appWidgetIds.length;
        for (int i=0; i<N; i++) {
            int appWidgetId = appWidgetIds[i];

            RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.meu_appwidget);
            
            appWidgetManager.updateAppWidget(appWidgetId, views);
        }
    }
}

Como estamos apenas testando, só implementarei o onUpdate. É preciso lembrar que você pode ter mais de um instância de um widget na home, entretanto só ocorerrá a chama de onUpdate apenas uma vez, e para todas as instâncias. Cabe a você atualizar todas usando o parâmetro appWidgetIds que identifica cada uma. Por isso precisamos daquele for, assim o código seguinte é executado para todas as instâncias do widget. Registros de event handler (click de botão, etc...) é feito após a criação de views e usando ela para encontrar as views filhas.
Agora vamos tentar executar o aplicativo e colocar o widget. Se você fez tudo certo deve receber a seguinte mensagem ao tentar adicionar: "O aplicativo não foi instalado no seu telefone". Isso acontece pois no appwidget_info.xml definimos uma activity de configuração mas não a implementamos ainda. Então, a título de teste, retire a linha do android:configure e tente novamente. No meu caso obtive isso:

Seu resultado pode variar a depender do background definido.
Tudo certo então. Vamos implementar agora a MeuAppWidgetConfigure. Desfaça a mudança feita no ultimo passo (de excluir a linha android:configure do appwidget_info.xml). Ela também precisa ser declarada no AndroidManifest.xml assim como qualquer outra activity, adicione isso dentro da tag <application>:


<activity android:name=".MeuAppWidgetConfigure">
    <intent-filter>
        <action android:name="android.appwidget.action.APPWIDGET_CONFIGURE"/>
    </intent-filter>
</activity>

Vamos criar o layout da activity (meu_configure_appwidget.xml), que inicialmente vamos apenas colocar um botão de confirmar, mas você pode colocar o que quiser para configurar o widget de acordo com suas necessidades:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:oritentation="vertical">

  <Button
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:id="@+id/btn_confirm">
    android:text="Confirmar"/>
</LinearLayout>

E finalmente criar o MeuAppWidgetConfigure em si:

import android.app.Activity;
import android.appwidget.AppWidgetManager;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.RemoteViews;

public class MensagemAppWidgetConfigure extends Activity {
 
 private Button mConfirmar;
 private int mAppWidgetId;

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.mensagem_configure_appwidget);
  
  setResult(RESULT_CANCELED);
  
  Bundle extras = getIntent().getExtras();
  if (extras != null) {
      mAppWidgetId = extras.getInt(
              AppWidgetManager.EXTRA_APPWIDGET_ID,
              AppWidgetManager.INVALID_APPWIDGET_ID);
  }
  AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(this);
  RemoteViews views = new RemoteViews(getPackageName(),
    R.layout.mensagem_appwidget);
                //Registro de eventos do widget registrados aqui!
  appWidgetManager.updateAppWidget(mAppWidgetId, views);
  
  mConfirmar = (Button) findViewById(R.id.btn_confirm);
  
  mConfirmar.setOnClickListener(new View.OnClickListener() {
   
   @Override
   public void onClick(View v) {
    Intent resultValue = new Intent();
    resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mAppWidgetId);
    setResult(RESULT_OK, resultValue);
    finish();
   }
  });
 }
}

Algumas considerações:

  • setResult(RESULT_CANCELED) precisa ser setado no inicio, pois caso o usuário não confirme a configuração o widget não deve ser criado;
  • Pegamos o id do widget pela intent que foi usada para chamar a activity (extras.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID));
  • Fazemos basicamente o mesmo trabalho feito no onUpdate para configurar o widget;
  • Criamos a ação do botão de confirmar para terminar a configuração;
  • É obrigatório o resultado ser RESULT_OK com uma Intent contendo o id do widget!
Pronto! Agora você tem seu app widget funcionando, com uma activity de configuração.

6 comentários:

Anônimo at 4 de outubro de 2012 18:50 disse...

Diga Gustavo,

Segui todos os passos do seu tutorial mas continua aparecendo a mensagem "O aplicativo não está instalado!".

Gustavo Carvalho at 5 de outubro de 2012 16:29 disse...

@Anônimo: Onde aparece essa mensagem? Isso pode ser problema quanto ao app ser instalado no SD Card ou problema de assinatura quando você transformou o projeto em apk, talvez usando uma keystore diferente.

Daniel at 25 de fevereiro de 2013 14:08 disse...

Não compila, mostram 3 erros, não reconhece my_widget_background, na classe MeuAppWidgetProvider não é reconhecido R.layout.meu_appwidget, e acho que estou criando o template de xml errado para appwidget_info.xml, pois mostra erro no começo do arquivo.
Vc criou um projeto a parte ou esta usando o projeto do seu app para fazer o widget? qual é a melhor pratica nesse caso?
Quero fazer um app tipo do beautiful widgets, onde vc toca no widget e ele abre informações detalhadas em outra tela.
Seria criar um app e o widget juntos no mesmo projeto? Obrigado

Jeferson Frazao at 18 de abril de 2013 12:43 disse...

qual programa vc usa para fazer.

Filipe SR at 30 de agosto de 2013 10:01 disse...

No ultimo arquivo, você deve substituir "mensagem_configure_appwidget" por "widget_configure" e "mensagem_appwidget" por "meu_appwidget".
Ótimo tutorial, parabéns...

Filipe SR at 30 de agosto de 2013 10:09 disse...

Ah sim... verifiquem se o caminho do android:configure está correto...

Postar um comentário

 
© 2011 Tutoriandroid | Recode by Ardhiansyam | Based on Android Developers Blog