Веб программирование

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

 
 

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

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

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

Родные VCL формы рисуются с помощью мыши очень быстро и тем самым экономят много труда.

В  Android видимый слой Layout или "Разметка слоя" полностью описывается в xml файле.

Графический редактор неудобный (и с причудами), и практически всегда приходится писать разметку вручную или дописывать "красоту", созданную в редакторе.

У стандартного компонента Button много полезных свойств.

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

Пример таких кнопок можно посмотреть в моём приложении фонарик.

Цвет кнопки можно задать при помощи свойства android:background: его можно указать напрямую через представление в виде трёх пар шестнадцатеричных цифр (#FFFFFF) .

Можно создать файл colors.xml  в папке  values и в нем описать нужные нам цвета, а потом просто указывать ссылку @color/red .

Вот пример такого файла :

[cce lang="xml" tab_size="2" no_links="false"]

<?xml version="1.0" encoding="utf-8"?> <resources>

<color name="black">#000</color> <color name="text_green">#407020</color> <color name="text_pink">#702040</color> <color name="text_silver">#888</color> <color name="text_blue">#224488</color> <color name="yellow">#fff9bb</color> <color name="red">#ff0000</color> <color name="header_bg">#000</color> <color name="navigation_bg">#e0e0e0</color> <color name="list_bg">#fff</color> <color name="splash_bg">#015daa</color> <color name="frame_color">#888</color> <color name="Blue">#0000FF</color>

</resources>

[/cce]

Можно задать фоном целый файл, описывающий графические  примитивы.

Вот код разметки кнопки из предыдущей статьи:

[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="@color/red" 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" />

<NumberPicker android:id="@+id/numberPicker1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:layout_centerHorizontal="true" android:layout_marginBottom="47dp" />

</RelativeLayout>

[/cce]

Для установки времени отсчета используем компонент NumberPicker.

Он доступен для устройств с апи 11+, т.е. Android 2.3

Для NumberPicker можно установить прокручиваемый список чисел или слов.

Список слов задается через массив String[] с помощью метода setDisplayedValues (String[] displayedValues).

А простой диапазон чисел задается с помощью методов setMinValue(int minValue) и setMaxValue(int maxValue).

Для получения изменений этого виджета, нужно повесить на него слушатель onValueChangedListener.

Для установки нужного значения подходит метод setValue(int value).

Если наш таймер не запущен, то мы будем сразу обновлять компонент TextView объявленный как digital_clock.

[cce lang="java" tab_size="2" no_links="false"]

NumberPicker.OnValueChangeListener onValueChanged = new NumberPicker.OnValueChangeListener() { @Override public void onValueChange(NumberPicker picker, int oldVal, int newVal) { if (!mIsRunning) { digital_clock.setText(intToTime(newVal)); mCurrentPeriod = newVal; } } };

[/cce]


Чтобы не клонировать код перевода числа в стоковое временное значение, вынес его в отдельную функцию:

[cce lang="java" tab_size="2" no_links="false"]

private String intToTime(int i) { return (new SimpleDateFormat("mm:ss")).format(new Date(i * 1000)); }

[/cce]

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

[cce lang="java" tab_size="2" no_links="false"]

private Runnable Timer_Tick = new Runnable() { public void run() { mCurrentPeriod--; digital_clock.setText(intToTime(mCurrentPeriod)); if (mCurrentPeriod == 0) { myTimer.cancel(); mIsRunning = false; SetNotification(); } // This method runs in the same thread as the UI. // Do something to the UI thread here

} };

[/cce]

И если время закончилось (mCurrentPeriod == 0), то вышлем извещение SetNotification().

Извещения в Android  - это сообщения (текстовые и графические), которые отображаются в специальной верхней области экрана – области уведомлений.

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

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

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

Чтобы процесс нельзя было остановить, применяют сервис Service, а для такого приложения достаточно дочернего потока.

Вот код создания извещения (Notifications):

[cce lang="java" tab_size="2" no_links="false"]

private void SetNotification() { Notification.Builder builder = new Notification.Builder(this) .setContentTitle("Время пришло!") .setContentText("Таймер истек ") .setSmallIcon(R.drawable.indicator_input_error) .setSound( RingtoneManager .getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)) .setAutoCancel(true); Intent intent = new Intent(this, MainActivity.class); PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);

builder.setContentIntent(pendingIntent); NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); notificationManager.notify(0, builder.build()); }

