Перейти к содержанию
Fire Monkey от А до Я
  • 0

Отображение картинок в ListView


ra.eremeev

Вопрос

Добрый день!

Друзья, помогите, пожалуйста, побороть одну проблему: при загрузке картинок в ListView картинки не отображаются до выполнения какого-либо действия с самим ListView (например, скрола или простого прикосновения к нему). Т.е., требуется прикоснуться к компоненту, чтобы картинки появились.

Проблема возникает только с собственноручно созданным объектом TListViewImage. При использовании для вывода изображений "стандартный" Image (например, ItemAppearance=ImageListItem), проблем не возникает :(

Но иногда требуется больше одного изображения и необходимо создавать свои.

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

Буду ОЧЕНЬ признателен за помощь!

LoadBitmaps.zip

Ссылка на комментарий

Рекомендуемые сообщения

  • 0
16 минут назад, wamaco сказал:

ownerBitmap := true;

Спасибо, но не помогает :(

В проекте это используется в процедуре создания TListitemImage

function TForm1.InsertImageObject(const Name:string; Width, Height:Single; PlaceOffsetX,PlaceOffsetY:Single; HorizAlign,VertAlign: TListItemAlign; AItem:TlistViewItem; LV:TListView):TListItemImage;
begin
  result := aitem.Objects.FindObjectT<TListItemImage>(name);
  if result = NIL then
  result := TListItemImage.Create(AItem);
  Result.Name := Name;
  result.width := Width;
  result.height := Height;
  result.placeoffset.x := PlaceOffsetX;
  result.placeoffset.y := PlaceOffsetY;
  result.Align := HorizAlign;
  result.VertAlign := VertAlign;
  result.OwnsBitmap:=true;
  Result.Bitmap:=TBitmap.Create;
end;

Ссылка на комментарий
  • 0
28 минут назад, mazayhin сказал:

Попробуйте сделать Adapter.ResetView, может поможет

Спасибо за отклик! В сформированном проекте он используется. Но проблему так и не решает :(

Ссылка на комментарий
  • 0
В 27.01.2018 в 22:05, ra.eremeev сказал:

Спасибо, но не помогает :(

В проекте это используется в процедуре создания TListitemImage

function TForm1.InsertImageObject(const Name:string; Width, Height:Single; PlaceOffsetX,PlaceOffsetY:Single; HorizAlign,VertAlign: TListItemAlign; AItem:TlistViewItem; LV:TListView):TListItemImage;
begin
  result := aitem.Objects.FindObjectT<TListItemImage>(name);
  if result = NIL then
  result := TListItemImage.Create(AItem);
  Result.Name := Name;
  result.width := Width;
  result.height := Height;
  result.placeoffset.x := PlaceOffsetX;
  result.placeoffset.y := PlaceOffsetY;
  result.Align := HorizAlign;
  result.VertAlign := VertAlign;
  result.OwnsBitmap:=true;
  Result.Bitmap:=TBitmap.Create;
end;

не там делаете  OwnsBitmap:=true.... сделаете примерно так... (в событии ListView: UpdateObjects)

procedure TForm1.LsvObjectsUpdateObjects(const Sender: TObject;
  const AItem: TListViewItem);
var
  ...
  oConnectImage: TListItemImage;
begin


  oConnectImage:=aItem.Objects.FindDrawable('ConnectImage') as TListItemImage;
  if (oConnectImage<>nil) then
  begin
    oConnectImage.OwnsBitmap:=True;
    oConnectImage.ImageIndex:=1;
  end;
  ...
end;
Изменено пользователем wamaco
Ссылка на комментарий
  • 0
58 минут назад, wamaco сказал:

не там делаете  OwnsBitmap:=true.... сделаете примерно так... (в событии ListView: UpdateObjects)


procedure TForm1.LsvObjectsUpdateObjects(const Sender: TObject;
  const AItem: TListViewItem);
var
  ...
  oConnectImage: TListItemImage;
begin


  oConnectImage:=aItem.Objects.FindDrawable('ConnectImage') as TListItemImage;
  if (oConnectImage<>nil) then
  begin
    oConnectImage.OwnsBitmap:=True;
    oConnectImage.ImageIndex:=1;
  end;
  ...
end;

Получается, что там же :(  InsertImageObject (в котором у меня  OwnsBitmap:=true) используется в ListViewUpdateObjects.

Т.е., то же самое, что в указанном Вами примере... 

Ссылка на комментарий
  • 0

Выкладываю решение. Надеюсь, кому-то будет полезно

Есть ощущение, что это очередной костыль, но работает.

Проблема - в необходимости перерисовки вручную добавленного TListItemImage после загрузки его Bitmap в потоке.

 

LoadBitmaps.zip

Ссылка на комментарий
  • 0
В 13.02.2018 в 09:22, ra.eremeev сказал:

Выкладываю решение. Надеюсь, кому-то будет полезно

Есть ощущение, что это очередной костыль, но работает.

Проблема - в необходимости перерисовки вручную добавленного TListItemImage после загрузки его Bitmap в потоке.

 

LoadBitmaps.zip

Добрый день!

Использую Ваш пример... и столкнулся с такой же траблой.. (в последнем приложенном файле та же трабла..)

Подскажите, пжл, куда копать..

procedure TForm1.FormCreate(Sender: TObject);
var
  sUrl: string;
  i: integer;
  item:TListViewItem;
begin
 with qLess do
  try
    if qLess.Active then Close;
    Open;
    while not eof do
     begin
      with ListView1 do
       begin
        Item:=listview1.Items.Add;
        Item.Text := qLessLESS_NAME.AsString;
        Item.Detail:= qLessLESS_DESC.AsString;
        Item.Data['URL'] := qLessIMG_URL.AsString;
        Item.Data['loading'] := 0; // даём знать, что можно загрузить картинку
       end;
       next;
     end;
  except
   //
  end;
 end;


procedure TForm1.ListView1Paint(Sender: TObject; Canvas: TCanvas;
  const ARect: TRectF);
var i:integer;
begin
  for i := 0 to ListView1.Items.Count-1 do
  begin
    if (i >= 0) and (i < ListView1.Items.Count) then
    begin
     if  ListView1.Items.Bitmap.Image<>NIL then
      if (ListView1.Items.Data['loading'].AsInteger = 0) then
      begin
        ListView1.Items.Data['loading']:= 1;
        ListView1.Items.Bitmap.LoadFromUrlToListViewItem(ListView1.Items.Data['URL'].AsString, ListView1);
      end;
    end;
  end;
end;

{ TBitmapHelper }

procedure TBitmapHelper.LoadFromUrlToListViewItem(AUrl: string;
  AListView: TListView);
var thread: TThread;
begin
  thread := TThread.CreateAnonymousThread(
  procedure
  var
  NetHTTPClient: TNetHTTPClient;
  Result: TMemoryStream;
  begin
    Result := TMemoryStream.Create;
    NetHTTPClient := TNetHTTPClient.Create(nil);
    try
      try
        NetHTTPClient.Get(AUrl, Result);
        TThread.Synchronize(TThread.CurrentThread,
        procedure()
        var
        tempBitMap: TBitmap;
        begin
          tempBitMap := TBitmap.Create;
          tempBitMap.LoadFromStream(Result);
          if not tempBitMap.IsEmpty then
          begin
            self.Assign(tempBitMap);
            AListView.Paint;
          end;
        end);
        except
        Result.Free;
      end;
      finally
      NetHTTPClient.Free;
    end;
  end);
  thread.FreeOnTerminate := true;
  thread.start;
end;

Ссылка на комментарий
  • 0
В 29.05.2018 в 12:41, Dmitry Stolyarov сказал:

Добрый день!

Использую Ваш пример... и столкнулся с такой же траблой.. (в последнем приложенном файле та же трабла..)

Подскажите, пжл, куда копать..

Дмитрий, добрый день!

Пропустил Ваше сообщение :( 

Полноценное решение так и не найдено. Все "работает" так, как представлено во вложенном проекте.

Удалось ли Вам решить?

Ссылка на комментарий
  • 0
В 28.01.2019 в 09:21, ra.eremeev сказал:

Дмитрий, добрый день!

Пропустил Ваше сообщение :( 

Полноценное решение так и не найдено. Все "работает" так, как представлено во вложенном проекте.

Удалось ли Вам решить?

Проблемы не существует в принципе. Пытался разобраться в коде, но из за его полной не читабельности, плюнул, все удалил а написал заново. Решил для забавы использовать анонимный поток для загрузки картинки. По кнопке все грузится и сразу отображается. Никаких костылей не используется (ну кроме как AListItemImage.OwnsBitmap:=True, но и без этого все работает).

Прилагаю код и архив проекта:

unit Unit1;

interface

uses
  System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
  FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs,
  FMX.ListView.Types, FMX.ListView.Appearances, FMX.ListView.Adapters.Base,
  System.Net.HttpClient,
  FMX.Controls.Presentation, FMX.StdCtrls, FMX.Layouts, FMX.ListView;

const
  ListViewItemImageEmpy = -1;
  ListViewItemImageLoading = 0;
  ListViewItemImageLoaded = 1;

type
  TForm1 = class(TForm)
    ListView1: TListView;
    Layout1: TLayout;
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
    procedure ListView1UpdatingObjects(const Sender: TObject;
      const AItem: TListViewItem; var AHandled: Boolean);
    procedure FormCreate(Sender: TObject);
  private
    { Private declarations }
    FListViewUpdating : Boolean;
    procedure LoadImage(const AItemIndex : Integer; AURL : String);
  public
    { Public declarations }
  end;

var
  Form1: TForm1;
implementation

{$R *.fmx}

procedure TForm1.FormCreate(Sender: TObject);
begin
  FListViewUpdating:=False;
end;

procedure TForm1.Button1Click(Sender: TObject);
var i:integer;
    item:TListViewItem;
begin
  listview1.ItemIndex:=0;
  listview1.ItemAppearance.ItemAppearance:='Custom';
  listview1.ItemAppearanceObjects.ItemObjects.Accessory.Visible:=false;

  //Очистка ListView
  for i := Listview1.ItemCount-1 downto 0 do
  ListView1.Items.Delete(i);

  //Формирование нового списка
  for i := 1 to 10 do
  begin
    Item:=listview1.Items.Add;
    item.Height:=45;
    FListViewUpdating:=true;
    item.data['ImageURL']:='http://fire-monkey.ru/uploads/monthly_2016_03/5-icon-29796ebcb510717e74ed258822feb2cc.png.365100cc218fe330c717aa80384fa4aa.png';
    Item.Data['ImageState']:=ListViewItemImageEmpy;
    FListViewUpdating:=false;
    item.Adapter.ResetView(item);
  end;

end;

procedure TForm1.LoadImage(const AItemIndex : Integer; AURL : String);
Var AHTTPClient : THTTPClient;
    AHTTPResponse : IHTTPResponse;

begin
  AHTTPClient:=THTTPClient.Create();
  AHTTPResponse:=AHTTPClient.Get(AURL);
  AHTTPClient.Free;
  if AHTTPResponse.StatusCode <> 200 then
    exit;
  TThread.Synchronize(Nil,
    procedure
    Var AListItemImage : TListItemImage;
    begin
      AListItemImage:=TListItemImage(ListView1.Items.Item[AItemIndex].View.FindDrawable('s_image'));
      if Assigned(AListItemImage) then
      begin
        AListItemImage.Bitmap:=TBitmap.Create;
        AListItemImage.Bitmap.LoadFromStream(AHTTPResponse.ContentStream);
      end;
    end
  );
end;

procedure TForm1.ListView1UpdatingObjects(const Sender: TObject; const AItem: TListViewItem; var AHandled: Boolean);
  function SetupImageObject(const AName : String; AWidth, AHeight, X , Y : Single; AAlign, AVertAlign: TListItemAlign) : TListItemImage;
  Var AImageState : Integer;
      AImageURL : String;
      AListItemImage : TListItemImage;
  begin
    AListItemImage:=TListItemImage(AItem.View.FindDrawable(AName));
    if AListItemImage = Nil then
    begin
      AListItemImage:=TListItemImage.Create(AItem);
      AListItemImage.Name:=AName;
      AListItemImage.Bitmap:=Nil;
      AListItemImage.OwnsBitmap:=True;
    end;
    if AListItemImage.Bitmap=Nil then
    begin
      AImageState:=AItem.Data['ImageState'].AsInteger;
      if AImageState=ListViewItemImageEmpy then
      begin
        AImageURL:=AItem.Data['ImageURL'].AsString;
        if Not AImageURL.IsEmpty then
        begin
          AItem.Data['ImageState']:=ListViewItemImageLoading;
          TThread.CreateAnonymousThread(
            procedure
            begin
              LoadImage(AItem.Index, AImageURL);
            end).Start;
        end;
      end;
    end;
    AListItemImage.Width:=AWidth;
    AListItemImage.Height:=AHeight;
    AListItemImage.PlaceOffset.X:=X;
    AListItemImage.PlaceOffset.Y:=Y;
    AListItemImage.Align:=AAlign;
    AListItemImage.VertAlign:=AVertAlign;
    AListItemImage.ScalingMode:=TImageScalingMode.StretchWithAspect;
    AListItemImage.Visible:=True;
    Result:=AListItemImage;
  end;
begin
  if FListViewUpdating then
    exit;
  SetupImageObject('s_image', 35, 35, 0 , 0, TListitemalign.Leading, TListItemAlign.Center);
  AHandled:=true;
end;

end.

 

LoadBitmaps.zip

Ссылка на комментарий
  • 0
4 часа назад, Евгений Корепов сказал:

Проблемы не существует в принципе. Пытался разобраться в коде, но из за его полной не читабельности, плюнул, все удалил а написал заново. Решил для забавы использовать анонимный поток для загрузки картинки. По кнопке все грузится и сразу отображается. Никаких костылей не используется (ну кроме как AListItemImage.OwnsBitmap:=True, но и без этого все работает).

За код спасибо! НО...

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

Я, например, гружу картинки в высоком качестве и большого разрешения + соответствующий Scale, который по сути умножает ширину и высоту Bitmap... к сожалению ваш код загнется на 100-120 элементе.

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

Ну и плюс у Вас есть задержка при выводе списка, а отрисовка в OnPaint, можно сделать только для видимых item-ов, поэтому ListView появляется мгновенно.

Попробуйте, поставьте количество элементов 500 и картинки 300*300 и запустите..... на Android умрет сразу!

Изменено пользователем #WAMACO
Ссылка на комментарий
  • 0

умрет потому что используется TThread.CreateAnonymousThread, 500 потоков это не шутка...
переделать на TTask. TTask плодит ограниченное кол-во потоков.
или вовсе отказаться от собственных потоков и отдать на откуп асинхронному запросу THTTPClient
1 экземпляр THTTPClient может одновременно обслуживать несколько асинхронных запросов (4-8)
HTTPClient.BeginGet
 

Изменено пользователем Slym
Ссылка на комментарий
  • 0
10 часов назад, #WAMACO сказал:

За код спасибо! НО...

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

Я, например, гружу картинки в высоком качестве и большого разрешения + соответствующий Scale, который по сути умножает ширину и высоту Bitmap... к сожалению ваш код загнется на 100-120 элементе.

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

Ну и плюс у Вас есть задержка при выводе списка, а отрисовка в OnPaint, можно сделать только для видимых item-ов, поэтому ListView появляется мгновенно.

Попробуйте, поставьте количество элементов 500 и картинки 300*300 и запустите..... на Android умрет сразу!

Вот вы же сейчас шутите да? Я просто взял код который не работал совсем (отрисовка после скрола - это совсем), и сделал из него 100% рабочий код. Потратил на это около 10 минут. Упомянул что код и метод подгрузки - для забавы. А вы начинаете мне говорить что для продакшина этот код не годится, что я все сделал не правильно, что на больших списках все загнется.

Автор топика четко описал проблему:

Цитата

побороть одну проблему: при загрузке картинок в ListView картинки не отображаются до выполнения какого-либо действия с самим ListView (например, скрола или простого прикосновения к нему)

Я проблему решил? Показал что проблема была в ошибочной логике приложения, а не в неких глюках ListView? А мы мне начинаете рассказывать о том что вот если этот демо код запустить в космос, то ой-ой-ой случится. ?

По пунктам:

1. Отрисовка в OnPaint - событие может вызываться как угодно часто, ничто не запретит системе вызвать его несколько раз в секунду. Цикл перебора всех элементов списка в этом событии с остановкой нормальной отрисовки - очень-очень плохой совет.

2. Картинки в высоком качестве и (или) их комплект с разным scale - логика приложения должна быть построена так, чтобы определять нужный scale и загружать только подходящие картинки с вашего сервера.

По поводу потоков и их количества - нужно помнить что кроме ресурсов телефона, используются ресурсы полосы пропускания текущего Интернет соединения на устройстве и ресурсы сервера. Поэтому лучше для загрузки использовать 1 (ну может 2-3) потока - десяток видимых картинок последовательно загрузятся почти мгновенно.

4 часа назад, Slym сказал:

умрет потому что используется TThread.CreateAnonymousThread, 500 потоков это не шутка...
переделать на TTask. TTask плодит ограниченное кол-во потоков.
или вовсе отказаться от собственных потоков и отдать на откуп асинхронному запросу THTTPClient
1 экземпляр THTTPClient может одновременно обслуживать несколько асинхронных запросов (4-8)
HTTPClient.BeginGet
 

К сожалению логика асинхронности HTTPClient упускает одну важную вещь - идентификацию полученного результата. Т.е. даем пяток заданий GET, загрузить картинку для Item 1,2,3,4,5 - но при получении результата мы никак не можем определить что полученный результат относится к Item 3, а не какому либо другом. По крайней мере мне это не удалось сделать просто и прозрачно. Посему остается вариант создания экземпляра HTTPClient для каждого запроса - возвращаемся к фактически анонимным процедурам, попутно стреляя себе в ногу, потому как создание/уничтожение экземпляра HTTPClient чуть ли не дороже скачивания 10 килобайтной картинки )

 

Можете посмотреть пример реализации подобной задачи в моем старинном приложении, там как раз реализован подход о котором говорил - загрузка картинок только видимых элементов (+ несколько про запас), загрузка картинок последовательно одним потоком ну и еще что то:. https://play.google.com/store/apps/details?id=ru.flintnet.Actions5

Ссылка на комментарий
  • 0
1 час назад, krapotkin сказал:

проверялось? или гипотеза?

Вывод на основе анализа кода:

THTTPClient.Create влечет за собой создание целого букета объектов - TCookieManager, TURLClient,  TCredentialsStorage,  TObjectDictionary<string, TURLClient>,  несколько TList в недрах этих объектов. Плюс нужно инициализировать параметры - таймауты, хидеры, акцепты, добавлять куки из глобального хранилища (если на сервере есть некая авторизация). Учитывая качество кода Эмбаркадеро, где то в этих объектах возможно есть утечки, так что создание/удаление нескольких тысяч экземпляров может привести к неясным последствиям.

Но это мое предвзятое мнение. Для подобных задач предпочитаю создавать один (или стек из нескольких) объект с потоком, который обслуживает нужды приложения от старта до завершения.

Ссылка на комментарий
  • 0

я делал апи которое создавало каждый раз все заново и тоже именно из-за подозрений на качество кода, мало ли что там накопится )))

производительности вполне хватало

Ссылка на комментарий
  • 0
В 01.02.2019 в 14:42, Евгений Корепов сказал:

К сожалению логика асинхронности HTTPClient упускает одну важную вещь - идентификацию полученного результата

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

Ссылка на комментарий
  • 0
12 часов назад, Slym сказал:

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

Точно! Век живи - век учись! Что то я сильно тупанул в свое время, проверяя возможности асинхронности. Проверил  - работает отменно. Сделал вариант с глобальным HTTPClient, отдельной процедурой загрузки (в ней также можно воткнуть проверку на видимость/невидимость итема, если требуется такая логика)

Единственная проблема - остановка всей ассинхронной загрузки. При выходе из приложения, при нажатии старт до полной загрузки картинок - выбрасывает исключение, что логично (специально для этого увеличил количество итемов до 10 тысяч). Пробовал создавать лист с IAsyncResult и ими манипулировать, но надоело разбираться, закомментировал. Просто болеем всей семьей,  сейчас у меня температура 39.8 - голова работает отчасти только )))))))))))

