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

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 caratteristiche "hue", "saturation" e "brightness".

Poi utilizza la funzione HSVToColor.

I tre parametri sono definiti in questo modo:

HUE

E' un valore intero tra 0 e 360 (rappresenta i "gradi" nel cerchio di selezione dei colori), che però viene passato come float.

SATURATION

E' un float tra 0 e 1, cioè un valore percentuale. Più è alto, più il colore è "carico".

BRIGHTNESS

E' un float tra 0 e 1, cioè un valore percentuale. Più è alto, più il colore è "chiaro".

Questo è il codice:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
private String getRandomColor()
{
    // variabili di appoggio
    int newColor;
    Random rnd = new Random();

    // generazione di HUE, SATURATION e BRIGHTNESS
    int hue = (int) Math.floor(0 + rnd.nextDouble()*(360));
    int saturation = (int) Math.floor(0 + rnd.nextDouble()*(40));
    int brightness = (int) Math.floor(70 + rnd.nextDouble()*(30));

    // HSVToColor ( [0..360], [0..1], [0..1])
    newColor = android.graphics.Color.HSVToColor(new float[] {((float) hue), ((float) saturation) / 100, ((float) brightness) / 100});

    return String.format("#%06X", (0xFFFFFF & newColor));

} // getRandomColor

Camera e autofocus

Gestire l'autofocus della camera è veramente una bazzecola, basta impostare un parametro.

Ecco come:

1
2
3
4
5
6
7
8
mCamera = Camera.open(0);

//set camera to continually auto-focus
Camera.Parameters params = mCamera.getParameters();

params.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);

mCamera.setParameters(params);

Salvare login e password sul dispositivo: SharedPreferences

Nel creare un'app "privata" appoggiata ad un web service online, ho dovuto scontrarmi con la gestione dell'autenticazione.

Una volta ottenuto il riconoscimento, ho deciso che l'app avrebbe dovuto salvare i dati (email e password) per poi presentarli alla successiva richiesta.

Gestione di dati

Android mette a disposizione un sistema per salvare ed utilizzare dati di configurazione all'interno dell'app: lo SharedPreference.

L'oggetto in questione punta ad un file contenente combinazioni chiave-valore e fornisce metodi per la lettura e la scrittura degli stessi. Questi file, gestiti dal framework, possono essere marcati come privati o come condivisi.

Tipologie di dati

Gli oggetti che possono essere salvati e recuperati dallo SharedPreference possono essere di vario tipo:
  • boolean
  • float
  • int
  • long
  • string

Dati immutabili

E' importante capire che gli oggetti ritornati dalle varie funzioni get devono essere trattati come immutabili dall'applicazione.

Creazione di uno SharedPreference

La creazione è abbastanza semplice: si richiama il metodo getSharedPreferences, a cui viene passato il nome del file da leggere e la modalità di operazione.

Questo è il codice:

1
SharedPreferences sp = getSharedPreferences("Login", MODE_PRIVATE);

Modalità di operazione

Queste sono le modalità operative legate agli SharedPreference:
  • MODE_PRIVATE
  • MODE_WORLD_READABLE
  • MODE_WORLD_WRITEABLE
  • MODE_MULTI_PROCESS
Da notare che gli ultimi tre sono stati dichiarati "deprecated", quindi l'unica opzione valida è la modalità "privata".

Salvare nuovi valori o aggiornare i valori presenti

Per mantenere la consistenza dei dati, è necessario utilizzare un editor. Una volta creato l'oggetto, bisogna quindi generare un SharedPreferences.Editor:

1
SharedPreferences.Editor spEd = sp.edit();

A questo punto si può inserire un nuovo valore (o aggiornare un valore già presente) con una delle funzioni put presenti.

Un esempio:

1
2
spEd.putString("email", field_email);
spEd.putString("password", field_password);

Il primo campo indica la chiave per il successivo recupero, il secondo campo invece è il contenuto da salvare / aggiornare.

Per eliminare un valore presente, invece, si richiama la funzione remove:

1
spEd.remove("email");

Applicare le modifiche

Una volta inseriti i nuovi valori o aggiornati quelli presenti, è necessario eseguire un commit per confermare le modifiche:

1
spEd.commit();

Lettura dei valori presenti

La lettura dei valori già salvati è anche più semplice, dato che non occorre generare un editor:

1
2
3
SharedPreferences spGet = this.getSharedPreferences("Login", MODE_PRIVATE);
String mail_recuperata = spGet.getString("email", null);
String pass_recuperata = spGet.getString("password", null);

Il secondo parametro passato a getString è il valore di default restituito nel caso in cui la voce non fosse presente nello SharedPreference.

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:

