Как написать приложение таймер

Как сделать таймер

Создаём собственный таймер-напоминалку

Таймеров и трекеров полно, но мы сделаем такой, какой нужен именно вам. Это легко.

Создаём собственный таймер-напоминалку

Таймеров и трекеров полно, но мы сделаем такой, какой нужен именно вам. Это легко.

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

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

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

Решение

Алгоритм решения будет таким:

  1. Спрашиваем, о чём напомнить и через сколько минут.
  2. Когда настанет нужное время — выводим напоминание и привлекаем внимание к этой вкладке.

Как и все простые программы, мы будем писать эту прямо в консоли «Инспектора». Чтобы его открыть, найдите в меню браузера пункт «Консоль» (обычно где-то в инструментах разработчика) или нажмите Cmd + Alt + I или Ctrl + Alt + I. Убедитесь, что у вас открыта консоль. Всё, что мы будем писать в ней, будет сразу исполняться браузером, а это именно то, что нам нужно.

Приветствие и вопрос

Начнём с приветствия:

// Начало основной программы, приветствуем пользователя
alert('Привет! Я — ваш таймер, который может вам напомнить о чём-то. Я буду работать до тех пор, пока вы не закроете эту страницу.');

Теперь узнаем, о чём нужно напомнить и когда:

// Спрашиваем текст напоминания, который нужно потом показать пользователю
var text = prompt('О чём вам напомнить?');
// Тут будем хранить время, через которое нужно показать напоминание
var time = prompt('Через сколько минут?');
// Чтобы работать со временем в JavaScript, нам нужно перевести его в миллисекунды. Для этого число минут умножаем на 60 тысяч:
time = time * 60 * 1000;

Теперь у нас две переменные: text и time. В одной — о чём напомнить, в другой — через сколько миллисекунд. Теперь нужно научить программу выводить text через время time.
Чтобы что-то сработало через определённое время, используем функцию setTimeout — мы про неё говорили в задаче про чат-бота:

setTimeout(function () {
  команды_которые_нужно_сделать
  }, время_в_миллисекундах_через_которое_нужно_выполнить_эти_команды );

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

// Выводим на экран текст напоминания, который хранится в переменной text
alert(text);

А чтобы привлечь внимание к окну, применим программистскую магию и возьмём функцию titleAlert() — она уже встроена в эту страницу, поэтому её можно сразу использовать.

Функция — как мини-программа внутри основной программы. У каждой функции есть имя — если его написать в теле основной программы, то она поставится на паузу, сделает всё, что написано в функции, а потом продолжит с того места, где остановилась.

Как использовать встроенные в страницу функции — читайте в отдельной статье.

В итоге наша функция setTimeout будет выглядеть так:

setTimeout(function () {
  // Выводим на экран текст напоминания, который хранится в переменной text
  alert(text);
  // Привлекаем внимание к окну мигающим заголовком
  titleAlert();
  // В самом конце функции указываем время, через которое она должна сработать
  }, time)

Теперь собираем всё вместе:

// Спрашиваем текст напоминания, который нужно потом показать пользователю
var text = prompt('О чём вам напомнить?');
// Тут будем хранить время, через которое нужно показать напоминание
var time = prompt('Через сколько минут?');
// Чтобы работать со временем в JavaScript, нам нужно перевести его в миллисекунды. Для этого число минут умножаем на 60 тысяч:
time = time * 60 * 1000;
// Ставим таймер на нужное время с помощью функции setTimeout
setTimeout(function () {
  // Выводим на экран текст напоминания, который хранится в переменной text
  alert(text);
  // Привлекаем внимание к окну мигающим заголовком
  titleAlert();
  // В самом конце функции указываем время, через которое она должна сработать
}, time);

Эту программу можно улучшить — например, сделать помидорный таймер, который будет напоминать о чём-то не однократно, а постоянно, с равными интервалами.

Время на прочтение
13 мин

Количество просмотров 34K

Решил тут на досуге написать статейку о том, как писать приложения для Android’а. Писать будет простенький тайм менеджер. В этой части я напишу саму программу, приделаю к ней интерфейс с анимацией, а в следующей, если будет интересно, напишу к ней сервис, чтоб программа работала еще и в фоновом режиме.

Что будет рассмотрено:

  • RelativeLayout для реализации интерфейса программы с наложением изображений друг на друга.
  • Timer для реализации алгоритма подсчета времени.
  • Animation для свистоперделок красивого интерфейса апплета.

Для наглядности добавил скринкаст как все это добро работает.

Итак, пишем простенький тайм менеджер для Android’а.

Предположим, что SDK + Eclipse у нас уже установлены.


Запускаем Eclipse, и идем в File — New — Other.
В открывшемся окне выбираем Android — Android Project.


Откроется вот такое окно.
Вбиваем параметры:
Project Name — это имя проекта, должно быть уникальным в текущем Workspace.
BuildTarget — версия ОС, для которой собирается проект. Следует помнить, что собирая проект для 1.5, он будет запускаться и на 1.6, и на 2.1, а если собирать для 1.6, то на 1.5 уже нет.
Application Name — название приложения. Будет отображаться в главном меню, откуда его можно будет запустить.
Package Name — com.<название конторы>.<название апплета>
Create Activity — имя Activity для запуска приложения.
Так как скрин делался после написания проекта, вы можете заметить у меня сверху ошибка «There is already a file …» из-за того, что проект с таким именем уже создан.
Жмем Finish.

Интерфейс


Вот к такому виду его надо привести, при этом размер текста таймеров должен быть одинаковым, увеличиваться уменьшаться благодаря Animation.
Для этого используем RelativeLayout с тремя чилдренами: ImageView, для тени вверху апплета, и двумя LinearLayout’ами, один для таймеров и еще один для кнопок внизу.
Если есть время и возможность можете нарисовать все сами, но на всякий пожарный прикладываю архивчик со всеми графическими элементами.