Код и архив с проектом:

unit Unit1;

interface

uses
  System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
  FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs,
  FMX.ListView.Types, FMX.ListView.Appearances, FMX.ListView.Adapters.Base,
  System.Net.HttpClient,
  System.Generics.Collections,
  FMX.Controls.Presentation, FMX.StdCtrls, FMX.Layouts, FMX.ListView;

const
  ListViewItemImageEmpy = -1;
  ListViewItemImageLoading = 0;
  ListViewItemImageLoaded = 1;

type
  TForm1 = class(TForm)
    ListView1: TListView;
    Layout1: TLayout;
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
    procedure ListView1UpdatingObjects(const Sender: TObject;
      const AItem: TListViewItem; var AHandled: Boolean);
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
  private
    { Private declarations }
    FListViewUpdating : Boolean;
    FHTTPClient : THTTPClient;
//    FAsyncResultList : TList<IAsyncResult>;
    procedure LoadImage(const AItem: TListViewItem; const AListItemImage : TListItemImage);
  public
    { Public declarations }
  end;

var
  Form1: TForm1;
implementation

{$R *.fmx}

procedure TForm1.FormCreate(Sender: TObject);
begin
  FHTTPClient:=THTTPClient.Create;
//  FAsyncResultList:=TList<IAsyncResult>.Create;
  FListViewUpdating:=False;
