Красивый таймер для Android

device-2014-09-18-144858_optВ разработке приложения, выполняющего что-то периодически по времени, есть несколько важных моментов.

Все большие вычисления нельзя проводить в главном потоке(UI), т.к. пользователь будет сильно страдать от зависаний. Для реализации числового таймера, нам нужно раз в секунду обновлять компонент TextView

Запускать обновление будем по кнопке, также сделаем паузу и сброс отчета.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Вот код разметки главной активности activity_main.xml:

[cce lang=»xml» tab_size=»2″ no_links=»false»]

<RelativeLayout xmlns:android=»http://schemas.android.com/apk/res/android»
xmlns:mySwitch=»http://schemas.android.com/apk/res-auto»
android:id=»@+id/RelativeLayout1″
android:layout_width=»match_parent»
android:layout_height=»match_parent»
android:orientation=»horizontal» >

<TextView
android:id=»@+id/tv_digital_clock»
android:layout_width=»wrap_content»
android:layout_height=»wrap_content»
android:layout_alignParentTop=»true»
android:layout_centerHorizontal=»true»
android:layout_marginTop=»95dp»
android:text=»00:00″
android:textAppearance=»?android:attr/textAppearanceMedium»
android:textColor=»@android:color/holo_red_dark»
android:textSize=»100sp» />

<Button
android:id=»@+id/pause_button»
android:layout_width=»wrap_content»
android:layout_height=»wrap_content»
android:layout_centerHorizontal=»true»
android:layout_centerVertical=»true»
android:background=»@drawable/small_buttonshape»
android:minWidth=»48dip»
android:onClick=»onPauseButtonClick»
android:text=»Пауза»
android:textAppearance=»?android:attr/textAppearanceSmall»
android:textColor=»@android:color/white» />

<Button
android:id=»@+id/start_button»
android:layout_width=»wrap_content»
android:layout_height=»wrap_content»
android:layout_alignBaseline=»@+id/pause_button»
android:layout_alignBottom=»@+id/pause_button»
android:layout_marginRight=»18dp»
android:layout_toLeftOf=»@+id/pause_button»
android:background=»@drawable/small_buttonshape»
android:minWidth=»48dip»
android:onClick=»onStartButtonClick»
android:text=»Старт»
android:textAppearance=»?android:attr/textAppearanceSmall»
android:textColor=»@android:color/white» />

<Button
android:id=»@+id/reset_button»
android:layout_width=»wrap_content»
android:layout_height=»wrap_content»
android:layout_alignBaseline=»@+id/pause_button»
android:layout_alignBottom=»@+id/pause_button»
android:layout_marginLeft=»15dp»
android:layout_toRightOf=»@+id/pause_button»
android:background=»@drawable/small_buttonshape»
android:minWidth=»28dp»
android:onClick=»onResetButtonClick»
android:text=»Сброс»
android:textAppearance=»?android:attr/textAppearanceSmall»
android:textColor=»@android:color/white» />

</RelativeLayout>

[/cce]

На ней у нас присутствуют 3 Button и один TextView.

Чтобы выводить цифры красивым шрифтом, его нужно зашить во внутрь приложения и при запуске установить на нужный элемент.

Мы будем использовать шрифт DS-Digital (TrueType Fonts).

Файл со шрифтом нужно положить в папку assets.

Снимок

При запуске приложения установим шрифт (Typeface) на элемент tv_digital_clock (TextView).

[cce lang=»java» tab_size=»2″ no_links=»false»]

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
digital_clock = (TextView) findViewById(R.id.tv_digital_clock);
tf = Typeface.createFromAsset(getAssets(), «DS-DIGI.TTF»);
digital_clock.setTypeface(tf);
}

[/cce]

Как работать с классом Timer я подсмотрел на этом сайте. Тут все доходчиво разъяснено, хоть и не на русском.

Он по смыслу очень похож на дельфийский, т.е. задаем интервал для выполнения одних и тех же действий.

Если нужно выключить таймер вызываем Timer.cancel().

Вот код кнопки запуска:

[cce lang=»java» tab_size=»2″ no_links=»false»]

public void onStartButtonClick(View v) {
myTimer = new Timer();
myTimer.schedule(new TimerTask() {
@Override
public void run() {
TimerMethod();
}
}, 0, 1000);
};