Generazione di un ID valido per un oggetto definito da codice

Ogni oggetto, per poter essere richiamato, ha un ID associato. Definirlo manualmente è possibile, ma può causare problemi, per cui il framework mette a disposizione un metodo della View per adempiere allo scopo:

1
View.generateViewId();

Questo metodo restituisce un ID valido che può essere associato ad un elemento definito da codice.

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);

Posticipare l'esecuzione di un pezzo di codice

Per posticipare l'esecuzione di un pezzo di codice, non è possibile utilizzare il Thread.sleep, in quanto blocca l'attività dell'interfaccia grafica.

Bisogna invece utilizzare questo pezzo di codice:

1
2
3
4
5
6
7
final Handler handler = new Handler();
handler.postDelayed(new Runnable() {
  @Override
  public void run() {
    //Do something after 100ms
  }
}, 100);

Utilizzando il postDelayed e l'oggetto Runnable, il sistema attende in un thread separato 100 millisecondi e poi esegue il corpo della funzione "run".

Gestire le animazioni su elementi dentro un ConstraintLayout

Il problema, quando si utilizza un ConstraintLayout, è che il sistema dà la priorità ai "constraint" più che alle specifiche altezze (che non vengono prese in considerazione).

Per gestire delle animazioni (o anche solo per gestire cambi di dimensioni), è necessario utilizzare di ConstraintSet e reimpostarli al bisogno.

Ecco un codice di esempio che gestisce la procedura:

  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
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
// button che avvia l'animazione
Button btn = (Button) findViewById(R.id.btnRileva);

// listener per l'attività
btn.setOnClickListener(new View.OnClickListener()
{
    @Override
    public void onClick(final View v)
    {
        // contenitore RelativeLayout
        final RelativeLayout rlContainer = (RelativeLayout) findViewById(R.id.rlContainer);

        // parametri di un ConstraintLayout
        final ConstraintLayout clMain = (ConstraintLayout) findViewById(R.id.ccMainLayout);

        // set di "constraint" (da cambiare o da ripristinare)
        final ConstraintSet set = new ConstraintSet();
        final ConstraintSet original = new ConstraintSet();

        // per avere una copia degli originali, bisogna clonarli
        set.clone(clMain);
        original.clone(clMain);

        // altezza iniziale dell'elemento
        final int initialHeight = rlContainer.getMeasuredHeight();

        // durata dell'effetto
        final int duration = (int)(initialHeight / v.getContext().getResources().getDisplayMetrics().density);

        Animation a = new Animation()
        {
            @Override
            protected void applyTransformation(float interpolatedTime, Transformation t)
            {
                // inizio dell'attività di animazione
                if (interpolatedTime == 0)
                {
                    // rottura della connessione del vincolo del contenitore da "animare"
                    set.clear(R.id.rlContainer, ConstraintSet.BOTTOM);

                    // applicazione del set al contenitore "padre" (la root del layout)
                    set.applyTo(clMain);

                    // impostazione dell'altezza iniziale, in modo da non presentare "strane variazioni"
                    rlContainer.getLayoutParams().height = initialHeight;

                    // avverto il sistema che il layout è cambiato, così viene ridisegnato
                    rlContainer.requestLayout();
                }
                // conclusione dell'attività di animazione
                else if(interpolatedTime == 1)
                {
                    // faccio sparire il contenitore
                    rlContainer.setVisibility(View.GONE);
                }
                else
                {
                    // calcolo dell'altezza in base al tempo di interpolazione
                    int height = initialHeight - ((int)(initialHeight * interpolatedTime));

                    // imposto l'altezza
                    rlContainer.getLayoutParams().height = height;

                    // avverto il sistema che il layout è cambiato, così viene ridisegnato
                    rlContainer.requestLayout();
                }
            }

            @Override
            public boolean willChangeBounds()
            {
                return true;
            }
        };

        // imposta la durata dell'animazione
        a.setDuration(duration);

        // imposto un listener per gestire avvio e fine dell'animazione
        a.setAnimationListener(new Animation.AnimationListener()
        {
            @Override
            public void onAnimationStart(Animation animation) { }

            @Override
            public void onAnimationEnd(Animation animation)
            {
                Toast.makeText(getApplicationContext(), "fine dell'animazione", Toast.LENGTH_LONG).show();

                // reimposto il ConstraintSet originale
                original.applyTo(clMain);
                rlContainer.getLayoutParams().height = initialHeight;
                rlContainer.requestLayout();
            }

            @Override
            public void onAnimationRepeat(Animation animation) { }
        });

        // avvio l'animazione
        rlContainer.startAnimation(a);
    }
});

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...