end;

procedure TForm1.FormDestroy(Sender: TObject);
Var I : Integer;
begin
  FListViewUpdating:=True;
  ListView1.Items.Clear;
//  for I := 0 to FAsyncResultList.Count - 1 do
//    if Assigned(FAsyncResultList.Items[I]) then
//      if Not FAsyncResultList.Items[I].IsCompleted then
//        FAsyncResultList.Items[I].Cancel;
//      FHTTPClient.EndAsyncHTTP(FAsyncResultList.Items[I]);
  if Assigned(FHTTPClient) then
    FHTTPClient.Free;
end;

procedure TForm1.Button1Click(Sender: TObject);
var i:integer;
    item:TListViewItem;
    ARandom : Integer;
begin
  listview1.ItemIndex:=0;
  listview1.ItemAppearance.ItemAppearance:='Custom';
  listview1.ItemAppearanceObjects.ItemObjects.Accessory.Visible:=false;


  //Очистка ListView
//  for i := Listview1.ItemCount-1 downto 0 do
//    ListView1.Items.Delete(i);
  FListViewUpdating:=True;
  ListView1.Items.Clear;
  FListViewUpdating:=False;

  //Формирование нового списка
  for i := 1 to 10000 do
  begin
//    FAsyncResultList.Add(Nil);
    FListViewUpdating:=True;
    Item:=listview1.Items.Add;
    item.Height:=45;
    Randomize;
    ARandom:=Random(6);
    case ARandom of
      0 : item.data['ImageURL']:='http://fire-monkey.ru/uploads/monthly_2017_06/me.thumb.jpg.966ddc17d5602ee14feb43479c1f6963.jpg';
      1 : item.data['ImageURL']:='http://fire-monkey.ru/uploads/monthly_2018_05/B-IpGQmVgTM.thumb.jpg.2ebeb0bd766ab7cf19f10195d6ea2be9.jpg';
      2 : item.data['ImageURL']:='http://fire-monkey.ru/uploads/monthly_2016_04/10.png.b9ab371e8fd38172fee96bcf75fb6699.thumb.png.b0685259b03bfff540903913845532a5.png';
      3 : item.data['ImageURL']:='https://secure.gravatar.com/avatar/9942c50b1641a921c52d4b389bd718d6?d=http://fire-monkey.ru/uploads/monthly_2017_12/K_member_87.png';
      4 : item.data['ImageURL']:='http://fire-monkey.ru/uploads/monthly_2016_11/photo-1529.png.7267be10b59f950b7c5bb3f34a60901e.thumb.png.22027ae85266216220310ed694d57628.png';
      5 : item.data['ImageURL']:='http://fire-monkey.ru/uploads/profile/photo-thumb-115.jpg';
    end;
    Item.Data['ImageState']:=ListViewItemImageEmpy;
    FListViewUpdating:=False;
    item.Adapter.ResetView(item);
  end;

