Перейти к содержанию
  • Регистрация
  • 0
ra.eremeev

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

Вопрос

Добрый день!

Друзья, помогите, пожалуйста, побороть одну проблему: при загрузке картинок в 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
21 минуту назад, wamaco сказал:

приложите целиком проект

Он приложен. В самом первом сообщении. Дублирую

LoadBitmaps.zip

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
  • 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
В 01.02.2019 в 12:42, Евгений Корепов сказал:

создание/уничтожение экземпляра HTTPClient дороже

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

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
  • 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 пользователей онлайн

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

×
×
  • Создать...