вторник, 22 октября 2013 г.

Delphi XE5 разработка мобильных приложений. Некоторые заметки для осмысления. (Upd 2)

Некоторые наблюдения по итогам тестовых приложений для Android. Мой взгляд на разработку.

Тут решил собирать некоторые заметки о разработке для Android.

1.Формы в мобильных устройствах -
Формы в мобильных устройствах лучше создавать динамически, т.к. расход памяти все таки критичен. Еще одной особенностью форм в Android-платформе является отсутствие в платформе поддержки диалоговых форм. 
Т.е. классически ShowModal вы использовать не сможете, сработает только метод Show. Соответственно, это накладывает некоторые ограничения например для окон настройки. Теперь изменяемые данные в псевдо-диалоге не могут быть интерпретированы как константные, т.к. форма может быть переключена, перекрыта другой, закрыта и т.п. 
Поэтому нужно заботится о том, что любое изменение на псевдо-диалоге должно вызывать оповещение зависящих от ее данных форм и контролов. Можно методами сообщений. Вообщем в отличии от десктоп-платформы это накладывает дополнительные расходы на код.

Upd:1
Как мне подсказали в комментариях, в Android системах для FM изменен механизм работы ShowModal. Теперь классический способ вызова, как мы все привыкли ранее:

if fDlfForm.ShowModal = mrOk
вызовет ошибку "ShowModal not implemented on this platform"
Это связано с тем, что в Windows системах, как и в Apple-системах в отличии от Android-а  есть цикл обработки сообщений для каждого отдельного окна.  
Т.е. метод ShowModalостанавливает прием сообщений в первичной форме и обрабатывает их в вызываемой форме (вторичной).
Однако в Androide не такой системы обработки сообщений, что делает решение модальных форм с одной стороны очень сложным. Однако есть способ обойти это ограничение - это новые перегруженные методы

function ShowModal: TModalResult; overload;
procedure ShowModal(const ResultProc: TProc<TModalResult>); overload;

с помощью них можно эмулировать поведение модальных окон, т.е вынести код после then в анонимный метод.

procedure TForm1.Button3Click(Sender: TObject);
var
  fFormDlg: TFormAdditional;
begin
  Try        // Классический метод на Android устройствах не сработает
    fFormDlg := TFormAdditional.Create(nil);
    fFormDlg.checkBox.IsChecked := True;
    IF fFormDlg.ShowModal = mrOk then
    begin
       if fFormDlg.checkBox.IsChecked then
          LabelInfo.Text := 'Ckecked'
       else
          LabelInfo.Text := 'UnCkecked';
    end
    else
       LabelInfo.Text := '-';
  Finally
    fFormDlg.DisposeOf;
  End;
end;

procedure TForm1.Button2Click(Sender: TObject);
var
  fFormDlg: TFormAdditional;
begin
  fFormDlg := TFormAdditional.Create(nil);
  fFormDlg.checkBox.IsChecked := True;

           // Анонимный метод на Android устройствах сработает
  fFormDlg.ShowModal(
    procedure(ModalResult: TModalResult)
    begin
      if ModalResult = mrOK then
      begin
        if fFormDlg.checkBox.IsChecked then
           LabelInfo.Text := 'Ckecked'
        else
           LabelInfo.Text := 'UnCkecked';
      end
      else
        LabelInfo.Text := '-';
      fFormDlg.DisposeOf;
    end
              );
end;

Обратите внимание, что код почти идентичен. Единственная различается в том, что код выполняется в второй форме (псевдо диалоговой), а не в первой как в классическом случае. Фактически мы как бы код передаем второй форме. Однако фактически хоть мы и эмулируем логику модальной форму - на самом деле это обычная форма с хитрым механизмом закрытия.

2.Прокрутка
Тут все очень просто, есть ScrollBox и похожие по функционалу VerticalScrollBox и т.д. Соответственно, если в форму у вас не влазят в дизайнере компоненты то их нужно формировать в рунтайме, что не совсем удобно или расширить место при помощи трюка (см.далее).
Тривиальный пример формирования 50 кнопок (в дизайнере не влезут на экран) в окне вертикальной прокрутки:
procedure TForm1.FormCreate(Sender: TObject);
var
 I : Integer;
 _Btn : TButton;
