Наверное свой таймер не писал только ленивый. А в контексте поддержки разработки для мобильных платформ, задачу написания таймера на 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. Опять же, ничего сложного, но данный момент стоит учесть.
Подведем промежуточные итоги. На первом этапе разработки мы получили функционально рабочее приложение для Windows. Разработка и отладка приложения до сих пор ничем не отличалась от разработки и отладки обычных настольных приложений. Это намного проще и быстрее чем отлаживать код под Android. Интерфейс приложения, правда остался довольно примитивным. В следующей части мы попробуем это исправить.
Добавьте пожалуйста побольше скриншотов уже готового приложения.
Совсем недавно я писал такое же приложение, но на родной для андроида яве
http://rusdelphi.com/help/krasivyj-tajmer-dlya-android
Будет интересно сравнить количество кода и интерфейсы приложений.