TimeTracker.zip (194Кб)
Распаковываем и кидаем в директорию drawable файлы bg.png, *.9.png, icon.png и topshadow.png.
9.png это 9-patch drawable. Поподробнее о нем можно почитать на developer.android.com/guide/developing/tools/draw9patch.html, замечу лишь, что эти файлы имеют возможность растягивать только определенные части изображения при необходимости. У меня таким образом работают кнопки. Если текст содержимого больше исходного изображения, либо у кнопки стоит атрибут layout_width / height = fill_parent, то android растянет не весь битмап, а только отмеченные участки.

Когда начинал делать не учел, но на будущее замечу, что эти изображения должны иметь минимальную длинну и ширину, у меня же ширина — 100px, поэтому кнопку уже ста пикселей я уже не сделаю.

Теперь необходимо дать знать android’у как растягивать фоновое изображение, header и менять картинки от состояния кнопки.
Для этого создаем в папке drawable файлы: background.xml, greebutton.xml, redbutton.xml и header.xml.

В первый файл пишем:

<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
android:src="@drawable/bg"
android:tileMode="repeat"/>

src — путь к битмапу, а именно к файлу bg.png, что лежит в папке drawable.
tileMode — как им правильно зарисовать поверхность.

Для greenbutton.xml:

<?xml version="1.0" encoding="utf-8"?#62;
<selector xmlns:android="http://schemas.android.com/apk/res/android">
	<item android:state_pressed="true"
		android:drawable="@drawable/greenbutton_dark" /> <!-- pressed -->
	<item android:state_focused="true"
		android:drawable="@drawable/greenbutton_dark" /> <!-- focused -->
	<item android:drawable="@drawable/greenbutton_light" /> <!-- default -->
 </selector>

Указываем битмап для каждого состояния кнопки. Можете, например добавить картинку для состояния focused.

Для redbutton.xml все так же как и в greenbutton.xml, за исключением битмапов. На хабре люди умные, думаю поймете, что туда надо написать.
Для header.xml тоже все так же как и в background.xml, только с другим битмапом.

Перебираемся в папочку layout — там лежат xml файлы, в которых описывается интерфейс апплета.
В нашем случае там лежит один единственный файл, открываем его и приводим к такому виду:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:background="@drawable/background"
    >
</RelativeLayout>

RelativeLayout позволяет размещать элементы относительно себя или друг друга. Благодаря этому мы сможем наложить на header таймеры.
Я описал следующие параметры:
orientation — ориентация чилдренов этого лэйаута.
К примеру если разместить две кнопки в LinearLayout с параметром orientation=«vertical», то они будут друг под другом, а с параметром orientation=«horizontal», друг за другом.
layout_width и layout_height — ширина и высота. Принимают три типа значения: fill_parent (принять параметр родителя), wrap_content (минимальное значение, где содержание не будет при этом обрезано), либо числовое значение.
background — фон. Немного выше мы создали background.xml в папке drawable, вот мы к нему сейчас и обращаемся.
Поясню, «@<фолдер в папке res>/<файл в этом фолдере>». Таким же образом задаются идентификаторы, но об этом чуть позже.
Затем внутри RelativeLayout’а мы создаем еще 3 элемента, о которых я говорил чуть выше.
ImageView

<ImageView
	android:src="@drawable/header"
	android:layout_width="fill_parent"
	android:layout_height="100px"
	android:scaleType="fitXY"
	android:layout_alignParentTop="true"
/>

src — путь к header.xml, который мы недавно создали, в котором лежат параметры для тени вверху апплета.
scaleType — как его растягивать.
В принципе можно было обойтись без header.xml, а сразу указать путь к topshadow.png.
layout_alignParentTop — параметры такого типа есть у каждого чилдрена RelativeView, и указывают, где разместить элемент. Конкретно этот говорит о том, что рисовать надо у верхней границы родителя, тоесть RelativeView.

LinearLayout с таймерами

<LinearLayout
	android:orientation="vertical"
	android:layout_width="fill_parent"
	android:layout_height="200dip"
	android:layout_centerVertical="true"
	android:layout_centerInParent="true"
>
	<TextView
		android:text="00:00:00"
		android:id="@+id/rest_timer"
		android:textColor="#AAFFAA"
		android:textSize="40dip"
		android:gravity="center_horizontal|bottom"
		android:singleLine="true"
		android:layout_width="fill_parent"
		android:layout_height="80dip"
	/>
	<TextView
		android:text="00:00:00"
		android:id="@+id/work_timer"
		android:textColor="#FFAAAA"
		android:textSize="40dip"
		android:gravity="center_horizontal|top"
		android:singleLine="true"
		android:layout_width="fill_parent"
		android:layout_height="80dip"
	/>
</LinearLayout>

id — идентификатор виджета. Для создания нового идентификатора используется «@+id/<имя>», для доступа к уже существующему, например в настройках надо сделать пункт зависимым от выбора другого пункта «@id/<имя>».
textColor — цвет текста. Задается либо как #RRGGBB, либо #AARRGGBB, где AA — hex альфа канала (прозрачность), RR, GG и BB — hex каждого цвета.
singleLine — текст в одну строку.
gravity — выравнивание самого текста.
text — попробуйте сами догадаться XD.

LinearLayout с кнопками

