Да там всё Евгений Корепов сделал уже. Я только три копейки своих добавил...
Вот конечный код:
unit Unit1;
interface
uses
System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants, System.Net.HttpClient, System.Generics.Collections,
FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.ListView.Types, FMX.ListView.Appearances, FMX.ListView.Adapters.Base,
FMX.Controls.Presentation, FMX.StdCtrls, FMX.Layouts, FMX.ListView;
const
ListViewItemImageEmpty = -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
FListViewUpdating : Boolean;
FHTTPClient : THTTPClient;
FAsyncResultList : TList<IAsyncResult>;
procedure LoadImage(const AItem: TListViewItem; const AListItemImage : TListItemImage);
procedure ClearListViewAndCancelAsynchronousRequests();
public
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;
FreeAndNil(FAsyncResultList);
if Assigned(FHTTPClient) then
FHTTPClient.Free;
end;
procedure TForm1.ClearListViewAndCancelAsynchronousRequests();
var
I: Integer;
begin
FListViewUpdating := True; // Запрещаем продолжать загружать фотки (если ещё не успели загрузиться все)
while FAsyncResultList.Count > 0 do // Дожидаемся окончания выполнения всех IAsyncResult.Cancel, несмотря на асинхронность
begin
for I := FAsyncResultList.Count - 1 downto 0 do
if Assigned(FAsyncResultList.Items) and (not FAsyncResultList.Items.IsCompleted) then
FAsyncResultList.Items.Cancel
else
FAsyncResultList.Delete(I); // Заодно удаляем отработанные элементы
end;
ListView1.Items.Clear;
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'] := ListViewItemImageEmpty;
FListViewUpdating := False;
Item.Adapter.ResetView(Item);
end;
end;
procedure TForm1.LoadImage(const AItem: TListViewItem; const AListItemImage : TListItemImage);
var
K: Integer; // Анонимная процедура захватывает локальную переменную, а не обращается к AItem, которой уже может не быть в момент _окончания_ скачивания фотки
AAsyncResult: IAsyncResult;
begin
if Not Assigned(AItem) or Not Assigned(AListItemImage) then
Exit;
if AItem.Data['ImageState'].AsInteger <> ListViewItemImageEmpty then
Exit;
if AItem.Data['ImageURL'].AsString.IsEmpty then
Exit;
AItem.Data['ImageState'] := ListViewItemImageLoading;
K := AItem.Index; // Запоминаем индекс в локальную K, которая уйдёт в анонимку (время жизни K > времени жизни анонимки)
FAsyncResultList.Items[K] := FHTTPClient.BeginGet(
procedure (const ASyncResult: IAsyncResult)
var
AHTTPResponse: IHTTPResponse;
begin
if ASyncResult.IsCancelled then
Exit;
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 FListViewUpdating or ASyncResult.IsCancelled then // Выходим, так как внутри анонимной процедуры AItem или AListItemImage - не сброшены в nil, хотя их уже может и не быть
Exit;
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;
FAsyncResultList.Items[K] := 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;
var
LIT: TListItemText;
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;
// Создаём надпись
LIT := TListItemText.Create(AItem);
LIT.Name := 'LIT-' + AItem.Index.ToString;
LIT.Width := 100;
LIT.Height := 22;
LIT.PlaceOffset.X := X + AWidth + 10;
LIT.PlaceOffset.Y := Y;
LIT.Text := LIT.Name;
LIT.Visible := 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.