NavigationBar in style iPhone UITabBarController per Android

Nel porting di applicazioni iOS su piattaforma Android, ci sono casi in cui si vuole necessariamente una barra di navigazione nella parte bassa dello schermo, come quella presente in molte applicazioni iPhone/iPad.

Ci sono diversi approcci che si possono seguire. Usare una libreria che rende anche possibile lo scrolling dei bottoni, un controllo personalizzato e gestito tutto via codice, dei TabHost con dei TabWidget, oppure inventarsi altro per gestire anche i Fragment.

Invece io voglio ottenere lo stesso risultato con la massima flessibilita’ possibile, quindi usando solo controlli standard e semplici, al massimo personalizzandone lo stile. Quindi il componente principale della navigationbar sara’ un RadioGroup, all’interno del quale ogni elemento RadioButton rappresentera’ un pulsante. Un diffuso uso di StateListDrawable rendera’ coerente l’aspetto grafico.

Di seguito il layout dell’activity principale, quella che conterra’ la navigation bar: res/layout/actnavbar.xml

< ?xml version="1.0" encoding="utf-8"?>
 
<relativelayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="fill_parent"
  android:layout_height="fill_parent"
  android:background="@android:color/white"
>
  <radiogroup android:layout_width="fill_parent"
    android:layout_height="60dp"
    android:layout_alignParentBottom="true"
    android:orientation="horizontal"
    android:background="@drawable/navbar_background"
  >
    <radiobutton android:id="@+id/btnAll"
      style="@style/navbar_button"
      android:drawableTop="@drawable/navbar_allselector"
      android:text="All"></radiobutton>
    <radiobutton android:id="@+id/btnPicture"
      style="@style/navbar_button"
      android:drawableTop="@drawable/navbar_pictureselector"
      android:text="Pictures"
      android:layout_marginLeft="5dp"></radiobutton>
    <radiobutton android:id="@+id/btnVideo"
      style="@style/navbar_button"
      android:drawableTop="@drawable/navbar_videoselector"
      android:text="Videos"
      android:layout_marginLeft="5dp"></radiobutton>
    <radiobutton android:id="@+id/btnFile"
      style="@style/navbar_button"
      android:drawableTop="@drawable/navbar_fileselector"
      android:text="Files"
      android:layout_marginLeft="5dp"></radiobutton>
    </radiogroup>
</relativelayout>


E il file con gli stili res/values/styles.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
  <style name="navbar_button">
    <item name="android:layout_width">0dp</item>
    <item name="android:layout_height">wrap_content</item>
    <item name="android:button">@null</item>
    <item name="android:background">@drawable/navbar_backgroundselector</item>
    <item name="android:gravity">center_horizontal</item>
    <item name="android:layout_weight">1</item>
    <item name="android:textSize">12dp</item>
  </style>
</resources>

Il trucchetto e’ impostare a @null la proprieta’ android:button (in pratica, il disegno di default del RadioButton) e poi impostare android:drawableTop del testo all’icona che si vuole far apparire nella navigationbar. Impostare invece android:background serve per ottenere l’effetto del glow quando il bottone e’ schiacciato. android:layout_width a 0dp e android:layout_weight a 1 fanno occupare a tutti i bottoni lo stesso spazio, a prescindere dalla lunghezza del testo.

Al resto pensano gli StateListDrawable, grazie ai quali vengono impostate le icone da usare quando il bottone e’ Checked oppure no. Il framework sottostante pensera’ poi a gestire cosa visualizzare a seconda dello stato.

Ecco quindi uno dei selector usati per i bottoni, il primo della navigation bar: res/drawable/navbar_allselector.xml

<?xml version="1.0" encoding="UTF-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
  <item
    android:state_checked="true"
    android:drawable="@drawable/navbar_allselected"
  />
  <item android:drawable="@drawable/navbar_allnormal"  />
</selector>

Gli altri sono tutti simili, cambiano solo le due icone utilizzate. Ovviamente il selector piu’ essere esteso per includere anche icone quando il bottone e’ disabilitato, oppure premuto e disabilitato e cosi’ via.

Per ottenere invece l’effetto glow quando il bottone e’ premuto, il file e’ il seguente: res/drawable/navbar_backgroundselector.xml