end;

procedure TForm1.LoadImage(const AItem: TListViewItem; const AListItemImage : TListItemImage);
Var AAsyncResult : IAsyncResult;
begin
  if Not Assigned(AItem) or Not Assigned(AListItemImage) then
    exit;
  if AItem.Data['ImageState'].AsInteger <> ListViewItemImageEmpy then
    exit;
  if AItem.Data['ImageURL'].AsString.IsEmpty then
    exit;
  AItem.Data['ImageState']:=ListViewItemImageLoading;

//  FAsyncResultList.Items[AItem.Index]:=FHTTPClient.BeginGet(
  FHTTPClient.BeginGet(
    procedure (const ASyncResult: IAsyncResult)
    Var AHTTPResponse : IHTTPResponse;
    begin
      AHTTPResponse:=THTTPClient.EndAsyncHTTP(ASyncResult);
      if AHTTPResponse.StatusCode <> 200 then
        exit;
      TThread.Synchronize(Nil,
        procedure
        begin
          if Not Assigned(AItem) or Not Assigned(AListItemImage) then
            exit;
          AListItemImage.BeginUpdate;
          AListItemImage.Bitmap:=TBitmap.Create;
          AListItemImage.Bitmap.LoadFromStream(AHTTPResponse.ContentStream);
          AListItemImage.EndUpdate;
          AItem.Data['ImageState']:=ListViewItemImageLoaded;
        end
      );
    end,
    AItem.Data['ImageURL'].AsString
  );