[/cce]

Создали новый класс Timer и задали ему расписание (schedule). Первый параметр означает задержку запуска, второй параметр (1000) означает период в миллисекундах, т.е. одна секунда.

Вот метод и дочерний поток:

[cce lang=»java» tab_size=»2″ no_links=»false»]

private void TimerMethod() {
// This method is called directly by the timer
// and runs in the same thread as the timer.

// We call the method that will work with the UI
// through the runOnUiThread method.
this.runOnUiThread(Timer_Tick);
}

private Runnable Timer_Tick = new Runnable() {
public void run() {

mCurrentPeriod++;
String temp = (new SimpleDateFormat(«mm:ss»)).format(new Date(
mCurrentPeriod * 1000));
digital_clock.setText(temp);
// This method runs in the same thread as the UI.
// Do something to the UI thread here

}
};

[/cce]

Тут интересен способ получения форматированной сточки обозначающей время и исходного числа секунд.

Мы создаем новый тип Date, на вход подаем количество миллисекунд. Потом применяем форматирование даты при помощи SimpleDateFormat. Нужный шаблон указан как минуты и секунды «mm:ss».

Вот код двух других кнопок:

[cce lang=»java» tab_size=»2″ no_links=»false»]
public void onPauseButtonClick(View v) {
if (myTimer != null)
myTimer.cancel();
};

public void onResetButtonClick(View v) {
mCurrentPeriod = 0;
if (myTimer != null)
myTimer.cancel();
digital_clock.setText(«00:00»);
};

[/cce]

А при выходе из приложения мы обязательно его спросим:

[cce lang=»java» tab_size=»2″ no_links=»false»]
public void onBackPressed() {
new AlertDialog.Builder(this)
.setMessage(«Вы действительно хотите покинуть программу?»)
.setCancelable(false)
.setPositiveButton(«Да», new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
finish();
}
}).setNegativeButton(«Нет», null).show();
};

[/cce]

На всякий случай здесь весь класс MainActivity целиком:

[cce lang=»java» tab_size=»2″ no_links=»false»]

package com.rusdelphi.timer;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;

import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.graphics.Typeface;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;

public class MainActivity extends Activity {
TextView digital_clock;
Typeface tf;

int mCurrentPeriod = 0;
private Timer myTimer;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
digital_clock = (TextView) findViewById(R.id.tv_digital_clock);
tf = Typeface.createFromAsset(getAssets(), «DS-DIGI.TTF»);
digital_clock.setTypeface(tf);
}

public void onBackPressed() {
new AlertDialog.Builder(this)
.setMessage(«Вы действительно хотите покинуть программу?»)
.setCancelable(false)
.setPositiveButton(«Да», new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
finish();
}
}).setNegativeButton(«Нет», null).show();
};

public void onStartButtonClick(View v) {
myTimer = new Timer();
myTimer.schedule(new TimerTask() {
@Override
public void run() {
TimerMethod();
}
}, 0, 1000);
};

public void onPauseButtonClick(View v) {
if (myTimer != null)
myTimer.cancel();
};

public void onResetButtonClick(View v) {
mCurrentPeriod = 0;
if (myTimer != null)
myTimer.cancel();
digital_clock.setText(«00:00»);
};

private void TimerMethod() {
// This method is called directly by the timer
// and runs in the same thread as the timer.

// We call the method that will work with the UI
// through the runOnUiThread method.
this.runOnUiThread(Timer_Tick);
}

private Runnable Timer_Tick = new Runnable() {
public void run() {
mCurrentPeriod++;
String temp = (new SimpleDateFormat(«mm:ss»)).format(new Date(
mCurrentPeriod * 1000));
digital_clock.setText(temp);
// This method runs in the same thread as the UI.
// Do something to the UI thread here

}
};

}

[/cce]