begin
 for I := 0 to 50 do
 begin
     _Btn := TButton.Create(Self);
     _Btn.Parent := ScrollBox;
     _Btn.Text   := 'Кнопка: ' + IntToStr(I);
     _Btn.Height := 50;
     _Btn.Width  := 200;
     _Btn.Position.X    := 0;
     _Btn.Position.Y    := (I * 50) + 1;
 end;
end;

Upd:1
Маленькая хитрость или трюк трюк по расширению рабочего места в дизайнере: Если положить ScrollBox и на него кинуть например кнопку, предварительно растянув ее по высоте максимально, после чего перетащить ее за верхнюю часть вниз, то в дизайнере в ScrollBox-е  действительно появляется дополнительное место, куда можно положить другие компоненты. Начинает работать прокрутка колесом мыши. После чего кнопку которая помогла нам расширить место в дизайнере можно убрать. Вот такая хитрость проектирования в дизайнере.

3.Стили
Тут все просто StyleBook на форму и связь с ним в форме. Однако есть некоторые ньюансы, Delphi XE5 иногда глючит если в свойстве Fill.Kind стоит отличное от bkNone значение. То есть возможно появление артефактов когда часть объектов (контролов) на фоме стала под стиль, а сама форма нет. Причем если в дизайнере вид более менее под стиль, то в устройстве вообще может быть по другому


4.Жесты
Поддержка жестов реализовано через TGestureManager.
Нам доступны как стандартные жесты (Standard) так и интерактивные жесты (InteractiveGestures) такие как маштабирование, нажатие + касание и т.п.
Стоит отметить, что если вы совмещаете стандартные и интерактивные жесты могут возникнуть коллизии, например если вы выставляете интерактивный жест igPan и используете стандартные действия влево вправо вверх и вниз, то нормально обрабатывать перемещение некого контрола вы не сможете, т.к. например делая жест влево с целью переместить например некий объект у вас сработает свойство обработки OnGesture c иденификатором sgiLeft а не ожидаемый igPan. Поэтому продумывайте какой тип жестов вы хотите интерактивный или стандартный в поведении контрола.
Данную коллизию жестов хорошо иллюстрирует простейший пример:
type
  TForm1 = class(TForm)
    Rectangle: TRectangle;
    GestureManager: TGestureManager;
    procedure RectangleGesture(Sender: TObject;
      const EventInfo: TGestureEventInfo; var Handled: Boolean);
    procedure FormCreate(Sender: TObject);
  private
    { Private declarations }
    SizeX,SizeY : Integer;
    procedure MyZoom(const eventInfo: TGestureEventInfo);
    procedure MyPan(const eventInfo: TGestureEventInfo);
    procedure MyUp;
    procedure MyDown;
    procedure MyLeft;
    procedure MyRight;
    procedure MyPressAndTap;
  public
    { Public declarations }
    LastPosition: TPointF;
    LastDistance: Integer;
  end;

var
  Form1: TForm1;

implementation

{$R *.fmx}

procedure TForm1.FormCreate(Sender: TObject);
begin
 SizeX := Screen.Size.Width;
 SizeY := Screen.Size.Height - 22;
 LastPosition := Rectangle.Position.Point;
end;


procedure TForm1.MyPressAndTap;
begin
 ShowMessage('Press and Tap');
end;

procedure TForm1.MyUp;
begin
  // Последовательная анимация для нелинейной скорости с замедлением в конце в течении
  // 1 секунды
 Rectangle.AnimateFloat('Position.Y', 0 , 1.0, TAnimationType.atOut, TInterpolationType.itQuadratic);
 LastPosition := Rectangle.Position.Point;
end;

procedure TForm1.MyRight;
begin
 Rectangle.AnimateFloat('Position.X',
            SizeX - Rectangle.Width,
                    1.0, TAnimationType.atOut, TInterpolationType.itQuadratic);
 LastPosition := Rectangle.Position.Point;
end;