<LinearLayout
	android:layout_alignParentBottom="true"
	android:layout_width="fill_parent"
	android:layout_height="wrap_content"
	android:orientation="horizontal"
	>
	<Button
		android:id="@+id/rest"
		android:layout_weight="1"
		android:text="@string/button_rest"
		android:textSize="18dip"
		android:textColor="#FFFFFF"
		android:textStyle="bold"
		android:layout_height="wrap_content"
		android:layout_width="fill_parent"
		android:background="@drawable/greenbutton"
		/>
	<Button
		android:id="@+id/work"
		android:layout_weight="1"
		android:text="@string/button_work"
		android:textSize="18dip"
		android:textColor="#FFFFFF"
		android:textStyle="bold"
		android:layout_height="wrap_content"
		android:layout_width="fill_parent"
		android:background="@drawable/redbutton"
		
		/>	
</LinearLayout>

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

Анимация

Принцип такой. Жмем кнопку, один таймер уменьшается, другой увеличивается.
Создаем в папке res подпапку anim. Туда пихаем 4 xml файла: magnify_rest.xml, shrink_rest.xml, magnify_work.xml и shrink_work.xml.
magnify — увеличение таймера.
shrink — уменьшение таймера.

Открываем magnify_rest.xml.

<?xml version="1.0" encoding="utf-8"?>
<scale xmlns:android="http://schemas.android.com/apk/res/android"
	android:fromXScale="1.0"
	android:toXScale="2.0"
	android:fromYScale="1.0"
	android:toYScale="2.0"
	android:pivotX="50%"
	android:pivotY="100%"
	android:startOffset="0"
	android:duration="400"
/>

fromXScale и fromYScale — значения размеров до начала анимации. Если поставить 2.0, то текст сначала резко увеличится в два раза, и уже от этого значения будет менять свой размер.
toXScale и toYScale — значения размеров к концу анимации. У меня стоит 2.0, тоесть текст будет увеличиваться в два раза от

исходного

значения, заданного в layout’е, а не от fromXScale и fromYScale.
pivotX и pivotY — точки, от которых происходит анимация. К примеру: если поставить в обоих по 50%, то текст будет увеличен во все стороны, если pivotX = 0%, то текст будет увеличиваться вправо, 100% — влево.
startOffset — промежуток до начала анимации.
duration — время анимации в миллисекундах.

shrink_rest.xml:

<?xml version="1.0" encoding="utf-8"?>
<scale xmlns:android="http://schemas.android.com/apk/res/android"
	android:fromXScale="2.0"
	android:toXScale="1.0"
	android:fromYScale="2.0"
	android:toYScale="1.0"
	android:pivotX="50%"
	android:pivotY="100%"
	android:startOffset="0"
	android:duration="400"
/>

Тут все тоже самое, за исключением размеров.

В итоге я хочу получить:

два таймера друг над другом, выравненных по вертикальному центру родителя. Жму кнопку, верхний увеличивается вверх, нижний уменьшается тоже вверх. Жму на другую, и они уменьшаются и увеличиваются соответственно, только уже вниз. Все это сделано для того, чтоб они не наезжали друг на друга.
Поэтому в верхних двух pivot’ы указаны как нижняя центральная точка. В двух других файлах все тоже самое, за исключением того, что pivot’ы уже указывают на верхнюю центральную точку, тоесть 50% и 0%.

Строки и локализация

Идем в res/values/strings.xml и приводим его к такому виду:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="hello">Hello World, Main!</string>
    <string name="app_name">Time Manager</string>
    
    <string name="button_work">Work</string>
    <string name="button_rest">Rest</string>
</resources>

name — чуть ранее я упоминал про это, так вот конструкция string/hello выдаст нам Hello World, Main!
В этом файле содержаться все строки для использования в программе.

Для локализации создаем директорию values-<локаль>, в нашем случае — values-ru, и копируем туда файл strings.xml, затем открываем его и переводим на русский значения строк.

Программа

Дело за малым. Осталось написать саму программу для того, чтоб вся эта красота зашевелилась, затикала и занажималась.
Открываем java файл в папке src. Он там должен быть один.
Вверху видим название package, в моем случае com.nixan.timetracker.
Потом идут импорты других классов.
Затем объявление самого класса:

public class Main extends Activity

У вас вместо Main может быть что-нибудь еще, а именно, то, что мы задавали в create activity при создании проекта.
Activity — это… аналог форм под виндой, скажем так. Есть еще ListActivity — перечесляемый список каких — нибудь элементов, PreferenceActivity — окно с настройками, при этом все они определяются в отдельном xml файле и нет необходимости изобретать layout под всю эту красоту, но речь не об этом.
У этого класса есть метод onCreate(), который вызывается при создании окна. Таких методов несколько, подробнее можете посмотреть developer.android.com/guide/topics/fundamentals.html#actlife

Перед onCreate задаем переменные:

Button rest_button;
Button work_button;
TextView rest_timer;
TextView work_timer;
Animation magnify_rest;
Animation shrink_rest;
Animation magnify_work;
Animation shrink_work;
TimerTask counter;
Timer timer;
boolean resting;
boolean working;
int rest_time;
int work_time;
SharedPreferences.Editor stats_editor;

rest_button и work_button — кнопки переключения работа/отдых.
rest_timer и work_timer — текст, отображающий текущие значения таймера.
magnify_rest, shrink_rest, magnify_work и shrink_work — анимации, которые мы задавали в xml файле.
counter — то, что таймер будет делать.
timer — сам таймер.
resting и working — переменные, в которых хранятся текущие состояния.
rest_time и work_time — собственно сколько секунд проработали и отдохнули.
stats_editor — настройки, в них хранятся отработанные и отдохнувшие секунды, для того, чтоб закрыв программу, таймер не сбрасывался.

Итак поехали!

Первым делом проверяем, есть ли вызов onCreate родителя.

super.onCreate(savedInstanceState);

Если нет, то дописываем.

Затем говорим этому Activity, какой именно layout надо отрисовать.

setContentView(R.layout.main);

