Home > Enterprise >  ListTile image disappears when reordering list
ListTile image disappears when reordering list

Time:12-05

I want to put an image as the leading element of a ListTile, but when I do, the reordering behavior is not optimal. On top of being painfully slow (the majority of the slowness isn't because a leading image is being used, but it isn't helping things), the image disappears when dragging the ListTile, and flashes in and out if you click and hold the ListTile. What is curious is that if I use a premade Flutter Icon, the disappearing issues don't exist.

What options do I have to correct this?

Dartpad gist here.

// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// Flutter code sample for ReorderableListView.ReorderableListView.builder

import 'dart:convert';
import 'package:flutter/material.dart';

// PNG base64 too big to include in SO question
const String _pngBase64 = 'YOUR_PNG_BASE_64_HERE';

void main() => runApp(const ReorderableApp());

class ReorderableApp extends StatelessWidget {
  const ReorderableApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: const Text('ReorderableListView Sample')),
        body: const Center(
          child: ReorderableExample(),
        ),
      ),
    );
  }
}

class ReorderableExample extends StatefulWidget {
  const ReorderableExample({super.key});

  @override
  State<ReorderableExample> createState() => _ReorderableExampleState();
}

class _ReorderableExampleState extends State<ReorderableExample> {
  final List<int> _items = List<int>.generate(50, (int index) => index);

  @override
  Widget build(BuildContext context) {
    final ColorScheme colorScheme = Theme.of(context).colorScheme;
    final Color oddItemColor = colorScheme.primary.withOpacity(0.05);
    final Color evenItemColor = colorScheme.primary.withOpacity(0.15);

    return ReorderableListView.builder(
      padding: const EdgeInsets.symmetric(horizontal: 40),
      itemCount: _items.length,
      itemBuilder: (BuildContext context, int index) {
        return ListTile(
          key: Key('$index'),
          leading: Image.memory(base64Decode(_pngBase64)),
          tileColor: _items[index].isOdd ? oddItemColor : evenItemColor,
          title: Text('Item ${_items[index]}'),
        );
      },
      onReorder: (int oldIndex, int newIndex) {
        setState(() {
          if (oldIndex < newIndex) {
            newIndex -= 1;
          }
          final int item = _items.removeAt(oldIndex);
          _items.insert(newIndex, item);
        });
      },
    );
  }
}

CodePudding user response:

Inspired by How to cache memory image using Image.memory() or MemoryImage() flutter?

You can try to use this custom ImageProvider :

import 'dart:ui';

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';

class CacheImageProvider extends ImageProvider<CacheImageProvider> {
  final String tag; //the cache id use to get cache
  final Uint8List img; //the bytes of image to cache

  CacheImageProvider(this.tag, this.img);

  @override
  ImageStreamCompleter load(
      CacheImageProvider key,
      Future<Codec> Function(Uint8List,
              {bool allowUpscaling, int? cacheHeight, int? cacheWidth})
          decode) {
    return MultiFrameImageStreamCompleter(
      codec: _loadAsync(decode),
      scale: 1.0,
      debugLabel: tag,
      informationCollector: () sync* {
        yield ErrorDescription('Tag: $tag');
      },
    );
  }

  Future<Codec> _loadAsync(
      Future<Codec> Function(Uint8List,
              {bool allowUpscaling, int? cacheHeight, int? cacheWidth})
          decode) async {
    // the DefaultCacheManager() encapsulation, it get cache from local storage.
    final Uint8List bytes = img;

    if (bytes.lengthInBytes == 0) {
      // The file may become available later.
      PaintingBinding.instance.imageCache.evict(this);
      throw StateError('$tag is empty and cannot be loaded as an image.');
    }

    return await decode(bytes);
  }

  @override
  Future<CacheImageProvider> obtainKey(ImageConfiguration configuration) {
    return SynchronousFuture<CacheImageProvider>(this);
  }

  @override
  bool operator ==(Object other) {
    if (other.runtimeType != runtimeType) return false;
    bool res = other is CacheImageProvider && other.tag == tag;
    return res;
  }

  @override
  int get hashCode => tag.hashCode;

  @override
  String toString() =>
      '${objectRuntimeType(this, 'CacheImageProvider')}("$tag")';
}

Then use it like this in your code

leading: Image(image: CacheImageProvider(_pngBase64, base64Decode(_pngBase64))),
  • Related