end;

procedure TForm1.ListView1UpdatingObjects(const Sender: TObject; const AItem: TListViewItem; var AHandled: Boolean);
  function SetupImageObject(const AName : String; AWidth, AHeight, X , Y : Single; AAlign, AVertAlign: TListItemAlign) : TListItemImage;
  begin
    Result:=TListItemImage(AItem.View.FindDrawable(AName));
    if Result = Nil then
    begin
      Result:=TListItemImage.Create(AItem);
      Result.Name:=AName;
      Result.Bitmap:=Nil;
      Result.OwnsBitmap:=True;
    end;
    Result.Width:=AWidth;
    Result.Height:=AHeight;
    Result.PlaceOffset.X:=X;
    Result.PlaceOffset.Y:=Y;
    Result.Align:=AAlign;
    Result.VertAlign:=AVertAlign;
    Result.ScalingMode:=TImageScalingMode.StretchWithAspect;
    Result.Visible:=True;
  end;
Var AListItemImage : TListItemImage;
begin
  if FListViewUpdating then
    exit;
  AListItemImage:=SetupImageObject('s_image', 35, 35, 0 , 0, TListitemalign.Leading, TListItemAlign.Center);
  LoadImage(AItem, AListItemImage);
  AHandled:=true;
end;

end.

LoadBitmaps.zip

