Блог Александра Божко
Архивы
Рубрики
Поделись с другими!
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  

Наверное свой таймер не писал только ленивый. А в контексте поддержки разработки для мобильных платформ, задачу  написания таймера на Delphi вообще можно считать культовой. Вот я и подумал, почему бы в качестве примера разработки FireMonkey приложения не разобрать именно таймер. Под Android, естественно. Конечно же, это будет именно мой взгляд на задачу, которая, хотя и не особо сложная, все же имеет свои нюансы. Возможно, у вас возникнут какие-то замечания, или предложения, было бы замечательно обсудить их в комментариях. Я отнюдь не эксперт в области написания мобильных приложений, поэтому любой ваше замечание будет для меня ценно.

Разрабатывать мы будем именно таймер, в англоязычном понимании этого термина. Т. е. на экране будет отображаться циферблат и четыре кнопки – “Пуск”, “Пауза”, “Стоп” и “Отмена”. Отсчет будет производиться в прямом направлении (т. е. время будет увеличиваться). Вариант, при котором задается время и идет обратный отсчет в англоязычной терминологии называется Stop Watch, возможно я попробую реализовать его позже.  То, приложение, которым займемся мы, по функциональности ближе к секундомеру.

Delphi XE7, позволяет значительно упростить процесс разработки, за счет того, что теперь мы можем создать и отладить реальное приложение для Win32, а затем просто добавить представления форм для необходимых мобильных устройств и, немного подкорректировав их, получить рабочее мобильное приложение. Звучит слишком красиво, что бы быть правдой? Возможно. Но проверить данное утверждение я и хочу, реализовав поставленную задачу.

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

TDroidTimer = class(TTimer)
private
  FStartTime: TDateTime;
  FSecInterval: integer;
  FState: TDroidTimerState;
  FSeparator: string;
  FStrTime: string;

  FBeforeStop,
  FAfterStop,
  FBeforePause,
  FAfterPause,
  FBeforeStart,
  FAfterStart,
  FAfterTimerStateChange: TNotifyEvent;

    function GetSecInterval: integer;
    function GetStartTime: TDateTime;
    procedure SetSecInterval(const Value: integer);
    procedure SetStartTime(const Value: TDateTime);
    function GetState: TDroidTimerState;
    procedure SetState(const Value: TDroidTimerState);
    function GetStrTime: string;
    function GetSeparator: string;
    procedure SetSeparator(const Value: string);

    procedure SetAfterStop(const Value: TNotifyEvent);
    procedure SetBeforeStop(const Value: TNotifyEvent);
    procedure SetAfterPause(const Value: TNotifyEvent);
    procedure SetBeforePause(const Value: TNotifyEvent);
    procedure SetAfterStart(const Value: TNotifyEvent);
    procedure SetBeforeStart(const Value: TNotifyEvent);
    procedure SetAfterTimerStateChange(const Value: TNotifyEvent);

protected
  procedure DoOnTimer; override;
  procedure InternalStop;

public
  procedure Start;
  procedure Stop; virtual;
  procedure Pause;

  constructor Create(AOwner: TComponent); override;

published
  property StartTime: TDateTime Read GetStartTime Write SetStartTime;
  property SecInterval: integer Read GetSecInterval Write SetSecInterval;
  property State: TDroidTimerState Read GetState Write SetState;

  property StrTime: string Read GetStrTime;
  property separator: string Read GetSeparator Write SetSeparator;

  property BeforeStop: TNotifyEvent read FBeforeStop write SetBeforeStop;
  property AfterStop: TNotifyEvent read FAfterStop write SetAfterStop;

  property BeforePause: TNotifyEvent read FBeforePAuse write SetBeforePause;
  property AfterPause: TNotifyEvent read FAfterPause write SetAfterPause;

  property BeforeStart: TNotifyEvent read FBeforeStart write SetBeforeStart;
  property AfterStart: TNotifyEvent read FAfterStart write SetAfterStart;

  property AfterTimerStateChange: TNotifyEvent read FAfterTimerStateChange write SetAfterTimerStateChange;

end;