<?xml version="1.0" encoding="UTF-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
  <item
    android:state_checked="true"
    android:drawable="@drawable/navbar_highlight"
  />
  <item android:drawable="@android:color/transparent"  />
</selector>

Anche il colore del testo potrebbe essere cambiato un base allo stato del bottone, basta impostare la proprieta’ android:textColor del RadioButton all’apposito ColorStateList.

Infine, il codice dell’Activity principale, src/com.test/NavbarActivity.java

public class NavbarActivity  extends Activity {
 
  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.actnavbar);
 
    RadioButton radioButton;
    radioButton = (RadioButton) findViewById(R.id.btnAll);
    radioButton.setOnCheckedChangeListener(btnNavBarOnCheckedChangeListener);
    radioButton = (RadioButton) findViewById(R.id.btnPicture);
    radioButton.setOnCheckedChangeListener(btnNavBarOnCheckedChangeListener);
    radioButton = (RadioButton) findViewById(R.id.btnVideo);
    radioButton.setOnCheckedChangeListener(btnNavBarOnCheckedChangeListener);
    radioButton = (RadioButton) findViewById(R.id.btnFile);
    radioButton.setOnCheckedChangeListener(btnNavBarOnCheckedChangeListener);
  }
 
  private CompoundButton.OnCheckedChangeListener btnNavBarOnCheckedChangeListener = new CompoundButton.OnCheckedChangeListener() {
    public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
      if (isChecked) {
        Toast.makeText(NavbarActivity.this, buttonView.getText(), Toast.LENGTH_SHORT).show();
      }
    }
  };
}

A questo link i sorgenti dell’esempio.

12 Comments

  1. Grande! Ottima soluzione! Ovviamente per ottenere un effetto simile ad iphone (con tanto di “back”) andrebbe abbinato ad un activity group con un framelayout (come fa tabhost) in modo da avere uno stack di activity all’interno del frame principale, giusto? O è meglio ridisegnare il menu sotto ad ogni activity “interna” secondao te?

  2. Grazie per i complimenti.

    Per me, se spingi back, e’ come se volessi chiudere l’applicazione, quindi niente stack delle activity. Altrimenti puoi usare i fragment (da 1.6 grazie alla compatibility library) e il loro meccanismo interno di gestione dello stack di quelli sperti, che fa proprio quello che vuoi tu,e nativamente con 2 righe di codice.

  3. Si avevo guardato la Fragment, ed è molto interessante (soprattutto visto il prossimo arrivo di ICS) ma certe cose non riesco a farle venire come vorrei (all’interno del frame io apro una listview, una tabview vera e una mapview quindi non so se è fattibile con i frame), la tengo come TODO, grazie ancora :)

  4. Hey! Great example.

    However, there are some drawables missing from the downloadable ZIP-file. FOr example, the navbar_highlight is missing. I’d like to see what is in that file. Can that be arranged?

    Thx!

  5. Hey!
    Thanks for the quick answer! yes indeed, I did take a look in the xdpi folders, but didnt find anything in hdpi so I just didnt look in the rest :-)

    Thanks for the clarification, and thanks again for a good example =)

  6. ciao rainbowbreeze,
    complimenti, soluzione davvero elegante! Ho un paio di osservazioni:
    1) nel file actnavbar.xml c’è un LinearLayout che probabilmente può essere tolto (ai fini dell’esempio, s’intende)
    2) qual’è secondo te il modo più elegante per includere questa barra in tutte le activities di un’applicazione pre-esistente?
    grazie e ancora complimenti!
    Fabio

  7. @Fabio
    1) Forse intendi il RelativeLayout. L’ho messo li dato che poi dentro ci vanno tutti gli altri elementi dello screen che utilizza quell’xml. la navbar infatti e’ agganciata in basso, e per fare questo hook mi serve un RelativeLayout

    2) Io metterei tutta la parte del radiogroup in un file xml separato e poi userei un include negli altri xml dove devo usare la navbar, avendo cura che l’include sia dentro un RelativeLayout, o gestire un altro modo per tenere sempre posizionata la barra in basso.

  8. Ciao! Ti ringrazio per la guida perchè è perfetta! Ti volevo chiedere solo una cosa: come posso impostare un bottone di default? perchè quando apro l’activity di default non mi apre nulla..

Leave a Reply