procedure TForm1.MyDown;
begin
 Rectangle.AnimateFloat('Position.Y',
            SizeY - Rectangle.Height,
                    1.0, TAnimationType.atOut, TInterpolationType.itQuadratic);
 LastPosition := Rectangle.Position.Point;
end;

procedure TForm1.MyLeft;
begin
 Rectangle.AnimateFloat('Position.X', 0,
                    1.0, TAnimationType.atOut, TInterpolationType.itQuadratic);
 LastPosition := Rectangle.Position.Point;
end;

// Маштабирование объекта
procedure TForm1.MyZoom(const eventInfo: TGestureEventInfo);
var
  NewDistance : Single;
begin
  NewDistance := EventInfo.Distance - LastDistance;
  Rectangle.Width      := Rectangle.Width      + (NewDistance );
  Rectangle.Height     := Rectangle.Height     + (NewDistance );
  Rectangle.Position.X := Rectangle.Position.X - (NewDistance / 2);
  Rectangle.Position.Y := Rectangle.Position.Y - (NewDistance / 2);
  // Запоминаем дистанцию нашего Rectangle
  LastDistance := EventInfo.Distance;
end;


// Перемещение объекта
procedure TForm1.MyPan(const eventInfo: TGestureEventInfo);
begin
  Rectangle.Position.X := Rectangle.Position.X + (EventInfo.Location.X - LastPosition.X);
  if Rectangle.Position.X < 0 then
     Rectangle.Position.X := 0;
  if Rectangle.Position.X > (SizeX - Rectangle.Width) then
     Rectangle.Position.X := SizeX - Rectangle.Width;

  Rectangle.Position.Y := Rectangle.Position.Y + (EventInfo.Location.Y - LastPosition.Y);
  if Rectangle.Position.Y < 0 then
     Rectangle.Position.Y := 0;
  if Rectangle.Position.Y > (SizeY - Rectangle.Height) then
     Rectangle.Position.Y := SizeY - Rectangle.Height;

  LastPosition := EventInfo.Location;
end;

procedure TForm1.RectangleGesture(Sender: TObject;  const EventInfo: TGestureEventInfo; var Handled: Boolean);
begin
 if EventInfo.GestureID = igiZoom then MyZoom(EventInfo) else
    if EventInfo.GestureID = igiPan then MyPan(EventInfo) else
       if EventInfo.GestureID = igiPressAndTap then MyPressAndTap else
          if EventInfo.GestureID = igiPressAndTap then MyPressAndTap else
             if EventInfo.GestureID = sgiUp   then MyUp else
                if EventInfo.GestureID = sgiDown then  MyDown else
                   if EventInfo.GestureID = sgiLeft then MyLeft else
                      if EventInfo.GestureID = sgiRight then MyRight;
end;

В этом примере хорошо видно, что начать нормально перемещать Rectangle вы можете только по диагонали, а не по вертикали и горизонтали. Нормального перемещения не получается - при жестах перемещения вверх, вниз, влево, вправо наш объект прыгает и в конце концов уезжает.  Однако выключив стандартные свойства отвечающие за поведение жестов - вверх, вниз, влево, вправо - вы добьетесь номального перемещения rectangle.

Выставлены стандартные жесты:
Вместе с ними выставлены интерактивные жесты - особое внимание на igPan:






Продолжение следует.


5 комментариев:

  1. Касательно модальных форм замечание не совсем корректное. Их использовать можно и нужно...
    Посмотрите на вот эту статейку: http://www.delphifeeds.com/go/s/108702

    ОтветитьУдалить
  2. Дело в том, что вызов ShowModal выдал на телефоне сообщение о неподдержке ее в этой платформе. Судя по статье там работа с ShowModal идет через анонимные методы, интересная концепция, попробуем новый механизм ShowModal.

    ОтветитьУдалить
  3. > 2.Прокрутка
    >... Соответственно, если в форму у вас не влазят в дизайнере компоненты то их нужно формировать в рунтайме, что не совсем удобно. ...

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

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

    ОтветитьУдалить
    Ответы
    1. да именно так я и пользуюсь. Бросаю несколько кнопок на TScrollBox и разношу их за пределы насколько это возможно.

      Удалить