quinta-feira, 17 de dezembro de 2015

Como criar um contador preciso em Android utilizando TimerTask

A maioria dos exemplos de códigos em Android de contadores executam todo código na Activity principal. 

Neste exemplo mostro uma abordagem de como fazer um contador utilizando TimerTask em uma Classe separada com métodos para controlar o estado de execução , deixando o código mais limpo.

Crie uma classe chamada ContadorTimerTask.java

public class ContadorTimertask {
    private TextView txtView;
    private Timer timer;
    private MyTimerTask myTimerTask;
    private Handler handler;
    private int counter = 0;
    private long delay = 1000;    // Atraso inicial
    private long interval = 1000; // Intervalo de execução
    boolean running = false;

    public ContadorTimertask(TextView txtView) {
        this.txtView = txtView;
        handler = new Handler();
    }
    public long getInterval() {
        return interval;
    }
    public void setInterval(long interval) {
        this.interval = interval;
        this.delay = interval;
    }

    public boolean isRunning() {
        return running;
    }
    public void start() {
        if (running == false) {
            running = true;
            timer = new Timer();
            timer.schedule(new MyTimerTask(), delay, interval);
        } else {
            Log.i("LOG", "ALREADY RUNNING");
        }
    }
    public void stop() {
        running = false;
        if (timer != null) {
            timer.cancel();
        }
        counter = 0;
        txtView.setText("0");
    }
    public void pause() {
        running = false;
        if (timer != null) {
            timer.cancel();
        }
    }
    class MyTimerTask extends TimerTask {
        @Override
        public void run() {
            handler.post(new Runnable() {
                @Override
                public void run() {
                    counter++;
                    txtView.setText("" + counter);
                }
            });
        }
    }

}


Como utilizar esta classe:
1) Cria uma instância da classe e passe o EditText no construtor
2) Configure o intervalo de execução
3) Utilize os métodos start(), stop(), pause()

Exemplo de como utilizar a Classe no MainActivity


public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        final Button startButton = (Button) findViewById(R.id.start_button);
        final Button stopButton = (Button) findViewById(R.id.stop_button);
        final TextView txtView = (TextView) findViewById(R.id.txtview);

        // Crie uma nova instância da classe
        final ContadorTimertask contador = new ContadorTimertask(txtView);
        // Configure o intervalo de execução
        contador.setInterval(1000); // Executa a cada 1 segundo
       // Evento do clique do botão start         startButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (!contador.isRunning()) {
                    contador.start();    // Inicia o contador
                    startButton.setText("PAUSAR");
                } else {
                    contador.pause();   // Pausa o contador
                    startButton.setText("CONTINUAR");
                }
            }
        });
        // Evento de clique do botão stop
        stopButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                contador.stop();      // Para o contador (zera contagem)
                startButton.setText("INICIAR");
            }
        });
    }

}

Vídeo do exemplo:





quarta-feira, 16 de dezembro de 2015

Criando um contador em Android utilizando Handle em uma classe externa

A maioria dos exemplos de códigos de Threads e Handlers em Android executam todo código na Activity principal. 
Neste exemplo mostro uma abordagem de separar a Thread em uma Classe separada com métodos para controlar o estado de execução da Thread, deixando o código mais limpo.


Crie uma classe ContadorHandle.class. Esta classe receberá o EditText onde será atualizado o contador.

public class ContadorHandle implements Runnable {
    private TextView txtView;
    private Handler handler;
    private boolean running = false;
    private int counter = 0;
    private int interval=1000;

    public ContadorHandle(TextView txtView) {

        this.txtView = txtView;
        handler = new Handler();
    }
    public int getInterval() {
        return interval;
    }
    public void setInterval(int interval) {
        this.interval = interval;
    }
    public void start() {
        if (running == false) {
            running = true;
            handler.postDelayed(this, interval);
            Log.i("LOG", "START");
        } else{
            Log.i("LOG", "ALREADY RUNNING");
        }
    }
    public void resume(){
        start();
    }
    public void restart(){
        stop();
        start();
    }
    public void stop() {
        Log.i("LOG", "STOPPED");
        running = false;
        handler.removeCallbacks(this);
        // No stop eu reinicio as variaveis       
        counter = 0;
        txtView.setText("" + 0);
    }
    public void pause() {
        Log.i("LOG", "PAUSED");
        running = false;
        handler.removeCallbacks(this);
    }    public boolean isRunning() {
        return running;
    }
    @Override    
     public void run() {
        counter++;
        handler.postDelayed(this, interval);
        txtView.setText("" + counter);
    }
}