Немного поясню смысл. В полях FStartTime и FSecInterval сохраняются значения времени запуска таймера и количества секунд, прошедших с момента запуска. Таймер имеет свойство State – состояние таймера. Ниже я привожу код реализации.

Type
TDroidTimerState = 0..4;

const

    dtsStop = TDroidTimerState(0);
    dtsWork = TDroidTimerState(1);
    dtsPause= TDroidTimerState(2);

Несложно догадаться, что свойство принимает значение 0, если таймер остановлен, 1 – если он работает, 2 – если он находится на паузе. Значения 3 и 4 я зарезервировал.

Методы Start, Stop и Pause запускают, останавливают  или ставят на паузу таймер, соответственно. при этом меняется состояние таймера.

 

constructor TDroidTimer.Create(AOwner: TComponent);
begin
  inherited;
  FSecInterval:= 0;
//  OnTimer:=
  FSeparator:= ':';
end;

function TDroidTimer.GetSecInterval: integer;
begin
  Result:= FSecInterval;
end;

function TDroidTimer.GetSeparator: string;
begin
Result:= FSeparator;
end;

function TDroidTimer.GetStartTime: TDateTime;
begin
  Result:= FStartTime;
end;

function TDroidTimer.GetState: TDroidTimerState;
begin
  Result:= FState;
end;

function TDroidTimer.GetStrTime: string;
var
 sMin, sSec, SHours: string;
 iMin, iH: integer;

begin

  sSec:= IntToStr(FSecInterval mod 60);
  if Length(sSec)<2 then
  sSec:=  '0'+sSec;

  iMin:=  FSecInterval div 60;

  sMin:= IntToStr(iMin mod 60);

  if Length(sMin)<2 then
  sMin:=  '0'+sMin;

  iH:= iMin div 60;

  sHours:= IntToStr(iH);
  FStrTime:= SHours+FSeparator+sMin+FSeparator+sSec;
  Result:= FStrTime;
end;

procedure TDroidTimer.InternalStop;
begin
 State:= dtsStop;
 Enabled:= False;

 if Assigned(FAfterTimerStateChange) then
    FAfterTimerStateChange(Self);
end;

procedure TDroidTimer.Pause;
begin
  if Assigned(FBeforePause) then
    FBeforePause(Self);

 if FState = dtsPause then
 begin
   Enabled:= True;
   FState:= dtsWork;
 end
 else
 begin
   Enabled:= False;
   FState:= dtsPause;

 end;

   if Assigned(FAfterPause) then
    FAfterPause(Self);

 if Assigned(FAfterTimerStateChange) then
    FAfterTimerStateChange(Self);
end;

procedure TDroidTimer.SetAfterPause(const Value: TNotifyEvent);
begin
 FAfterPause:= Value;
end;

procedure TDroidTimer.SetAfterStart(const Value: TNotifyEvent);
begin
 FAfterStart:= Value;
end;

procedure TDroidTimer.SetAfterStop(const Value: TNotifyEvent);
begin
  FAfterStop:= Value;
end;

procedure TDroidTimer.SetAfterTimerStateChange(const Value: TNotifyEvent);
begin
  FAfterTimerStateChange := Value;
end;

procedure TDroidTimer.SetBeforePause(const Value: TNotifyEvent);
begin
 FBeforePause:= Value;
end;

procedure TDroidTimer.SetBeforeStart(const Value: TNotifyEvent);
begin
 FBeforeStart:= Value;
end;

procedure TDroidTimer.SetBeforeStop(const Value: TNotifyEvent);
begin
 FBeforeStop:= Value;
end;

procedure TDroidTimer.SetSecInterval(const Value: integer);
begin
  FSecInterval:= Value;
end;

procedure TDroidTimer.SetSeparator(const Value: string);
begin
 FSeparator:= Value;
end;

procedure TDroidTimer.SetStartTime(const Value: TDateTime);
begin
  FStartTime:= Value;
end;

procedure TDroidTimer.SetState(const Value: TDroidTimerState);
begin
   FState:= Value;
end;