R.layout.main — аналог «@layout/main», где main это main.xml в папке layout. Если ваш layout называется не main, поправьте вызов.
Перед setContentView(); можно дописать еще requestWindowFeature();, у которой в параметрах можно передать, например Window.FEATURE_NO_TITLE, чтоб убрать серый заголовок вверху окна.

Получаем сохраненные параметры таймеров:

SharedPreferences saved_stats = PreferenceManager.getDefaultSharedPreferences(this);
rest_time = saved_stats.getInt("key_rest_time", 0);
work_time = saved_stats.getInt("key_work_time", 0);

getInt — методу передаются два параметра: строка указывающая на имя параметра и параметр по-умолчанию, если строка не найдена.

Инициализируем переменные:

stats_editor = PreferenceManager.getDefaultSharedPreferences(this).edit();
magnify_rest = AnimationUtils.loadAnimation(this, R.anim.magnify_rest);
shrink_rest = AnimationUtils.loadAnimation(this, R.anim.shrink_rest);
magnify_work = AnimationUtils.loadAnimation(this, R.anim.magnify_work);
shrink_work = AnimationUtils.loadAnimation(this, R.anim.shrink_work);
resting = false;
working = false;
rest_timer = (TextView) findViewById(R.id.rest_timer);
work_timer = (TextView) findViewById(R.id.work_timer);
rest_button = (Button) findViewById(R.id.rest);
work_button = (Button) findViewById(R.id.work);

В моем случае я сделал преобразование секунд в строку ЧЧ: ММ: СС двумя разными способами.
Заранее sorry for my bad Java, но думается мне, что первый кушает меньше памяти, но работает медленнее, а другой наоборот, поэтому сильно не ругайте.

private String getTime(int time)
	{
		String result = "";
		int hours = time/3600;
		int minutes = (time - (hours * 3600))/60;
		int seconds = (time - (hours * 3600) - (minutes * 60));
		result = String.valueOf(hours) + ":";
		if (minutes < 10)
		{
			result += "0"+String.valueOf(minutes)+":";
		}
		else
		{
			result += String.valueOf(minutes)+":";
		}
		if (seconds < 10)
		{
			result += "0"+String.valueOf(seconds);
		}
		else
		{
			result += String.valueOf(seconds);
		}
		return result;
	}

Я еще раз скажу, не знаю, насколько это правильно и грамотно, но вроде работает.

И сразу после инициализации переменных добавляем:

rest_timer.setText(getTime(rest_time));
work_timer.setText(getTime(work_time));

Таким образом выставляем ранее посчитанные секунды в текст таймеров.

Создаем задание для таймера:

counter = new TimerTask()
	{
		@Override
		public void run() {
		}
	};

Перед Override переменные, которые отвечают за секунды, минуты и часы таймера. Так как весь процесс у меня реализован в одном таймере, то по набору на отдых и работу:

int seconds_r = 0;
int hours_r = 0;
int minutes_r = 0;
String seconds_ind_r;
String minutes_ind_r;
String hours_ind_r;
int seconds_w = 0;
int hours_w = 0;
int minutes_w = 0;
String seconds_ind_w;
String minutes_ind_w;
String hours_ind_w;

И собственно задание счета:

if (resting)
	{
		if (seconds_r == 0 && minutes_r == 0 && hours_r == 0)
		{
			hours_r = rest_time/3600;
			minutes_r = (rest_time - (hours_r * 3600))/60;
			seconds_r = (rest_time - (hours_r * 3600) - (minutes_r * 60));							
		}
		rest_time++;
		seconds_r++;
		if (seconds_r >= 60)
		{
			seconds_r = 0;
			minutes_r++;
			if (minutes_r >= 60)
			{
				minutes_r = 0;
				hours_r++;
			}
		}
		if (seconds_r < 10)
		{
			seconds_ind_r = "0"+String.valueOf(seconds_r);
		}
		else
		{
			seconds_ind_r = String.valueOf(seconds_r);
		}
		if (minutes_r < 10)
		{
			minutes_ind_r = "0"+String.valueOf(minutes_r);
		}
		else
		{
			minutes_ind_r = String.valueOf(minutes_r);
		}
		hours_ind_r = String.valueOf(hours_r);
		runOnUiThread(new Runnable()
		{
			public void run() {
			// TODO Auto-generated method stub
			rest_timer.setText(hours_ind_r+":"+minutes_ind_r+":"+seconds_ind_r);
			}
		});  
	}

resting — булевая переменная, обозначающая, что мы отдыхаем.
runOnUiThread() — исполняет участок кода в потоке с интерфейсом.
Выше — участок кода для отдыха, для работы же просто после этого добавляется схожий код, только с набором секунд, минут и часов для работы, первый if меняется на if (working) и текст мы устанавливаем уже не для rest_timer’а, а для work_timer’а.

Дело за малым: обработка нажатий кнопок.

rest_button.setOnClickListener(new OnClickListener()
	{
		public void onClick(View v) {
		if (!resting)
		{
			rest_timer.startAnimation(magnify_rest);
			resting = true;
		}
		else
		{
			rest_timer.startAnimation(shrink_rest);
			resting = false;
			stats_editor.putInt("key_rest_time", rest_time);
		}
		if (working)
		{
			work_timer.startAnimation(shrink_work);
			working = false;
			stats_editor.putInt("key_work_time", work_time);
		}
	stats_editor.commit();
	}
});       

Если в данный момент мы не отдыхаем, то начинаем отдыхать.
Если отдыхаем, то прекращаем отдыхать.
Если работает, то прекращаем работать.
При этом меняются только логические переменные working и resting, которые каждую секунду проверяются в таймере, и если они включены, то время начинает увеличиваться.

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

Для кнопки работы все схоже.

Сохраняемя, подключаем трубку и запускаем приложение.