Como utilizar esta classe:
 1) Instancie a classe 
 2) Configure o intervalo de tempo
 3) Utilize os métodos para controlar o estado da Thread.

Exemplo de utilização na MainActivity

@Override 

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    final Button startButton = (Button) findViewById(R.id.start_button);

    final Button stopButton = (Button) findViewById(R.id.stop_button);
    final TextView txtView = (TextView) findViewById(R.id.txtview);

    // Instancia o contador com handle e passa o TextView que será atualizado

    final ContadorHandle contador = new ContadorHandle(txtView);

    // Configure o intervalo de execução

    contador.setInterval(1000); // 1 segundo

    // Se clicar no botão PAUSAR mudar o texto para CONTINUAR

    startButton.setOnClickListener(new View.OnClickListener() {
        @Override 
        public void onClick(View v) {
            if (!contador.isRunning()) {
                contador.start();    // Inicia ou continua a contagem
                startButton.setText("PAUSAR");
            } else {
                contador.pause();    // Pausa a contagem
                startButton.setText("CONTINUAR");
            }
        }
    });

    // Evento de clique do botão STOP     
       stopButton.setOnClickListener(new View.OnClickListener() {
        @Override        
        public void onClick(View v) {
            contador.stop();        // Para o contador (volta em zero)
            startButton.setText("INICIAR");  
        }
    });

}


Arquivo activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout    
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"    
android:orientation="vertical"    
app:layout_behavior="@string/appbar_scrolling_view_behavior"    
tools:context=".MainActivity"    
tools:showIn="@layout/activity_main">

    <LinearLayout        

 android:orientation="horizontal"        
 android:layout_width="match_parent"        
 android:layout_height="wrap_content">

        <TextView            

  android:layout_width="100dp"            
  android:layout_height="wrap_content"            
  android:textAppearance="?android:attr/textAppearanceMedium"            
  android:text="HANDLE"            
  android:id="@+id/textView"            />

        <TextView            

  android:id="@+id/txtview"            
  android:layout_width="wrap_content"            
  android:layout_height="wrap_content"            
  android:gravity="center_horizontal"            
  android:text="0"            
  android:textSize="30dp"            
  android:layout_weight="1"/>
    </LinearLayout>

    <LinearLayout        

 android:layout_width="match_parent"        
 android:layout_height="wrap_content"        
 android:orientation="horizontal">

        <Button            

  android:id="@+id/start_button"            
  android:layout_width="wrap_content"            
  android:layout_height="wrap_content"            
  android:layout_weight="1"            
  android:text="INICIAR"/>

        <Button            

  android:id="@+id/stop_button"            
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"            
  android:layout_weight="1"            
  android:text="PARAR"/>
    </LinearLayout>

</LinearLayout>


Vídeo do contador:




A desvantagem desse exemplo é que utilizando Handler desta forma não oferece uma precisão muito boa, com o tempo é possível verificar que o contador começa atrasar a contagem de segundos. De qualquer forma, essa classe permite ser adaptada para outros fins onde não é necessário muita precisão, como por exemplo fazer a animação de uma figura.


Exemplo da classe ContadorHandle adaptada para girar uma figura:

public class RotateImageHandle implements Runnable {
    private ImageView imageView;
    private Handler handler;
    private boolean running = false;
    private float rotation = 0;
    private int interval = 1;

    public RotateImageHandle(ImageView imageView) {
        this.imageView = imageView;
        handler = new Handler();
    }

    public int getInterval() {
        return interval;
    }

    public void setInterval(int interval) {
        this.interval = interval;
    }

    public void start() {
        if (running == false) {
            running = true;
            handler.postDelayed(this, interval);
            Log.i("LOG", "START");
        } else {
            Log.i("LOG", "ALREADY RUNNING");
        }
    }

    public void stop() {
        Log.i("LOG", "STOP");
        running = false;
        handler.removeCallbacks(this);
        // No stop eu reinicio as variaveis
        rotation = 0;
        imageView.setRotation(0);
    }

    public void pause() {
        Log.i("LOG", "PAUSE");
        running = false;
        handler.removeCallbacks(this);
    }

    public boolean isRunning() {
        return running;
    }

    @Override
    public void run() {
        rotation = rotation + 8;
        handler.postDelayed(this, interval);
        imageView.setRotation(rotation);
    }
}

Esta classe funciona igual ao ContadorHandle, mas ao invés de receber um EditText recebe um ImageView, e começa a rotacionar a imagem com o método start.

Para criar um contador com maior precisão é necessário utilizar a classe TimerTask, que mostro no post a seguir.

Até mais.