ListView에서 스크롤 시 아이템이 재로딩되는 현상은 일반적으로 상태를 적절하게 관리하지 않거나, 특히 이미지 로드와 관련해서는 캐싱이 제대로 되지 않을 때 발생합니다. 이를 해결하기 위해서는 다음과 같은 방법을 사용할 수 있습니다.
1. `ListView.builder` 사용
`ListView.builder`는 아이템을 효율적으로 렌더링하기 때문에, 스크롤 시 전체 아이템을 다시 빌드하는 것이 아니라 필요한 아이템만 빌드합니다.
이렇게 하면 불필요한 렌더링을 줄일 수 있습니다.
ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(items[index].value),
);
},
)
2. `CachedNetworkImage` 사용
이미지를 네트워크에서 가져올 때는 `cached_network_image` 패키지를 사용하면 이미지가 캐싱되어 스크롤 시 재로딩되는 현상을 방지할 수 있습니다.
`pubspec.yaml`에 패키지를 추가합니다.
dependencies:
cached_network_image: ^3.2.1
그런 다음 이미지를 로드할 때 `CachedNetworkImage`를 사용합니다.
import 'package:cached_network_image/cached_network_image.dart';
// ...
CachedNetworkImage(
imageUrl: items[index].imageUrl,
placeholder: (context, url) => CircularProgressIndicator(),
errorWidget: (context, url, error) => Icon(Icons.error),
)
3. `AutomaticKeepAliveClientMixin` 사용
아이템이 복잡하거나 상태를 유지해야 하는 경우 `AutomaticKeepAliveClientMixin`을 사용하여 아이템의 상태를 유지할 수 있습니다.
class MyListItem extends StatefulWidget {
final Item item;
MyListItem({required this.item});
@override
_MyListItemState createState() => _MyListItemState();
}
class _MyListItemState extends State<MyListItem> with AutomaticKeepAliveClientMixin {
@override
bool get wantKeepAlive => true;
@override
Widget build(BuildContext context) {
super.build(context); // Don't forget to call super.build().
return ListTile(
title: Text(widget.item.value),
leading: CachedNetworkImage(
imageUrl: widget.item.imageUrl,
placeholder: (context, url) => CircularProgressIndicator(),
errorWidget: (context, url, error) => Icon(Icons.error),
),
);
}
}
그리고 `ListView.builder`에서 `MyListItem`을 사용합니다.
ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
return MyListItem(item: items[index]);
},
)
4. 데이터 모델에 `Equatable` 사용
`Equatable` 패키지를 사용하면 아이템의 상태를 비교하여 변경된 경우에만 다시 빌드할 수 있습니다.
이는 특히 아이템의 상태 관리와 관련하여 유용합니다.
dependencies:
equatable: ^2.0.3
아이템 모델을 `Equatable`을 사용하여 정의합니다.
import 'package:equatable/equatable.dart';
class Item extends Equatable {
final String id;
final String value;
final String imageUrl;
Item({required this.id, required this.value, required this.imageUrl});
@override
List<Object> get props => [id, value, imageUrl];
}
이렇게 하면 `ListView`의 아이템 상태 관리가 더욱 효율적으로 이루어집니다.
5. cacheExtent 사용
cacheExtent 속성을 사용하여 미리 로드할 아이템의 범위를 설정합니다.
다음은 전체적인 코드 예제입니다.
import 'package:flutter/material.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_database/firebase_database.dart';
import 'package:hive/hive.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'package:cached_network_image/cached_network_image.dart';
import 'item_model.dart';
import 'firebase_options.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
await Hive.initFlutter();
Hive.registerAdapter(ItemAdapter());
await Hive.openBox<Item>('itemsBox');
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final DatabaseReference databaseReference = FirebaseDatabase.instance.reference();
Box<Item>? itemsBox;
@override
void initState() {
super.initState();
itemsBox = Hive.box('itemsBox');
syncData();
}
Future<void> syncData() async {
databaseReference.child('items').onValue.listen((event) async {
var snapshot = event.snapshot;
Map<dynamic, dynamic>? items = snapshot.value as Map<dynamic, dynamic>?;
if (items != null) {
await itemsBox!.clear();
items.forEach((key, value) async {
await itemsBox!.put(key, Item(id: key, value: value['value'], imageUrl: value['imageUrl']));
});
}
setState(() {});
});
}
@override
Widget build(BuildContext context) {
List<Item> items = itemsBox!.values.toList().cast<Item>();
return Scaffold(
appBar: AppBar(
title: Text('Flutter Demo Home Page'),
),
body: ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
return MyListItem(item: items[index]);
},
cacheExtent: 1000, // Increase this value to cache more items
),
);
}
}
class MyListItem extends StatefulWidget {
final Item item;
MyListItem({required this.item});
@override
_MyListItemState createState() => _MyListItemState();
}
class _MyListItemState extends State<MyListItem> with AutomaticKeepAliveClientMixin {
@override
bool get wantKeepAlive => true;
@override
Widget build(BuildContext context) {
super.build(context); // Don't forget to call super.build().
return ListTile(
title: Text(widget.item.value),
leading: CachedNetworkImage(
imageUrl: widget.item.imageUrl,
placeholder: (context, url) => CircularProgressIndicator(),
errorWidget: (context, url, error) => Icon(Icons.error),
),
);
}
}
설명
- ListView.builder: 효율적으로 아이템을 렌더링합니다.
- CachedNetworkImage: 이미지를 캐싱하여 네트워크 요청을 줄입니다.
- AutomaticKeepAliveClientMixin: 각 아이템의 상태를 유지합니다.
- Equatable : 특히 아이템의 상태 관리와 관련하여 유용합니다.
- cacheExtent: ListView.builder의 cacheExtent 속성을 사용하여 화면 외부에 있는 아이템을 미리 로드합니다. 예제에서는 1000 픽셀의 범위를 미리 로드하도록 설정했습니다.
이렇게 하면 ListView의 성능이 개선되고, 스크롤 시 아이템이 재로딩되는 현상을 방지할 수 있습니다.
필요에 따라 cacheExtent 값을 조정하여 성능과 메모리 사용량 간의 균형을 맞출 수 있습니다.
이 방법들을 결합하여 적용하면 ListView에서 스크롤 시 아이템이 재로딩되는 문제를 해결할 수 있습니다.
Starting Google Play App Distribution! "Tester Share" for Recruiting 20 Testers for a Closed Test.
'Flutter' 카테고리의 다른 글
플러터에서의 코드 리팩토링: 초보자를 위한 기준점과 방법 (0) | 2024.07.30 |
---|---|
플러터에서 사용할 수 있는 5가지 추천 아이콘 패키지 (0) | 2024.07.30 |
플러터에서 해상도에 따라 위젯 위치 자동 배치하기 [LayoutBuilder / MediaQuery] (0) | 2024.07.26 |
안드로이드 개발자 모드 활성화: 숨겨진 기능 탐험 가이드 (0) | 2024.07.26 |
플러터에서 상속과 믹싱: 객체 지향 프로그래밍의 핵심 (0) | 2024.07.26 |