Visualizzazione post con etichetta XML. Mostra tutti i post
Visualizzazione post con etichetta XML. Mostra tutti i post

Aggiunta di una ProgressBar orizzontale sotto la ActionBar

Durante lo sviluppo di un'app che si connetteva ad un servizio remoto, ho avuto la necessità di visualizzare un elemento che indicasse all'utente l'attività in background che il dispositivo stava effettuando.

All'inizio ho optato per un ProgressDialog. E' una buona opzione, ma nel caso siano presenti delle animazioni a seguito di un invio, il dialog "blocca" la visualizzazione delle stesse e risulta antiestetico.

Ho quindi optato per un sistema meno "invasivo": una barra orizzontale per segnalare il lavoro in background.

L'oggetto ProgressBar è semplice da implementare, lo è meno l'integrazione con una ActionBar personalizzata e "importata" nel layout tramite un'inclusione, come in questo esempio:

1
2
3
4
5
6
7
8
9
<android.support.constraint.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="it.example.test_progressbar.MainActivity">

    <include layout="@layout/toolbar"/>

Impostazioni di base ed elementi necessari

Dopo aver creato un nuovo progetto "base" con una Activity vuota, per implementare la ProgressBar sono necessari i seguenti file (da creare o modificare):
  • activity_main.xml
  • toolbar.xml
  • AndroidManifest.xml
  • styles.xml
  • MainActivity.java

Modifiche XML

Partiamo dalle modifiche al design XML, che poi saranno le più "corpose".

AndroidManifest.xml

Il problema iniziale da affrontare è che la struttura "base" creata da Android Studio inserisce una ActionBar "basilare" direttamente dall'AndroidManifest attraverso questa voce:

1
2
3
<application
    ...
    android:theme="@style/AppTheme">

Per eliminare l'ActionBar, bisogna caricare specificare queste righe nel file styles.xml:

1
2
3
4
<style name="AppTheme.NoActionBar">
    <item name="windowActionBar">false</item>
    <item name="windowNoTitle">true</item>
</style>

rimuovere la voce incriminata e sostituirla con questa:

1
2
3
<application
    ...
    android:theme="@style/AppTheme.NoActionBar">

activity_main.xml

L'XML dell'Activity principale richiede una sola modifica, l'aggiunta della toolbar contenuta nel file separato.

Basta aggiungere, sotto all'apertura del tag del ConstraintLayout inserito dal sistema, di questa riga:

1
<include layout="@layout/toolbar"/>

toolbar.xml

La toolbar di norma viene innestata all'interno di un CoordinatorLayout, che verrà mantenuto.
Il codice di partenza è questo:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<android.support.design.widget.CoordinatorLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="it.tortellinux.diceroller.MainActivity">

    <android.support.design.widget.AppBarLayout
        android:layout_height="wrap_content"
        android:layout_width="match_parent"
        android:theme="@style/AppTheme.AppBarOverlay">

        <android.support.v7.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            android:background="?attr/colorPrimary"
            app:popupTheme="@style/AppTheme.PopupOverlay" />

    </android.support.design.widget.AppBarLayout>

    <LinearLayout
        android:id="@+id/fragment_container"
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_behavior="android.support.design.widget.AppBarLayout$ScrollingViewBehavior"
        >
    </LinearLayout>

</android.support.design.widget.CoordinatorLayout>

Per gestire correttamente la ProgressBar vanno applicate alcune modifiche sostanziali.

Per prima cosa, va rimosso quello che Android "presenta" sotto la Toolbar, cioè un piccolo margine e l'ombra.
Questo perché la ProgressBar verrà visualizzata "sotto" l'ombra e l'effetto sarà... inguardabile.

Bisogna quindi impostare l'elevation ed il margine inferiore a 0dp:

1
2
3
4
5
<android.support.design.widget.AppBarLayout
    ...
    app:elevation="0dp"
    android:layout_marginBottom="0dp"
    />

Poi è necessario aggiungere un FrameLayout che conterrà la ProgressBar. La rappresentazione "base" della ProgressBar utilizza un bordo superiore ed uno, più piccolo, inferiore.

Questi verranno eliminati inserendo un altezza di 3dp al frame ed impostando un margine negativo (sì, si può) alla ProgressBar, oltre ad un'altezza fissa.

E' necessario, infine, impostare questo comportamento al frame:

1
2
3
4
<FrameLayout
    ...
    app:layout_behavior="@string/appbar_scrolling_view_behavior"
    >