Ссылка на комментарий
  • 0
В 04.02.2019 в 10:19, krapotkin сказал:

я делал апи которое создавало каждый раз все заново и тоже именно из-за подозрений на качество кода, мало ли что там накопится )))

производительности вполне хватало

Надо собраться с силами и сделать тест производительности на разных платформах. А то вот я руководствуюсь субъективными ощущениями в этом вопросе ?

Ссылка на комментарий
  • 0
procedure TForm1.LoadImage(const AItem: TListViewItem; const AListItemImage : TListItemImage);
Var AAsyncResult : IAsyncResult;
begin
  if Not Assigned(AItem) or Not Assigned(AListItemImage) then
    exit;
  if AItem.Data['ImageState'].AsInteger <> ListViewItemImageEmpy then
    exit;
  if AItem.Data['ImageURL'].AsString.IsEmpty then
    exit;
  AItem.Data['ImageState']:=ListViewItemImageLoading;

  if not assigned(FHTTPClient) then exit;

  TMonitor.Enter(listview1);
  try
    AItem.TagObject:=
    FHTTPClient.BeginGet(
      procedure (const ASyncResult: IAsyncResult)
      Var AHTTPResponse : IHTTPResponse;
      begin
        if assigned(AItem.TagObject) then
        begin
          TMonitor.Enter(listview1);
          try
            AItem.TagObject:=nil;
          finally
            TMonitor.exit(listview1);
          end;
        end;
        if ASyncResult.IsCancelled then exit;

        AHTTPResponse:=THTTPClient.EndAsyncHTTP(ASyncResult);
        if AHTTPResponse.StatusCode <> 200 then
          exit;
        TThread.Queue(nil,
          procedure
          begin
            if Not Assigned(AItem) or Not Assigned(AListItemImage) then
              exit;
            AListItemImage.BeginUpdate;
            AListItemImage.Bitmap:=TBitmap.Create;
            AListItemImage.Bitmap.LoadFromStream(AHTTPResponse.ContentStream);
            AListItemImage.EndUpdate;
            AItem.Data['ImageState']:=ListViewItemImageLoaded;
          end);
      end,
      AItem.Data['ImageURL'].AsString) as TBaseAsyncResult;
  finally
    TMonitor.Exit(listview1);
  end;
end;

procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
var
  i:integer;
  IResult:IAsyncResult;
begin
  TMonitor.Enter(listview1);
  try
    for i:=0 to listview1.Items.Count-1 do
      if assigned(listview1.Items[i].TagObject) then
        (listview1.Items[i].TagObject as TBaseAsyncResult).Cancel;
  finally
    TMonitor.Exit(listview1);
  end;

  TMonitor.Enter(listview1);
  try
    for i:=0 to listview1.Items.Count-1 do
      if assigned(listview1.Items[i].TagObject) then
      begin
        IResult:=TBaseAsyncResult(listview1.Items[i].TagObject) as IAsyncResult;
        TMonitor.Exit(listview1);
        try
          THTTPClient.EndAsyncHTTP(IResult);
        except
        end;
        TMonitor.Enter(listview1);
      end;
  finally
    TMonitor.Exit(listview1);
  end;
end;

 

Ссылка на комментарий
  • 0
4 часа назад, Slym сказал:

procedure TForm1.LoadImage(const AItem: TListViewItem; const AListItemImage : TListItemImage);
Var AAsyncResult : IAsyncResult;
begin
  if Not Assigned(AItem) or Not Assigned(AListItemImage) then
    exit;
  if AItem.Data['ImageState'].AsInteger <> ListViewItemImageEmpy then
    exit;
  if AItem.Data['ImageURL'].AsString.IsEmpty then
    exit;
  AItem.Data['ImageState']:=ListViewItemImageLoading;

  if not assigned(FHTTPClient) then exit;

  TMonitor.Enter(listview1);
  try
    AItem.TagObject:=
    FHTTPClient.BeginGet(
      procedure (const ASyncResult: IAsyncResult)
      Var AHTTPResponse : IHTTPResponse;
      begin
        if assigned(AItem.TagObject) then
        begin
          TMonitor.Enter(listview1);
          try
            AItem.TagObject:=nil;
          finally
            TMonitor.exit(listview1);
          end;
        end;
        if ASyncResult.IsCancelled then exit;

        AHTTPResponse:=THTTPClient.EndAsyncHTTP(ASyncResult);
        if AHTTPResponse.StatusCode <> 200 then
          exit;
        TThread.Queue(nil,
          procedure
          begin
            if Not Assigned(AItem) or Not Assigned(AListItemImage) then
              exit;
            AListItemImage.BeginUpdate;
            AListItemImage.Bitmap:=TBitmap.Create;
            AListItemImage.Bitmap.LoadFromStream(AHTTPResponse.ContentStream);
            AListItemImage.EndUpdate;
            AItem.Data['ImageState']:=ListViewItemImageLoaded;
          end);
      end,
      AItem.Data['ImageURL'].AsString) as TBaseAsyncResult;
  finally
    TMonitor.Exit(listview1);
  end;
end;

procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
var
  i:integer;
  IResult:IAsyncResult;
begin
  TMonitor.Enter(listview1);
  try
    for i:=0 to listview1.Items.Count-1 do
      if assigned(listview1.Items[i].TagObject) then
        (listview1.Items[i].TagObject as TBaseAsyncResult).Cancel;
  finally
    TMonitor.Exit(listview1);
  end;

  TMonitor.Enter(listview1);
  try
    for i:=0 to listview1.Items.Count-1 do
      if assigned(listview1.Items[i].TagObject) then
      begin
        IResult:=TBaseAsyncResult(listview1.Items[i].TagObject) as IAsyncResult;
        TMonitor.Exit(listview1);
        try
          THTTPClient.EndAsyncHTTP(IResult);
        except
        end;
        TMonitor.Enter(listview1);
      end;
  finally
    TMonitor.Exit(listview1);
  end;
end;

 