procedure TDroidTimer.Start;
begin
   if Assigned(FBeforeStart) then
    FBeforeStart(Self);

 if FState= dtsStop then
 begin
    FStartTime:= Now;
    FSecInterval:= 0;
 end;

 if FState= dtsPause then
 begin
    FStartTime:= Now;
    //FSecInterval:= 0;
 end;

 Enabled:= True;
 State:= dtsWork;

if Assigned(FAfterStart) then
    FAfterStart(Self);

if Assigned(FAfterTimerStateChange) then
    FAfterTimerStateChange(Self);
end;

procedure TDroidTimer.Stop;
begin
if Assigned(FBeforeStop) then
    FBeforeStop(Self);

  InternalStop;

if Assigned(FAfterStop) then
    FAfterStop(Self);
end;

procedure TDroidTimer.DoOnTimer;
begin
  inherited;
  Inc(FSecInterval);
end;

Также стоит обратить внимание на свойство StrTime. Его значение – строка, содержащая отформатированной значение времени. Иными словами, мы можем просто присваивать значению свойства Caption какой-либо метки значение свойства StrTime.

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

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

На следующем этапе мы создадим приложение и попытаемся использовать вновь созданный компонент. Для того, что бы в последствии запустить приложение под Android, необходимо создать Multi-Device Application. Выберем шаблон Blank Application. Поместим на форму компонент TDroidTimer, четыре кнопки и метку TLabel. Кнопки расположите в нижней части формы в горизонтальный ряд и измените для каждой из них свойство StyleLookup: playtoolbutton, pausetoolbutton, stoptoolbutton и refreshtoolbutton. Для наглядности я переименовал кнопки. Код приложения получился таким:

procedure TForm2.btnCancelClick(Sender: TObject);
begin
DroidTimer1.Stop;
lblTime.Text:= '00:00:00';
end;

procedure TForm2.btnPauseClick(Sender: TObject);
begin
DroidTimer1.Pause;
end;

procedure TForm2.btnStartClick(Sender: TObject);
begin
 DroidTimer1.Start;
end;

procedure TForm2.btnStopClick(Sender: TObject);
begin
  DroidTimer1.Stop;
end;

procedure TForm2.DroidTimer1Timer(Sender: TObject);
begin
lblTime.Text:=  DroidTimer1.StrTime;
end;

Думаю, комментировать здесь особо нечего.  Настройками внешнего вида займемся в следующей части. А пока хочу обратить внимание на один момент. В современных версиях Delphi при установке компонентов все не так просто, как, скажем в Delphi 7. Дело в том, что пути к папкам, содержащим откомпилированные модули нужно указывать указывать для каждой из поддерживаемых платформ. Естественно, если вы хотите, что бы компоненты работали не только под Win32, но, к примеру и под Android. Опять же, ничего сложного, но данный момент стоит учесть.

PrjOptions

Подведем промежуточные итоги. На первом этапе разработки мы получили функционально рабочее приложение для Windows. Разработка и отладка приложения до сих пор ничем не отличалась от разработки и отладки обычных настольных приложений. Это намного проще и быстрее чем отлаживать код под Android. Интерфейс приложения, правда остался довольно примитивным. В следующей части мы попробуем это исправить.


Поделись с другими!
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  

2 комментария: FireMonkey на практие. Еще один таймер для Android. #0

  • Добавьте пожалуйста побольше скриншотов уже готового приложения.
    Совсем недавно я писал такое же приложение, но на родной для андроида яве
    http://rusdelphi.com/help/krasivyj-tajmer-dlya-android
    Будет интересно сравнить количество кода и интерфейсы приложений.

    • :) По странному стьечению обстоятельств DelphiFeeds.Ru я почитываю периодически, и кончено, обратил внимание на Ваш таймер. Собственно, поэтому и решил сделать свою серию постов. Конечно, будут скриншоты. Но в первой части особо ничего было скриншотить. :)

Оставить комментарий

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

Вы можете использовать это HTMLтеги и атрибуты: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Продукты DevArt
Купить онлайн:



Читай русскоязычные Delphi блоги
Каталог блогов Blogdir.ru
Яндекс.Метрика