Questo impedisce alla Toolbar di "coprire" la ProgressBar.

Questo è il codice finale:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<android.support.design.widget.CoordinatorLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="it.tortellinux.test_progressbar.MainActivity">

    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="3dp"
        app:layout_behavior="@string/appbar_scrolling_view_behavior">

        <ProgressBar
            android:id="@+id/progress_spinner"
            style="?android:attr/progressBarStyleHorizontal"
            android:layout_width="match_parent"
            android:layout_height="7dp"
            android:layout_gravity="top"
            android:layout_marginTop="-3dp"
            android:indeterminate="true"
            android:visibility="visible"
            />
    </FrameLayout>

    <android.support.design.widget.AppBarLayout
        android:id="@+id/appBarLayout"
        android:layout_height="wrap_content"
        android:layout_width="match_parent"
        android:theme="@style/AppTheme.AppBarOverlay"
        app:elevation="0dp"
        android:layout_marginBottom="0dp">
        <android.support.v7.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="?attr/colorPrimary"
            app:popupTheme="@style/AppTheme.PopupOverlay"
            />

    </android.support.design.widget.AppBarLayout>

</android.support.design.widget.CoordinatorLayout>

MainActivity.java

Per l'implementazione della ProgressBar bastano le modifiche all'XML indicate qui sopra, solo che c'è bisogno di un sistema per renderla visibile o farla sparire.

Dopo aver recuperato l'oggetto attraverso la funzione findViewById, per impostare la visibilità della ProgressBar si utilizzano due semplici comandi.

Per visualizzarla:

1
progressBar.setVisibility(View.VISIBLE);

Per farla sparire:

1
progressBar.setVisibility(View.GONE);

L'effetto finale

Completati i vari passaggi, si ottiene questo effetto:

Impostare l'altezza di un ToggleButton

Quando si utilizza un ToggleButton con un background drawable (o anche solo con la grafica standard del Material Design), il sistema imposta automaticamente un padding attorno al testo dell'elemento, la cui presenza impedisce di definire programmaticamente l'altezza.

Per ovviare a questo problema, è necessario impostare l'altezza minima di questo elemento a zero. In questo modo, il sistema elimina il padding ed utilizza solamente le impostazioni definite per l'oggetto.

Definizione da layout XML

E' possibile impostare questo parametro nel codice XML in questo modo:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
<ToggleButton
    android:id="@+id/button2"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:background="@drawable/togglebutton_color"
    android:textColor="@color/togglebutton_text"
    android:layout_margin="5dp"
    android:text="10"
    android:minHeight="0dp"
    />

Oggetto generato da codice

Nel caso, invece, si voglia generare l'oggetto in modo dinamico direttamente da codice ed inserirlo in una View, bisogna aggiungere un'impostazione in più.

I due metodi da richiamare sono:

  • tb.setMinHeight(0);
  • tb.setMinimumHeight(0);

Questo è il codice per la creazione dell'oggetto:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// nuova istanza ToggleButton
ToggleButton tb = new ToggleButton(this);

// generazione di ID e TAG
int generatedId = View.generateViewId();
tb.setId(generatedId);
tb.setTag("TAG");

// testo / impostazione iniziale
tb.setTextOff("Testo OFF");
tb.setTextOn("Testo ON");
tb.setChecked( i == 1 );

// parametri di layout di base
LinearLayout.LayoutParams lParam = new LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1.0f);
tb.setLayoutParams(lParam);

// altezza minima
tb.setMinHeight(0);
tb.setMinimumHeight(0);

// background
tb.setBackgroundDrawable(this.getResources().getDrawable(R.drawable.togglebutton_color, this.getTheme()));

// colore del testo
tb.setTextColor(getResources().getColorStateList(R.color.togglebutton_text, this.getTheme()));

// listener (così ne seleziona uno solo tra tutti quelli della riga
tb.setOnClickListener(new View.OnClickListener()
{
    @Override
    public void onClick(View v)
    {
        // fai qualcosa
    }
});

Far "pulsare" un'immagine... o qualche altro elemento!

Per far "pulsare" qualche elemento nella propria app sono necessari 3 elementi:
  • l'oggetto da far "pulsare" (ad esempio, un ImageView)
  • l'XML con la configurazione dell'animazione
  • il codice per gestire l'animazione

Oggetto da far "pulsare"

L'oggetto da far pulsare può essere un semplice ImageView, come questo:

1
2
3
4
5
6
<ImageView
    android:id="@+id/ivIconOk"
    android:layout_width="match_parent"
    android:layout_height="80dp"
    app:srcCompat="@drawable/ic_result_ok"
    />

Animare utilizzando un XML

L'XML per l'animazione è il seguente:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <scale xmlns:android="http://schemas.android.com/apk/res/android"
           android:duration="120"
           android:fromXScale="1"
           android:fromYScale="1"
           android:pivotX="50%"
           android:pivotY="50%"
           android:repeatCount="3"
           android:repeatMode="reverse"
           android:toXScale="0.8"
           android:toYScale="0.8"
           android:interpolator="@android:anim/bounce_interpolator"
        />
</set>

Quest'animazione fa pulsare 3 volte l'oggetto (riduce dal 100% al 50% di dimensione, fissando il punto centrale) e poi si ferma.

Alcune annotazioni

Per "pulsazione" si intende riduzione o ingrandimento. Impostarlo a 3 volte vuol dire che, partendo dalla dimensione minore, si ingrandirà, si rimpicciolirà e poi si reingrandirà di nuovo.
Il repeatMode "reverse" indica che, concluso un ciclo, riprende dalla forma corrente (ingrandimento-riduzione-ingrandimento-riduzione...)

Codice Java

Questo invece è il codice per attivare la "pulsazione":

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
final ImageView iv = (ImageView) findViewById(R.id.ivIcon);
Animation pulse = AnimationUtils.loadAnimation(getApplicationContext(), R.anim.pulse);

pulse.setAnimationListener(new Animation.AnimationListener()
{
    @Override
    public void onAnimationStart(Animation animation)
    {
        // avvio della pulsazione
    }

    @Override
    public void onAnimationEnd(Animation animation)
    {
        // conclusione dell'animazione
    }

    @Override
    public void onAnimationRepeat(Animation animation)
    {
        // ad ogni ripetizione dell'animazione
    }
});

iv.startAnimation(pulse);

Disabilitare "effetto" click in un button, mantenendo e gestendo il click

Per rimuovere l'effetto del "click" (cioè l'ombreggiatura al momento del tocco) in un bottone, bisogna definire il background del bottone con un elemento drawable:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
<Button
    android:id="@+id/btnVisualizzaMenuPeso"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="@string/button_peso"
    app:layout_constraintBottom_toTopOf="@+id/llListaPeso"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintTop_toTopOf="parent"
    android:background="@drawable/togglebutton_peso_color_background"
    android:textColor="@color/colorTextWhite"
    />

A questo punto si definisce l'elemento @drawable/togglebutton_peso_color_background:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_checked="true">
        <shape android:shape="rectangle" >
            <corners android:radius="3dip" />
            <stroke
                android:width="1dip"
                android:color="@android:color/transparent"
                />
            <solid android:color="@color/colorTextWhite" />
            <padding
                android:bottom="10dp"
                android:left="10dp"
                android:right="10dp"
                android:top="10dp"
                />
        </shape>
    </item>
    <item>
        <shape android:shape="rectangle">
            <padding
                android:bottom="10dp"
                android:left="10dp"
                android:right="10dp"
                android:top="10dp"
                />
        </shape>
    </item>
</selector>

In questo modo, quando l'elemento viene "toccato", non visualizza l'ombra o altro.

Disegnare un rettangolo in XML

Questa è la guida per disegnare un rettangolo in XML.

Crea un file XML nella directory drawable e aggiungi il codice seguente:


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
<?xml version="1.0" encoding="UTF-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/listview_background_shape">
    <stroke android:width="2dp" android:color="#ff207d94" />
    <padding android:left="2dp"
        android:top="2dp"
        android:right="2dp"
        android:bottom="2dp" />
    <corners android:radius="5dp" />
    <solid android:color="#ffffffff" />
</shape>

Questo invece è il codice per inserirlo come background in una View:


1
2
3
4
5
<View
    android:id="@+id/myRectangleView"
    android:layout_width="200dp"
    android:layout_height="50dp"
    android:background="@drawable/rectangle"/>

A questo punto dovrebbe essere tutto a posto.

Una nota: per le ImageViews, bisogna utilizzare l'attributo android:src!

Generare un colore randomico di tonalità "pastello"

Questo script restituisce il codice di un colore generato a caso nelle tonalità pastello. La generazione viene definita attraverso le 3 ca...