[/cce]

Для создания извещения используется класс Notification Builder, а для запуска – системный сервис NotificationManager.

В нашем извещении мы задаем заголовок setContentTitle, текст setContentText, картинку setSmallIcon и звук setSound.

Звук берем из системы при помощи RingtoneManager. Лучше всего брать стандартный getDefaultUri(RingtoneManager.TYPE_NOTIFICATION), чтобы пользователь не путался.

Для того, чтобы при нажатии по извещению, оно сразу удалялось, ставим  свойство setAutoCancel(true).

В NotificationManager  есть несколько способов удаления извещения:

  • cancel() удаляек конкретное извещение
  • cancelAll() удаляет все

Метод setContentIntent вешает отложенное намеренье PendingIntent, которое запустится при нажатии пользователя на уведомление.

В нашем случае мы просто запустим главную активность приложения MainActivity.class.

Для поддержки старых версий Android есть класс NotificationCompat , стандартный Notification Builder работет с апи 11+.

В ранних проектах я поддерживал апи 8+, но в этом пришлось бы подключать сторонний NumberPicker.

Поэтому в манифесте приложения укажем android:minSdkVersion="11".

Вот полный код получившегося класса 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.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.graphics.Typeface; import android.media.RingtoneManager; import android.os.Bundle; import android.view.View; import android.widget.NumberPicker; import android.widget.TextView;

public class MainActivity extends Activity { TextView digital_clock; NumberPicker np; Typeface tf; boolean mIsRunning = false; 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); np = (NumberPicker) findViewById(R.id.numberPicker1); np.setMaxValue(120); np.setMinValue(0); np.setOnValueChangedListener(onValueChanged); }

NumberPicker.OnValueChangeListener onValueChanged = new NumberPicker.OnValueChangeListener() { @Override public void onValueChange(NumberPicker picker, int oldVal, int newVal) { if (!mIsRunning) { digital_clock.setText(intToTime(newVal)); mCurrentPeriod = newVal; } } };

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) { if (mCurrentPeriod == 0) return; mIsRunning = true; 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; mIsRunning = false; if (myTimer != null) myTimer.cancel(); digital_clock.setText("00:00"); np.setValue(0); };

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 String intToTime(int i) { return (new SimpleDateFormat("mm:ss")).format(new Date(i * 1000)); }

private void SetNotification() { Notification.Builder builder = new Notification.Builder(this) .setContentTitle("Время пришло!") .setContentText("Таймер истек ") .setSmallIcon(R.drawable.indicator_input_error) .setSound( RingtoneManager .getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)) .setAutoCancel(true); Intent intent = new Intent(this, MainActivity.class); PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);

builder.setContentIntent(pendingIntent); NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); notificationManager.notify(0, builder.build()); }

private Runnable Timer_Tick = new Runnable() { public void run() { mCurrentPeriod--; digital_clock.setText(intToTime(mCurrentPeriod)); if (mCurrentPeriod == 0) { myTimer.cancel(); mIsRunning = false; SetNotification(); } // This method runs in the same thread as the UI. // Do something to the UI thread here

} };

}

[/cce]

Вид получившегося приложения:

 

 


Есть вопросы? Спроси на нашем форуме!!
Alexander25 [22.08.2016 10:43]

Это только я не вижу "Вид получившегося приложения:"?

DIGIUS470 [13.10.2016 05:42]

Оформите в соответствии с требованиями сайта и статья будет опубликована.



Оставлять комментарии можно только зарегистрированным




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