Красивый таймер для Android: 12 комментариев

  1. Григорий

    Здравствуйте! А не могли бы вы в объяснении показать как выглядит базовый шаблон класса таймер? Мне как новичку не все понятно.

    Например, обработчик запуска таймера содержит код:
    public void onStart(View view) {
    mTimer = new Timer();
    mTimer.schedule(new TimerTask() {
    @Override
    public void run() {
    TimerMethod();
    }
    });
    }

    я понимаю это базовый шаблон, где мы инициализируем наш таймер,
    затем вызываем метод schedule( ), в котором как условие создаем новый метод new TimerTask, в котором в свою очередь переопределяем метод run ( ); и в нем уже запускаем свой код.

    В нашем случае этим кодом является метод(?) — TimerMethod ();

    В этом методе есть строка:
    this.runOnUiThread(Timer_Tick);

    Тут я уже начинаю терять суть. хотелось бы пояснить эту строку кода. Судя по названию, это запуск в основном потоке. Ведь таймер работает в своем собственном потоке.

    И далее Timer_Tick это переменная интерфейса Runnable, в котором я уже совсем потерялся.

    Хотелось бы поподробнее что этот Runnable умеет и как выглядит его базовый шаблон. То есть для чего его вызывают и какие команды в нем обычно используют.

    1. blog

      Доступ ко всем элементам интерфейса должен проходить в главном потоке. Потому мы и говорим runOnUiThread, т.е. работай в главном потоке.Вот же все по-русски написано:
      // This method is called directly by the timer
      // and runs in the same thread as the timer.
      // We call the method that will work with the UI
      // through the runOnUiThread method.
      Этот метод вызывается прямо из таймера и работает в том же потоке что и таймер. А потом нам нужно его переслать в главный поток.
      Иными словами, нам нужен поток, который периодически стучится в главный поток с UI, чтобы обновить интерфейс.

      1. Григорий

        Не по русски, а по английски ;))
        То есть это шаблонная структура? Уже сам код внутри Runnable > Run () { необходимы код }

        1. blog

          Runnable это interface, и у него один абстрактный класс run, который нужно реализовать самому. Вот же пацаны пишут Often used to run code in a different Thread. И без русско-болгарского разговорника все понятно )

          1. Григорий

            Окей) буду пилить этот вариант реализации таймера))

  2. Григорий

    Я прочитал описание Runnable по ссылке из примера, но хотелось бы живой пример и лучше на нашем примере все разобрать))

  3. Андрей

    Очень понятная статья. Спасибо.
    Нашел дефект: если нажать еще раз на кнопку старта (что очень вероятно со смартфонами), то создается еще один поток и время начинает идти быстрее.
    С потоками побоялся, что-то делать, так что починил путем добавления:
    Button startButton = (Button) findViewById(R.id.start_button);
    startButton.setClickable(false);
    В метод запуска таймера и такую же разблокировку в метод для кнопки паузы.

    1. Сергей

      Решил эту проблему тем, что повесил на кнопку условие с переменными, которые контролируют работу потока, типа если раз нажал, переменную изменил, и если переменная не та, что нужна, то не выполнять код повторного запуска потока)

      1. Владимир

        Вам нужно работу по отсчету времени вынести в отдельный сервис, и из него отправлять широковещательные сообщения в активити.

  4. Владимир

    Не запустилось.
    FATAL EXCEPTION: main
    01-03 15:12:54.106 1358-1358/? E/AndroidRuntime: java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example.user.timer/com.example.user.timer.MainActivity}: java.lang.RuntimeException: native typeface cannot be made

  5. Сергей

    Привет, сделал по вашему примеру таймер, использовал не весь код, кусками и многое переделал под себя. Но нетронутыми оставил работу процесса таймера в пользовательском потоке. Протестировал приложение, и выявил проблему в том, что таймер работает чуть больше одной минуты, в случае если приложение свернуто или экран телефона заблокирован. Полагаю, что пользовательский процесс имеет свойство останавливаться, если устройство не используется. По этой причине у меня возник вопрос, в каком потоке, в таком случае, нужно запускать код таймера, чтобы он работал до тех пор пока время не выйдет? Понимаю, что статья уже достаточно старая, но вдруг случится чудо и мне ответят, не автор, так может кто-то из пользователей кто сталкивался с такой же проблемой)) з.ы. спасибо автору за урок, несколько новых фишек для себя взял на вооружение

  6. Дмитрий

    Здравствуйте.
    Не могли бы вы выложить ссылку на проект ?

Добавить комментарий для Владимир Отменить ответ

Ваш адрес email не будет опубликован. Обязательные поля помечены *