Красивый таймер для Android (часть2)

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

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

Сказать, что переход с 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»]

<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» />

[/cce]

У этой кнопки задний фон описывает файл small_buttonshape.xml , который находится в папке drawable

android:background=«@drawable/small_buttonshape»

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

<?xml version=»1.0″ encoding=»utf-8″?>
<shape xmlns:android=»http://schemas.android.com/apk/res/android»
android:shape=»rectangle» >
<corners android:radius=»7dp» />
<solid android:color=»#4F4F4F» />
<size
android:height=»45dp»
android:width=»90dp» />
<stroke
android:width=»4dp»
android:color=»#878787″ />
</shape>

[/cce]

Есть сайт Android Button Maker,  где можно легко собрать нужный стиль для кнопки.

Код всей разметки 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=»@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]

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

device-2014-09-24-150227_opt

 

Нашли ошибку в тексте?

Красивый таймер для Android (часть2): 3 комментария

  1. Григорий

    Здравствуйте! Я написал по вашим урокам и таймер и секундомер. А теперь хотелось бы соединить их в одну программу, чтобы переключаться между ними свайпами вправо/влево. Я так понимаю нужно изучить как делаются эти самые вкладки и в главном активити будет ветвление типа если открыта первая вкладка, то используется такой код, если вторая то другой. И код таймера и секундомера мне кажется нужно будет вынести в отдельный класс? например TimerActivity и StopwatchActitvity. Или их можно объединить в одном классе? У меня возникли эти вопросы, потому что я пока что все делал в одном классе, изучал по кусочкам программирование. Вот теперь надо понять как соединять эти кусочки))

    1. blog

      Очень за Вас рад!
      Для переключения свайпами можно реализовать вкладки во фрагментах.
      можно простые вкладки, тогда нужно отлавливать касания и движения по экрану.
      Вот пример http://developer.alexanderklimov.ru/android/views/tabhost-tabwidget.php
      В общем тут хозяин-барин. Как можете так и реализуйте.

      1. Григорий

        Пробую сейчас с фрагментами и toolbar. Еще нашел на Github несколько библиотек. Пользоваться ими для меня в новинку, но и этот способ попробую)) В общем голова кипит от моря инфы, как когда первый раз глянул на код таймера. Но таймер теперь кажется простым)) надеюсь на этой неделе я и вкладки освою в материал дизайне.

Добавить комментарий

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