У меня на Rio работает как то не стабильно. Изредка вылазят исключения при закрытии приложения. То в TMonitor, то даже в TDictionary (((

 

Сделал стабильную версию - работает быстро и без глюков, но с использованием внешнего списка с IAsyncResult. Добавил процедуру ClearListViewAndCancelAsynchronousRequests() где выполняется Cancel и очищается ListView. Теперь можно клацать по кнопке сколько угодно.

unit Unit1;

interface

uses
  System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
  FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs,
  FMX.ListView.Types, FMX.ListView.Appearances, FMX.ListView.Adapters.Base,
  System.Net.HttpClient,
  System.Generics.Collections,
  FMX.Controls.Presentation, FMX.StdCtrls, FMX.Layouts, FMX.ListView;

const
  ListViewItemImageEmpy = -1;
  ListViewItemImageLoading = 0;
  ListViewItemImageLoaded = 1;

type
  TForm1 = class(TForm)
    ListView1: TListView;
    Layout1: TLayout;
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
    procedure ListView1UpdatingObjects(const Sender: TObject;
      const AItem: TListViewItem; var AHandled: Boolean);
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
  private
    { Private declarations }
    FListViewUpdating : Boolean;
    FHTTPClient : THTTPClient;
    FAsyncResultList : TList<IAsyncResult>;
    procedure LoadImage(const AItem: TListViewItem; const AListItemImage : TListItemImage);
    procedure ClearListViewAndCancelAsynchronousRequests();
  public
    { Public declarations }
  end;

var
  Form1: TForm1;
implementation

{$R *.fmx}

procedure TForm1.FormCreate(Sender: TObject);
begin
  listview1.ItemIndex:=0;
  listview1.ItemAppearance.ItemAppearance:='Custom';
  listview1.ItemAppearanceObjects.ItemObjects.Accessory.Visible:=false;

  FHTTPClient:=THTTPClient.Create;
  FAsyncResultList:=TList<IAsyncResult>.Create;
  FListViewUpdating:=False;
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  ClearListViewAndCancelAsynchronousRequests();
  FListViewUpdating:=True;
  if Assigned(FHTTPClient) then
    FHTTPClient.Free;
end;

procedure TForm1.ClearListViewAndCancelAsynchronousRequests();
Var I : Integer;
begin
  for I := 0 to FAsyncResultList.Count - 1 do
    if FAsyncResultList.Items[I] <> Nil then
      if Not FAsyncResultList.Items[I].IsCompleted then
        FAsyncResultList.Items[I].Cancel;
  FListViewUpdating:=True;
  for I := ListView1.Items.Count - 1 downto 0 do
    ListView1.Items.Delete(I);
  FListViewUpdating:=False;
end;

procedure TForm1.Button1Click(Sender: TObject);
var i:integer;
    item:TListViewItem;
    ARandom : Integer;
begin
  ClearListViewAndCancelAsynchronousRequests();

  //Формирование нового списка
  for i := 1 to 10000 do
  begin
    FAsyncResultList.Add(Nil);
    FListViewUpdating:=True;
    Item:=listview1.Items.Add;
    item.Height:=45;
    Randomize;
    ARandom:=Random(6);
    case ARandom of
      0 : item.data['ImageURL']:='http://fire-monkey.ru/uploads/monthly_2017_06/me.thumb.jpg.966ddc17d5602ee14feb43479c1f6963.jpg';
      1 : item.data['ImageURL']:='http://fire-monkey.ru/uploads/monthly_2018_05/B-IpGQmVgTM.thumb.jpg.2ebeb0bd766ab7cf19f10195d6ea2be9.jpg';
      2 : item.data['ImageURL']:='http://fire-monkey.ru/uploads/monthly_2016_04/10.png.b9ab371e8fd38172fee96bcf75fb6699.thumb.png.b0685259b03bfff540903913845532a5.png';
      3 : item.data['ImageURL']:='https://secure.gravatar.com/avatar/9942c50b1641a921c52d4b389bd718d6?d=http://fire-monkey.ru/uploads/monthly_2017_12/K_member_87.png';
      4 : item.data['ImageURL']:='http://fire-monkey.ru/uploads/monthly_2016_11/photo-1529.png.7267be10b59f950b7c5bb3f34a60901e.thumb.png.22027ae85266216220310ed694d57628.png';
      5 : item.data['ImageURL']:='http://fire-monkey.ru/uploads/profile/photo-thumb-115.jpg';
    end;
    Item.Data['ImageState']:=ListViewItemImageEmpy;
    FListViewUpdating:=False;
    item.Adapter.ResetView(item);
  end;

end;

procedure TForm1.LoadImage(const AItem: TListViewItem; const AListItemImage : TListItemImage);
Var AAsyncResult : IAsyncResult;
begin
  if Not Assigned(AItem) or Not Assigned(AListItemImage) then
    exit;
  if AItem.Data['ImageState'].AsInteger <> ListViewItemImageEmpy then
    exit;
  if AItem.Data['ImageURL'].AsString.IsEmpty then
    exit;
  AItem.Data['ImageState']:=ListViewItemImageLoading;

  FAsyncResultList.Items[AItem.Index]:=FHTTPClient.BeginGet(
//  AItem.TagObject:=TBaseAsyncResult(FHTTPClient.BeginGet(
//  FHTTPClient.BeginGet(
    procedure (const ASyncResult: IAsyncResult)
    Var AHTTPResponse : IHTTPResponse;
    begin
      if ASyncResult.IsCancelled then
      begin
        exit;
      end;
      try
        AHTTPResponse:=THTTPClient.EndAsyncHTTP(ASyncResult);
        if Not Assigned(AHTTPResponse) then
          exit;
        if AHTTPResponse.StatusCode <> 200 then
          exit;
      except
        exit;
      end;

      TThread.Synchronize(Nil,
        procedure
        begin
          if Not Assigned(AItem) then
            exit;
          if Not Assigned(AListItemImage) then
            exit;

          AListItemImage.BeginUpdate;
          AListItemImage.Bitmap:=TBitmap.Create;
          AListItemImage.Bitmap.LoadFromStream(AHTTPResponse.ContentStream);
          AListItemImage.EndUpdate;
          AItem.Data['ImageState']:=ListViewItemImageLoaded;
          FAsyncResultList.Items[AItem.Index]:=Nil;
        end
      );
    end,
    AItem.Data['ImageURL'].AsString
  );
end;

procedure TForm1.ListView1UpdatingObjects(const Sender: TObject; const AItem: TListViewItem; var AHandled: Boolean);
  function SetupImageObject(const AName : String; AWidth, AHeight, X , Y : Single; AAlign, AVertAlign: TListItemAlign) : TListItemImage;
  begin
    Result:=TListItemImage(AItem.View.FindDrawable(AName));
    if Result = Nil then
    begin
      Result:=TListItemImage.Create(AItem);
      Result.Name:=AName;
      Result.Bitmap:=Nil;
      Result.OwnsBitmap:=True;
    end;
    Result.Width:=AWidth;
    Result.Height:=AHeight;
    Result.PlaceOffset.X:=X;
    Result.PlaceOffset.Y:=Y;
    Result.Align:=AAlign;
    Result.VertAlign:=AVertAlign;
    Result.ScalingMode:=TImageScalingMode.StretchWithAspect;
    Result.Visible:=True;
  end;
Var AListItemImage : TListItemImage;
begin
  if FListViewUpdating then
    exit;
  AListItemImage:=SetupImageObject('s_image', 35, 35, 0 , 0, TListitemalign.Leading, TListItemAlign.Center);
  LoadImage(AItem, AListItemImage);
  AHandled:=true;
end;

end.

 

Ссылка на комментарий

Присоединяйтесь к обсуждению

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

Гость
Ответить на вопрос...

×   Вставлено с форматированием.   Вставить как обычный текст

  Разрешено использовать не более 75 эмодзи.

×   Ваша ссылка была автоматически встроена.   Отображать как обычную ссылку

×   Ваш предыдущий контент был восстановлен.   Очистить редактор

×   Вы не можете вставлять изображения напрямую. Загружайте или вставляйте изображения по ссылке.

  • Последние посетители   0 пользователей онлайн

    • Ни одного зарегистрированного пользователя не просматривает данную страницу
×
×
  • Создать...