И сразу же находим первый баг. Если таймер запущен, и мы поворачиваем экран, то таймер останавливается.

Находим:

resting = false;
working = false;

И меняем на:

resting = saved_stats.getBoolean("key_resting", false);
working = saved_stats.getBoolean("key_working", false);
if (resting)
{
	rest_timer.startAnimation(magnify_rest);
}
if (working)
{
	work_timer.startAnimation(magnify_work);
}

Находим в каждом обработчике нажатий на кнопку:

stats_editor.commit();

И дописываем перед ним:

stats_editor.putBoolean("key_working", working);
stats_editor.putBoolean("key_resting", resting);

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

Запускаем, тыкаем, вроде все ок.

ЗЫ
Если стоит, продолжу чуть позже написанием сервиса, чтоб апплет висел в памяти и не было необходимости постоянно держать его запущенным.
ЗЫЫ
Исходники, если кому интересно — nixan.org/habr/timetracker/TimeManager.zip
ЗЫЫЫ
nixan.org/habr/timetracker/TimeManager.apk — пакет с прогой, либо в маркете pub:Nixan
ЗЫЫЫЫ
Добавил видеоролик.
ЗЫЫЫЫЫ
Простой Тайм Менеджер для Android. Часть 2

Как сделать таймер в android-приложении? Просто использовать java.util.Timer?

diralik's user avatar

diralik

9,2656 золотых знаков23 серебряных знака55 бронзовых знаков

задан 16 авг 2011 в 12:29

angry's user avatar

angryangry

8,64717 золотых знаков72 серебряных знака180 бронзовых знаков

Есть в ОС вот что — CountDownTimer
А вообще вот Вам примерчик:

Timer timer = new Timer();
timer.schedule(new UpdateTimeTask(), 0, 1000); //тикаем каждую секунду без задержки 
//задача для таймера
class UpdateTimeTask extends TimerTask {
    public void run() {
        ...
    } 
}

На самом деле, попробуйте сперва CountDownTimer.
А, вспомнил. Вот вам еще дока.

Только таймер работает один раз (тот что java.util.Timer), такая у него особенность, и нужно перехватывать IllegalStateException.

diralik's user avatar

diralik

9,2656 золотых знаков23 серебряных знака55 бронзовых знаков

ответ дан 16 авг 2011 в 12:37

DroidAlex's user avatar

Можно с помощью CountDownTimer.

public class MyTimer extends CountDownTimer
{

    public MyTimer(long millisInFuture, long countDownInterval) 
    {
          super(millisInFuture, countDownInterval);
    }

    @Override
    public void onFinish() 
    {
        // Do something...
    }

    public void onTick(long millisUntilFinished) 
    {

    }

}

ответ дан 16 авг 2011 в 12:36

AndroidDev's user avatar

AndroidDevAndroidDev

1,6842 золотых знака28 серебряных знаков58 бронзовых знаков

Если нужен простой таймер, можно использовать Chronometer.

Например, в layout помещаем chronometer:

<Chronometer
android:id="@+id/chronometer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>

В Activity инициализируем Chronometer, получаем время с момента старта приложения и устанавливаем его в качестве базового для нашего Cronometer:

Chronometer chronometer = (Chronometer) findViewById(R.id.chronometer);
long startTime = SystemClock.elapsedRealtime();
chronometer.setBase(startTime);

Получаем на экране обычный тикающий таймер.

Формат вывода времени можно поменять в setFormat(String format)

ответ дан 12 окт 2017 в 22:39

Aliaksei's user avatar

AliakseiAliaksei

4636 серебряных знаков16 бронзовых знаков

параметры

параметр подробности
long millisInFuture Общая продолжительность работы таймера, так как в будущем вы хотите, чтобы таймер заканчивался. В миллисекундах.
long countDownInterval Интервал, на который вы хотите получать обновления таймера. В миллисекундах.
long millisUntilFinished Параметр, указанный в onTick() , указывает, сколько времени осталось CountDownTimer. В миллисекундах

замечания

CountDownTimer — довольно скудный класс — он делает одну вещь очень хорошо. Поскольку вы можете только запустить / отменить CountDownTimer, вам нужно реализовать функции паузы / возобновления, как показано во втором примере. Для более сложных функций или для указания таймера, который должен выполняться неограниченное время, используйте объект Timer .

Создание простого таймера обратного отсчета

CountDownTimer полезен для многократного выполнения действия в устойчивом интервале для заданной продолжительности. В этом примере мы будем обновлять текстовое представление каждую секунду в течение 30 секунд, рассказывая, сколько времени осталось. Затем, когда таймер закончится, мы установим TextView, чтобы сказать «Готово».

TextView textView = (TextView)findViewById(R.id.text_view);

CountDownTimer countDownTimer = new CountDownTimer(30000, 1000) {
    public void onTick(long millisUntilFinished) {
        textView.setText(String.format(Locale.getDefault(), "%d sec.", millisUntilFinished / 1000L));
    }

    public void onFinish() {
        textView.setText("Done.");
    }
}.start();

Более сложный пример

В этом примере мы остановимся / возобновим CountDownTimer на основе жизненного цикла Activity.

private static final long TIMER_DURATION = 60000L;
private static final long TIMER_INTERVAL = 1000L;

private CountDownTimer mCountDownTimer;
private TextView textView;

private long mTimeRemaining;

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

    textView = (TextView)findViewById(R.id.text_view); // Define in xml layout.

    mCountDownTimer = new CountDownTimer(TIMER_DURATION, TIMER_INTERVAL) {
        
        @Override
        public void onTick(long millisUntilFinished) {
            textView.setText(String.format(Locale.getDefault(), "%d sec.", millisUntilFinished / 1000L));
            mTimeRemaining = millisUntilFinished; // Saving timeRemaining in Activity for pause/resume of CountDownTimer.
        }

        @Override
        public void onFinish() {
            textView.setText("Done.");
        }
    }.start();
}


