Autore Topic: [Medio] Animazione del testo di un Button  (Letto 4691 volte)

Offline Verandi

  • Moderatore
  • Utente normale
  • *****
  • Post: 378
  • Respect: +75
    • Mostra profilo
  • Sistema operativo:
    Windows 7
[Medio] Animazione del testo di un Button
« il: 30 Ottobre 2011, 12:14:47 CET »
+6
Livello di difficoltà: medio
Target SDK: 4
Min SDK: 4
Link al file compresso del progetto eclipse: file in allegato

Ciao! Su richiesta/spunto da questo topic, ho pensato di scrivere il mio primo tutorial su come animare il testo all'interno di un Button. Il metodo è applicabile anche ad altre View (per esempio TextView), ma l'esempio avrà come oggetto la classe Button.


Per creare del testo scorrevole all'interno di un pulsante non è possibile usare la classe Animation di Android, in quanto testo (scorrevole) e background (fisso) fanno parte di un'unica View e la classe Animation può essere usata solo per l'animazione delle View per intero.

Le possibili soluzioni sono due:
  • Separare background e testo, ovvero usare un ViewGroup (LinearLayout, RelativeLayout…) che faccia da contenitore per lo sfondo e inserire all'interno una TextView con il testo da animare (su cui useremo la classe Animation).
       ○ PRO: più semplice da implementare
       ○ CONTRO: scomoda se avessimo numerosi pulsanti in quanto, per ogni "pulsante", avremmo un LinearLayout e una TextView.

  • Creare una Custom View, personalizzando e aggiungendo alcuni metodi che rendano il testo scorrevole:
       ○ PRO:  conserva le caratteristiche della classe Button ed è riutilizzabile in qualsiasi punto dell'applicazione
       ○ CONTRO: un po' laboriosa  se non si ha dimestichezza nella creazione delle Custom View
   
In questo tutorial utilizziamo il secondo metodo.

Sorgenti:

Questa è la nostra view personalizzata. Estende la classe Button, di cui implementiamo i tre costruttori, il metodo onMeasure() , il metodo onDraw() e il metodo setText(). (Nella bibliografia al termine del post c'è il link alla documentazione ufficiale)
I dettagli sono nei commenti del codice, per rendere più diretta la spiegazione. (Per abitudine uso l'inglese per i nomi di variabili/metodi/classi, spero non crei problemi nella comprensione)
Ho cercato di renderla funzionante nella maggior parte dei casi (p.e. colore e stile del testo definiti nel file del layout), però in alcune situazioni dovrà essere modificata in qualche punto.

CustomButton.java
Codice (Java): [Seleziona]
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.Region.Op;
import android.os.Handler;
import android.text.TextPaint;
import android.util.AttributeSet;
import android.view.ViewGroup.LayoutParams;
import android.widget.Button;

public class CustomButton extends Button {
        String mText;// Stringa che conterrà il testo che verrà visualizzato (Per semplificazione non usiamo la classe StringBuilder)
        final TextPaint mPaint = new TextPaint(TextPaint.ANTI_ALIAS_FLAG);// Paint utilizzato per scrivere il testo                                                                                            
        final Rect textArea = new Rect(); // Area del pulsante in cui scrivere il testo
        float visibleX; // Posizione orizzontale del testo "visibile".
        float hiddenX; // Posizione orizzontale del testo "nascosto".
        boolean isFirstDraw = true; // Indica se è il primo frame dell'animazione
        int stringWidth; // Larghezza della stringa, utile per il posizionamento
        Handler mHandler; // Ci servirà per l'animazione
        Runnable mRunnable; // Ci servirà per l'animazione

        /********************************************
         * Classici tre costruttori della classe View
         ********************************************/


        public CustomButton(Context context) {
                // Richiamato quando aggiungiamo il pulsante da un'activity
                super(context);
                setup();
        }

        public CustomButton(Context context, AttributeSet attrs) {
                // Richiamato da file xml senza definirne lo style.
                super(context, attrs);
                setup();
        }

        public CustomButton(Context context, AttributeSet attrs, int defStyle) {
                // Richiamato da file xml quando definiamo una risorsa style (attributo
                // style="@+id/style")
                super(context, attrs, defStyle);
                setup();
        }

        /********************************************
         * Metodi di questa custom view
         ********************************************/


        /**
         * "Inizializza" le variabili. Richiamato in ogni possibile costruttore.
         */

        private void setup() {
                // Ci serviranno per fare il refresh automatico della View
                mHandler = new Handler();
                mRunnable = new Runnable() {

                        @Override
                        public void run() {
                                // Fa il refresh del Button
                                invalidate();
                        }
                };
                // E' un pulsante con testo scorrevole, quindi vogliamo che sia alto
                // solo una riga!
                setMaxLines(1);
                // Usiamo colore, dimensione e stile definito nell'xml. Nel caso
                // definissimo questi attributi nel codice, dovremmo inserirli in un
                // altro metodo che richiameremmo in ogni onDraw();
                mPaint.setColor(getCurrentTextColor());
                mPaint.setTextSize(getTextSize());
                mPaint.setTypeface(getTypeface());
        }

        /**
         * Misura le posizioni delle due stringhe e l'area di disegno. Richiamato
         * solo al primo onDraw()
         */

        private void measure() {
                // Misuriamo la larghezza della stringa
                stringWidth = (int) mPaint.measureText(mText) + getPaddingLeft()
                                + getPaddingRight();
                // La stringa visibile partirà dopo lo spazio di padding
                visibleX = getPaddingLeft();
                // La stringa invisibile sarà a sinistra della stringa visibile
                hiddenX = visibleX - stringWidth;
                // Salvo nel Rect "textArea" il rettangolo che rappresenta tutta l'area
                // in cui posso disegnare
                getDrawingRect(textArea);
                // Tolgo all'area disegnabile il padding di destra e sinistra
                textArea.inset(getPaddingLeft() + getPaddingRight(), 0);
                isFirstDraw = false;
        }

        /**
         * Fa scorrere il testo
         */

        private void scrollText() {
                // Se una stringa non è più visibile, la spostiamo dietro all'altra
                // stringa
                visibleX = (visibleX >= getWidth()) ? hiddenX - stringWidth : visibleX;
                hiddenX = (hiddenX >= getWidth()) ? visibleX - stringWidth : hiddenX;
                // Spostiamo le stringhe verso destra di due pixel. Dovremmo usare i dip
                // al posto dei pixel, altrimenti la velocità sarà diversa in base alla
                // risoluzione dello schermo!
                visibleX += 2;
                hiddenX += 2;
        }

        /******************************************************************
         * Metodi della superclasse Button che vogliamo modificare a nostro
         * piacimento
         ******************************************************************/


        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
                // Richiamato prima che la view sia visibile, quando deve essere
                // misurata.

                // Nel caso avessimo definito una larghezza in pixel (o dpi...) nel file
                // xml, non modifichiamo nulla.
                // Nel caso,invece, la larghezza sia "wrap_content", poiché nel metodo
                // setText() passiamo una stringa vuota, verrebbe misurato come un
                // pulsante senza testo
                if (getLayoutParams().width == LayoutParams.WRAP_CONTENT) {
                        // Quindi in questo caso gli mettiamo come larghezza la larghezza
                        // della stringa più padding destro e sinistro
                        widthMeasureSpec = MeasureSpec.makeMeasureSpec(
                                        (int) (mPaint.measureText(mText)) + getPaddingLeft()
                                                        + getPaddingRight(), MeasureSpec.EXACTLY);
                }
                // Passo le dimensioni alla superclasse
                super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        }

        @Override
        public void setText(CharSequence text, BufferType type) {
                // Richiamato quando aggiungiamo il testo (sia da activity che da
                // file xml)
                mText = text.toString();
                // Passiamo alla superclasse una stringa vuota, perché vogliamo essere
                // noi a gestire il "disegno" del testo
                super.setText("", type);

        }

        @Override
        protected synchronized void onDraw(Canvas canvas) {
                // Richiamato quando disegniamo la view (e ogni volta che la view deve
                // essere aggiornata - in questo caso quando il pulsante viene cliccato,
                // o cambiamo il testo del pulsante, lo sfondo ecc -)

                //Se è il primo frame, fa le misurazioni necessarie
                if (isFirstDraw) {
                        measure();
                }
                // Limitiamo l'area del canvas in cui disegnare (Ovvero togliamo il
                // padding dall'area disegnabile)
                canvas.clipRect(textArea, Op.INTERSECT);

                // Scriviamo due stringhe: una è quella in uscita, l'altra è
                // quella in entrata
                canvas.drawText(mText, visibleX,
                                (getHeight() / 2) + (mPaint.measureText("o") / 2), mPaint);
                canvas.drawText(mText, hiddenX,
                                (getHeight() / 2) + (mPaint.measureText("o") / 2), mPaint);

                // Aggiorniamo le posizioni delle due stringhe per il prossimo frame
                scrollText();
                // Impostiamo il prossimo refresh della view
                mHandler.removeCallbacks(mRunnable);
                mHandler.postDelayed(mRunnable, 1000 / 30); // 1000/30 = 30 frame al
                                                                                                        // secondo
                super.onDraw(canvas);
        }

        @Override
        protected void onDetachedFromWindow() {
                // Interrompiamo l'animazione quando l'activity diventa invisibile
                mHandler.removeCallbacks(mRunnable);
                super.onDetachedFromWindow();
        }

}

Questo è il file di layout dell'activity che visualizza il pulsante:

Main.xml
Codice (XML): [Seleziona]
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
   android:layout_width="fill_parent"
   android:layout_height="fill_parent"
   android:background="#FFFFFF"
   android:orientation="vertical" >

    <!-- Pulsante customizzato. Sostituire l'indirizzo del pacchetto com.example.scrollbutton con quello della vostra applicazione. -->
    <com.example.scrollbutton.CustomButton
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:text="Testo di prova con scroll" />

    <!-- Pulsante normale -->
    <Button
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:text="Testo di prova fisso" />

</LinearLayout>


Bibliografia:
« Ultima modifica: 31 Ottobre 2011, 09:32:22 CET da Verandi »

Offline schroedinger

  • Nuovo arrivato
  • *
  • Post: 1
  • Respect: 0
    • Mostra profilo
Re:[Medio] Animazione del testo di un Button
« Risposta #1 il: 17 Aprile 2013, 18:32:19 CEST »
0
Ciao, bellissimo tutorial...
hai pensato a una versione in cui poter scegliere la direzione dell scorrimento?
ad es. da destra a sinistra

Offline Nicola_D

  • Utente storico
  • *****
  • Post: 2479
  • SBAGLIATO!
  • Respect: +323
    • Github
    • Google+
    • nicoladorigatti
    • Mostra profilo
  • Dispositivo Android:
    Nexus 6p, Nexus 4, Nexus S, Nexus 7(2012)
  • Sistema operativo:
    Windows 7
Re:[Medio] Animazione del testo di un Button
« Risposta #2 il: 17 Aprile 2013, 19:05:15 CEST »
0
Ci sono anche questi metodi...
http://stackoverflow.com/questions/1827751/is-there-a-way-to-make-ellipsize-marquee-always-scroll

Inviato dal mio Nexus S con Tapatalk 2

IMPORTANTE:NON RISPONDO A PROBLEMI VIA MESSAGGIO PRIVATO
LOGCAT: Non sai cos'è? -> Android Debug Bridge | Android Developers
               Dov'è in Eclipse? -> Window -> Open Prospective -> DDMS e guarda in basso!
[Obbligatorio] Logcat, questo sconosciuto! (Gruppo AndDev.it LOGTFO) - Android Developers Italia

Offline filipposapo

  • Utente junior
  • **
  • Post: 135
  • Respect: +1
    • Mostra profilo
  • Dispositivo Android:
    Tablet Samsung Note 10.1
  • Sistema operativo:
    Windows 8
Re:[Medio] Animazione del testo di un Button
« Risposta #3 il: 14 Gennaio 2014, 16:02:04 CET »
0
Salve a tutti, io stavo cercando di modificare il codice per rendere il testo scorrevole da destra a sinistra. Ci sono riuscito compreso il concatenamento delle due stringhe ma lo fa solo un'unica volta per cui volevo capire dove sto sbagliando a modificare per realizzare la stessa cosa ma nel senso inverso.
Ecco il codice.

Codice (Java): [Seleziona]
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.Region.Op;
import android.os.Handler;
import android.text.TextPaint;
import android.util.AttributeSet;
import android.view.View.MeasureSpec;
import android.view.ViewGroup.LayoutParams;
import android.widget.TextView;
import android.widget.TextView.BufferType;

public class CustomButton extends TextView {
        String mText;// Stringa che conterrà il testo che verrà visualizzato (Per
                                        // semplificazione non usiamo la classe StringBuilder)

        final TextPaint mPaint = new TextPaint(TextPaint.ANTI_ALIAS_FLAG);// Paint
                                                                                                                                                // utilizzato
                                                                                                                                                // per
                                                                                                                                                // scrivere
                                                                                                                                                // il
                                                                                                                                                // testo
        Rect textArea = new Rect(); // Area del pulsante in cui scrivere il testo
        float visibleX; // Posizione orizzontale del testo "visibile".
        float hiddenX; // Posizione orizzontale del testo "nascosto".
        boolean isFirstDraw = true; // Indica se è il primo frame dell'animazione
        int stringWidth; // Larghezza della stringa, utile per il posizionamento
        Handler mHandler; // Ci servirà per l'animazione
        Runnable mRunnable; // Ci servirà per l'animazione

        /********************************************
         * Classici tre costruttori della classe View
         ********************************************/


        public CustomButton(Context context) {
                // Richiamato quando aggiungiamo il pulsante da un'activity
                super(context);
                setup();
        }

        public CustomButton(Context context, AttributeSet attrs) {
                // Richiamato da file xml senza definirne lo style.
                super(context, attrs);
                setup();
        }

        public CustomButton(Context context, AttributeSet attrs, int defStyle) {
                // Richiamato da file xml quando definiamo una risorsa style (attributo
                // style="@+id/style")
                super(context, attrs, defStyle);
                setup();
        }

        /********************************************
         * Metodi di questa custom view
         ********************************************/


        /**
         * "Inizializza" le variabili. Richiamato in ogni possibile costruttore.
         */

        private void setup() {
                // Ci serviranno per fare il refresh automatico della View
                mHandler = new Handler();
                mRunnable = new Runnable() {

                        @Override
                        public void run() {
                                // Fa il refresh del Button
                                invalidate();
                        }
                };
                // E' un pulsante con testo scorrevole, quindi vogliamo che sia alto
                // solo una riga!
                setMaxLines(1);
                // Usiamo colore, dimensione e stile definito nell'xml. Nel caso
                // definissimo questi attributi nel codice, dovremmo inserirli in un
                // altro metodo che richiameremmo in ogni onDraw();
                mPaint.setColor(getCurrentTextColor());
                mPaint.setTextSize(getTextSize());
                mPaint.setTypeface(getTypeface());
        }

        /**
         * Misura le posizioni delle due stringhe e l'area di disegno. Richiamato
         * solo al primo onDraw()
         */

        private void measure() {
                // Misuriamo la larghezza della stringa
                stringWidth = (int) mPaint.measureText(mText) + getPaddingLeft()
                                + getPaddingRight();
                // La stringa visibile partirà dopo lo spazio di padding
                visibleX = getPaddingRight();
                // La stringa invisibile sarà a sinistra della stringa visibile
                hiddenX = visibleX + stringWidth;
                // Salvo nel Rect "textArea" il rettangolo che rappresenta tutta l'area
                // in cui posso disegnare
                getDrawingRect(textArea);
                // Tolgo all'area disegnabile il padding di destra e sinistra
                textArea.inset(getPaddingLeft() + getPaddingRight(), 0);
                isFirstDraw = false;
        }

        /**
         * Fa scorrere il testo
         */

        private void scrollText() {
                // Se una stringa non è  più visibile, la spostiamo dietro all'altra
                // stringa
                visibleX = (visibleX >= getWidth()) ? hiddenX + stringWidth : visibleX;
                hiddenX = (hiddenX >= getWidth()) ? visibleX + stringWidth : hiddenX;
                // Spostiamo le stringhe verso destra di due pixel. Dovremmo usare i dip
                // al posto dei pixel, altrimenti la velocità sarà diversa in base alla
                // risoluzione dello schermo!
                visibleX -= 3;
                hiddenX -= 3;
        }

        /******************************************************************
         * Metodi della superclasse Button che vogliamo modificare a nostro
         * piacimento
         ******************************************************************/


        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
                // Richiamato prima che la view sia visibile, quando deve essere
                // misurata.

                // Nel caso avessimo definito una larghezza in pixel (o dpi...) nel file
                // xml, non modifichiamo nulla.
                // Nel caso,invece, la larghezza sia "wrap_content", poiché nel metodo
                // setText() passiamo una stringa vuota, verrebbe misurato come un
                // pulsante senza testo
                if (getLayoutParams().width == LayoutParams.WRAP_CONTENT) {
                        // Quindi in questo caso gli mettiamo come larghezza la larghezza
                        // della stringa più padding destro e sinistro
                        widthMeasureSpec = MeasureSpec.makeMeasureSpec(
                                        (int) (mPaint.measureText(mText)) + getPaddingLeft()
                                                        + getPaddingRight(), MeasureSpec.EXACTLY);
                }
                // Passo le dimensioni alla superclasse
                super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        }

        @Override
        public void setText(CharSequence text, BufferType type) {
                // Richiamato quando aggiungiamo il testo (sia da activity che da
                // file xml)
                mText = text.toString();
                // Passiamo alla superclasse una stringa vuota, perché vogliamo essere
                // noi a gestire il "disegno" del testo
                super.setText("", type);

        }

        @Override
        protected synchronized void onDraw(Canvas canvas) {
                // Richiamato quando disegniamo la view (e ogni volta che la view deve
                // essere aggiornata - in questo caso quando il pulsante viene cliccato,
                // o cambiamo il testo del pulsante, lo sfondo ecc -)

                // Se è il primo frame, fa le misurazioni necessarie
                if (isFirstDraw) {
                        measure();
                }
                // Limitiamo l'area del canvas in cui disegnare (Ovvero togliamo il
                // padding dall'area disegnabile)
                canvas.clipRect(textArea, Op.INTERSECT);

                // Scriviamo due stringhe: una è quella in uscita, l'altra è
                // quella in entrata
                canvas.drawText(mText, visibleX,
                                (getHeight() / 2) + (mPaint.measureText("o") / 2), mPaint);
                canvas.drawText(mText, hiddenX,
                                (getHeight() / 2) + (mPaint.measureText("o") / 2), mPaint);

                // Aggiorniamo le posizioni delle due stringhe per il prossimo frame
                scrollText();
                // Impostiamo il prossimo refresh della view
                mHandler.removeCallbacks(mRunnable);
                mHandler.postDelayed(mRunnable, 1000 / 30); // 1000/30 = 30 frame al
                                                                                                        // secondo
                super.onDraw(canvas);
        }

        @Override
        protected void onDetachedFromWindow() {
                // Interrompiamo l'animazione quando l'activity diventa invisibile
                mHandler.removeCallbacks(mRunnable);
                super.onDetachedFromWindow();
        }
}