@Override
protected void onResume() {
    super.onResume();
    
    if (mCountDownTimer == null) { // Timer was paused, re-create with saved time.
        mCountDownTimer = new CountDownTimer(timeRemaining, INTERVAL) {
            @Override
            public void onTick(long millisUntilFinished) {
                textView.setText(String.format(Locale.getDefault(), "%d sec.", millisUntilFinished / 1000L));
                timeRemaining = millisUntilFinished;
            }

            @Override
            public void onFinish() {
                textView.setText("Done.");
            }
        }.start();
    }
}

@Override
protected void onPause() {
    super.onPause();
    mCountDownTimer.cancel();
    mCountDownTimer = null;
}

Для того, чтобы в фоновом режиме выполнить отчет времени, по истечению которого в вашем приложении должно произойти какое либо событие, можно использовать встроенный класс Timer и TimerTask. Сам класс Timer служит исключительно для отсчета времени, он может работать в двух режимах: одноразовое выполнение задания, либо же повторяющееся действие (например, напоминание, что у вас не лицензионная версия антивируса :). Метод TimerTask используется для описания действий, которые должны происходить по истечению времени отсчета в таймере Timer. То есть, здесь реализуется визуальное проявление работы таймера, иначе он просто будет считать себе в фоне и все без толку, никто о нем даже не узнает.

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

Создаем новый проект, выбираем Blank Activity. В файле activity_main.xml создаем интерфейс нашего приложения:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 xmlns:tools="http://schemas.android.com/tools"
 android:layout_width="match_parent"
 android:layout_height="match_parent"
 android:orientation="vertical"
 tools:context=".MainActivity" >

 <CheckBox
 android:id="@+id/single_shot"
 android:layout_width="wrap_content"
 android:layout_height="wrap_content"
 android:text="Один раз"/>
 <Button
 android:id="@+id/start"
 android:layout_width="match_parent"
 android:layout_height="wrap_content"
 android:text="Запуск"/>
 <Button
 android:id="@+id/stop"
 android:layout_width="match_parent"
 android:layout_height="wrap_content"
 android:text="Остановить"/>
 <TextView
 android:id="@+id/count"
 android:textSize="25dp"
 android:layout_width="match_parent"
 android:layout_height="wrap_content"/>

</LinearLayout>

Теперь вся остальная работа будет происходить с помощью кода в MainActivity.java. Здесь мы выполняем стандартные шаги: объявляем используемые объекты, связываемся с элементами интерфейса. Ну и задаем Timer и метод для выполнения задачи по истечению таймера TimerTask:

import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Timer;
import java.util.TimerTask;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.TextView;
import android.app.Activity;

public class MainActivity extends Activity {

 // ОБъявляем используемые объекты:
 CheckBox mCheck;
 Button mStart, mStop;
 TextView mCount;

 Timer timer;
 TimerTask mTimerTask;

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

 // Связываемся с элементами пользовательского интерфейса:
 mCheck = (CheckBox)findViewById(R.id.single_shot);
 mStart = (Button)findViewById(R.id.start);
 mStop = (Button)findViewById(R.id.stop);
 mCount = (TextView)findViewById(R.id.count);

 // Настраиваем слушателя нажатий по кнопке "Старт":
 mStart.setOnClickListener(new OnClickListener(){
 @Override
 public void onClick(View arg0) {
 if(timer != null){
 timer.cancel();
 }

 // Задаем поведение таймера при включенном и выключенном переключателе,
 // выполняем задачу MyTimerTask, описание которой будет ниже:
 timer = new Timer();
 mTimerTask = new MyTimerTask();

 if(mCheck.isChecked()){

 // Выполняем действие с задержкой 1 секунда:
 timer.schedule(mTimerTask, 1000);

 }else{
 // После задержки одна секунда, повторяем действие таймера каждую секунду:
 timer.schedule(mTimerTask, 1000, 1000);
 }
 }});

 // Кнопка "Остановить" отменяет действие таймера:
 mStop.setOnClickListener(new OnClickListener(){
 @Override
 public void onClick(View v) {
 if (timer!=null){timer.cancel();timer = null;
 }
 }
 });

 }

 // Метод для описания того, что будет происходить при работе таймера (задача для таймера):
 class MyTimerTask extends TimerTask {
 @Override
 public void run() {

 // Берем дату и время с системного календаря:
 Calendar calendar = Calendar.getInstance();
 SimpleDateFormat simpleDateFormat = new SimpleDateFormat("HH:mm:ss a");

 // Преобразуем информацию в строковые данные:
 final String strDate = simpleDateFormat.format(calendar.getTime());
 runOnUiThread(new Runnable(){

 // Отображаем информацию в текстовом поле count:
 @Override
 public void run() {
 mCount.setText(strDate);
 }});
 }
 }
}

Вот так выполняется реализация системного Android Timer. Пробуем запустить и смотрим на результат:

Таймер

Теперь сделаем немного сложнее — при истечении 5 секунд в таймере будем запускать второе окно приложения. Как создать второе окно и выполнять на него переход мы уже детально разбирали раньше.

Как вы уже поняли, все изменения будут касаться лишь времени таймера:

if(mCheck.isChecked()){
// Выполняем переход с задержкой 5 секунда:
timer.schedule(mTimerTask, 5000);

и описания действий в методе MyTimerTask:

// Выполняем переход на второй экран с помощью Intent:
 class MyTimerTask extends TimerTask {
 @Override
 public void run() {

 Intent intent = new Intent(MainActivity.this,Second.class);
 startActivity(intent);
 }
 }

Запускаем и тестируем:

переход совершен

Переход выполнен успешно! Как видите, Android Timer довольно простая и интересная вещь, теоретически в метод TimerTask можно запихнуть что угодно. Поэкспериментируйте! А еще освойте счетчик обратного отсчета CountDownTimer.

Классы Timer и TimerTask из пакета java.util позволяют планировать запуск задания на определённое время в будущем. Вы можете создать поток, выполняющий в фоновом режиме и ожидающий заданное время. Когда время истечёт, задача, связанная с этим потоком, будет запущена. С помощью параметров можно запланировать задачу на повторяющий запуск либо на запуск по определённой дате. Вам не нужно создавать поток с помощью класса Thread, так как таймер упрощает эту задачу.

Учитывайте обстоятельство, что таймер выполняется в своём потоке и не должен задействовать UI-элементы, которые выполняются в своём потоке. Для решения этой проблемы можете использовать метод runOnUiThread() для обновления данных у компонентов.

Классы Timer и TimerTask работают в связке. Класс Timer используется для планирования выполнения задачи. Запланированная к выполнению задача должна быть экземпляром класса TimerTask. Вы сначала создаёте объект класса TimerTask, а затем планируете его запуск с помощью класса Timer.

Класс TimerTask реализует интерфейс Runnable и может быть использован для создания потока выполнения.

В классе TimerTask имеется абстрактный метод run(), который следует переопределить. Метод должен содержать исполняемый код.

Метод cancel() прерывает задание и возвращает значение true, если выполнение задания прервано.

Метод scheduleExecutionTime() возвращает время, на которое последний раз планировался запуск задания.

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

Методы класса Timer:

  • void cancel() — прерывает поток таймера
  • int purge() — удаляет прерванные задания из очереди таймера
  • void schedule (TimerTask task, long delay) — задание task планируется к выполнению через период в миллисекундах, переданный в параметре delay
  • void schedule (TimerTask task, long delay, long period) — задание task планируется к выполнению через период в миллисекундах, переданный в параметре delay. Затем задание повторяется повторно периодически — каждые period миллисекунд
  • void schedule (TimerTask task, Date when) — задание task планируется на время, указанное в параметре when
  • void schedule(TimerTask task, Date when, long period) — задание task планируется на время, указанное в параметре when. Затем задание выполняется повторно периодически — каждые period миллисекунд
  • void scheduleAtFixedRate (TimerTask task, long delay, long period) — задание task планируется к выполнению через период в миллисекундах, переданный в параметре delay. Затем задание выполняется повторно периодически — каждые period миллисекунд. Время каждого повтора задаётся относительно первого запуска.
  • void scheduleAtFixedRate (TimerTask task, Date when, long period) — задание task планируется к выполнению на время, указанное в параметре when. Задание затем выполняется повторно периодически — каждые period миллисекунд. Время каждого повтора задаётся относительно первого запуска.

Между методами schedule() и scheduleAtFixedRate() есть небольшая разница, которая заключается в разном поведении, которое зависит от стартовой точки запуска. Так второй метод работает как startTime + iterationNumber * delayTime и помнит время запуска. А обычный метод schedule() помнит последнее время выполнения и работает по формуле lastExecutionTime + delayTime. Для быстрых операций это не сильно отличается, а при ресурсоёмких задач разница будет заметна, например, при работе сборщика мусора приложение может притормозить и следующая задача может запуститься чуть позже.

Как только объект класса Timer создан, запуск планируется вызовом его метода schedule() и его родственника (см. выше).

Если вам нужно периодически вызывать какое-нибудь событие, то воспользуйтесь следующим примером:


public void onCreate(Bundle savedInstanceState) {
	super.onCreate(savedInstanceState);
	setContentView(R.layout.activity_test);

	Timer myTimer;
	myTimer = new Timer();

	myTimer.schedule(new TimerTask() {
		public void run() {
			timerTick();
		}
	}, 0, 5000); // каждые 5 секунд
}

private void timerTick() {
	this.runOnUiThread(doTask);
}

private Runnable doTask = new Runnable() {
	public void run() {
		Toast toast = Toast.makeText(getApplicationContext(), "Мяу!",
				Toast.LENGTH_SHORT);
		toast.show();
	}
};

Через каждые пять секунд будет появляться всплывающее сообщение.

Запускаем таймер

Напишем другой пример. Подготовим разметку.


<?xml version="1.0" encoding="UTF-8"?>
<LinearLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <CheckBox
        android:id="@+id/checkBoxSingleShot"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Single Shot" />

    <Button
        android:id="@+id/buttonStart"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Start" />

    <Button
        android:id="@+id/buttonCancel"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Cancel" />

    <TextView
        android:id="@+id/textViewCounter"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textSize="24sp"
        android:textStyle="bold" />

</LinearLayout>

Будем использовать два варианта таймера — одиночное и периодическое срабатывание.


package ru.alexanderklimov.timer;

// Если этот код работает, его написал Александр Климов,
// а если нет, то не знаю, кто его писал.

import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Locale;
import java.util.Timer;
import java.util.TimerTask;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.TextView;

public class MainActivity extends Activity {

	private CheckBox mSingleShotCheckBox;
	private Button mStartButton, mCancelButton;
	private TextView mCounterTextView;

	private Timer mTimer;
	private MyTimerTask mMyTimerTask;

	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);

		mSingleShotCheckBox = findViewById(R.id.checkBoxSingleShot);
		mStartButton = findViewById(R.id.buttonStart);
		mCancelButton = findViewById(R.id.buttonCancel);
		mCounterTextView = findViewById(R.id.textViewCounter);

		mStartButton.setOnClickListener(new OnClickListener() {

			@Override
			public void onClick(View arg0) {

				if (mTimer != null) {
					mTimer.cancel();
				}

				// re-schedule timer here
				// otherwise, IllegalStateException of
				// "TimerTask is scheduled already"
				// will be thrown
				mTimer = new Timer();
				mMyTimerTask = new MyTimerTask();

				if (mSingleShotCheckBox.isChecked()) {
					// singleshot delay 1000 ms
					mTimer.schedule(mMyTimerTask, 1000);
				} else {
					// delay 1000ms, repeat in 5000ms
					mTimer.schedule(mMyTimerTask, 1000, 5000);
				}
			}
		});

		mCancelButton.setOnClickListener(new OnClickListener() {

			@Override
			public void onClick(View v) {
				if (mTimer != null) {
					mTimer.cancel();
					mTimer = null;
				}
			}
		});
	}

	class MyTimerTask extends TimerTask {

		@Override
		public void run() {
			Calendar calendar = Calendar.getInstance();
			SimpleDateFormat simpleDateFormat = new SimpleDateFormat(
					"dd:MMMM:yyyy HH:mm:ss a", Locale.getDefault());
			final String strDate = simpleDateFormat.format(calendar.getTime());

			runOnUiThread(new Runnable() {

				@Override
				public void run() {
					mCounterTextView.setText(strDate);
				}
			});
		}
	}
}

Генерируем случайные показания

Допустим нам нужны ежедневные показания термометра. Но мы не можем ждать весь день при написании программ. Поэтому мы можем случайным образом создавать показания с небольшим интервалом и проверить поведение приложения.

Для удобства создадим отдельный класс-утилиту.


package ru.alexanderklimov.supercat;

import java.util.Random;

class Utils {

    private static final Random RANDOM = new Random();

    static int randInt(int min, int max) {
        return RANDOM.nextInt((max - min) + 1) + min;
    }
}

Создадим в классе активности метод для генерации значений и вызовем в onCreate().


package ru.alexanderklimov.supercat;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;

import java.util.Timer;
import java.util.TimerTask;

public class MainActivity extends AppCompatActivity  {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        setValues();
    }

    private void setValues() {
        Timer timer = new Timer();

        timer.scheduleAtFixedRate(new TimerTask() {

            @Override
            public void run() {
                final float value = Utils.randInt(-10, 35);

                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        Log.i("Info", "Value: " + value);
                    }
                });
            }
        }, 0, 3500);
    }
}
Реклама

Разбираем создание таймера на языке программирования C# в приложении Windows Forms. Полный исходный код с подробными комментариями можно будет скачать внизу страницы.

Для начала в Windows Forms создаём внешнюю оболочку программы. У нас она выглядит вот так:

таймер на C# в Windows Forms - vscode.ru

Здесь у нас 8 Label’ов, 3 TextBox’a, 3 Buttom’a и сам Timer.

Примечание: при переносе элемента Timer в форму, на неё ничего не появляется. Лишь в нижней части окна программы под формой появляется значок Значок таймера, не пугайтесь.

Щёлкнем на значок таймера Значок таймера на C# и в окне «Свойства» в группе «Поведение» устанавливаем значение параметра Interval равным 1000. Данный параметр определяет длину тика таймера в миллисекундах, указав 1000, мы сделали один тик равным одной секунде.

После оформления и настройки приступаем к коду. Вводим целочисленные переменные h — часы, m- минуты, s — секунды.

Затем дважды щёлкаем мышью на кнопке «Старт» и переходим на участок кода, отвечающий за клик на эту кнопку.

Туда мы пишем следующий код:

h = Convert.ToInt32(textBox1.Text);

m = Convert.ToInt32(textBox2.Text);

s = Convert.ToInt32(textBox3.Text);

timer1.Start();

То есть мы считываем с TextBox’ов данные, которые ввёл туда пользователь, и после этого включаем таймер. Время пошло.

Также нам надо настроить счёт времени самого таймера. Для этого дважды кликаем на элементе Безымянный1 и внутри тела кода, в который нас отправило, пишем:

private void timer1_Tick(object sender, EventArgs e)

        {

            s = s 1;

            if (s == 1)

            {

                m = m 1;

                s = 59;

            }

Здесь мы настраиваем таймер таким образом, чтобы каждую секунду переменная s уменьшалась на единицу. Если s становится меньше нуля, значит прошла минута, следовательно, m должна уменьшаться на единицу, а отсчёт с секундами s снова начнётся с 59.

То же самое мы делаем с часами и минутами:

if (m==-1)

      {

          h = h 1;

          m = 59;

      }

Теперь позаботимся о том, что случится, когда время, указанное пользователем, выйдет:

if (h==0 && m==0 && s==0)

     {

          timer1.Stop();

          MessageBox.Show(«Время вышло!»);

      }

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

таймер на C# в Windows Forms - vscode.ru

А чтобы пользователь мог видеть, как идёт время, и как отсчитываются часы, минуты и секунды, мы вынесем всё вышепроисходящее на экран при помощи label’ов:

label1.Text = Convert.ToString(h);

label3.Text = Convert.ToString(m);

label5.Text = Convert.ToString(s);

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

Код кнопки «Стоп»:

private void button2_Click(object sender, EventArgs e)

        {

            timer1.Stop();

        }

Тут всё просто и понятно.

В кнопке «Сброс» нам надо помимо остановки сбросить значения переменных до нулей:

private void button3_Click(object sender, EventArgs e)

        {

            timer1.Stop();

            label1.Text = «0»;

            label3.Text = «0»;

            label5.Text = «0»;

        }

Программа готова. Таймер на C#:

Скачать исходник

Понравилась статья? Поделить с друзьями:
  • Как написать приложение нарочно
  • Как написать приложение на пайтоне
  • Как написать приложение на пайтон
  • Как написать приложение на python для windows
  • Как написать приложение на ios на windows