Flutter에서 쉽게 사용할 수 있는 이미지 확장 위젯: ImageExpandableWidget

길위에 히피·2024년 9월 26일
0

Flutter

목록 보기
38/44

모바일 애플리케이션에서 이미지를 부분적으로 보여주고, 사용자가 클릭하면 확장하여 전체 이미지를 보여주는 기능은 자주 사용됩니다. 이를 효과적으로 구현하려면 이미지의 크기를 계산하고, 레이아웃을 조정하는 복잡한 작업이 필요할 수 있습니다.

이 글에서는 이런 복잡함을 간소화하고, 쉽게 재사용할 수 있는 ImageExpandableWidget을 만드는 방법을 소개하겠습니다. 이 위젯은 이미지를 기본적으로 부분적으로 보여주고, 사용자가 버튼을 클릭하면 전체 이미지를 보여줍니다.

주요 기능

이미지 부분 표시 및 전체 확장: 위젯은 이미지의 일부분만 보여주다가, 확장 시 전체 이미지를 표시합니다.
커스터마이징 가능한 확장 버튼: 확장 버튼의 텍스트, 아이콘, 스타일 등을 커스터마이징할 수 있습니다.
그라데이션 효과: 이미지 하단에 그라데이션을 추가하여 이미지가 부분적으로 가려져 있다는 시각적 힌트를 제공합니다.
확장 버튼 대신 커스터마이징된 위젯 제공: 필요에 따라 기본 제공 확장 버튼 대신, 커스터마이징된 위젯을 사용할 수 있습니다.

ImageExpandableWidget 구현

1. 위젯 코드

import 'package:flutter/material.dart';

class ImageExpandableWidget extends StatefulWidget {
  final String imageUrl; // 더 일반적인 이미지 URL 필드
  final double initialMaxHeight;
  final double gradientHeight;
  final Color gradientColor;
  final String expandText;
  final Widget? expandWidget; // 확장 버튼 대신 사용할 수 있는 커스터마이징된 위젯
  final TextStyle? expandTextStyle;
  final Icon? expandIcon;
  final double imageBorderRadius;

  const ImageExpandableWidget({
    super.key,
    required this.imageUrl, // 이미지 URL만 필요하도록 간소화
    this.initialMaxHeight = 1075,
    this.gradientHeight = 210,
    this.gradientColor = Colors.white,
    this.expandText = "더보기",
    this.expandTextStyle,
    this.expandIcon,
    this.expandWidget,
    this.imageBorderRadius = 6.0,
  });

  @override
  State<ImageExpandableWidget> createState() => _ImageExpandableWidgetState();
}

class _ImageExpandableWidgetState extends State<ImageExpandableWidget> {
  final GlobalKey _imageKey = GlobalKey();
  late double maxHeight;
  late ImageProvider imageProvider;
  bool isExpanded = false; // 확장 여부를 내부 상태로 관리

  @override
  void initState() {
    super.initState();
    maxHeight = widget.initialMaxHeight;
    imageProvider = NetworkImage(widget.imageUrl);
  }

  void _getImageSize() {
    WidgetsBinding.instance.addPostFrameCallback((_) {
      final RenderBox? renderBox =
      _imageKey.currentContext?.findRenderObject() as RenderBox?;
      if (renderBox != null && renderBox.size.height <= maxHeight) {
        setState(() {
          maxHeight = renderBox.size.height;
          isExpanded = true;
        });
      }
    });
  }

  void _toggleExpand() {
    setState(() {
      isExpanded = !isExpanded;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Stack(
          children: [
            ClipRRect(
              borderRadius: BorderRadius.circular(widget.imageBorderRadius),
              child: Container(
                height: isExpanded ? null : maxHeight,
                child: isExpanded
                    ? Image(
                        image: imageProvider,
                        fit: BoxFit.fitWidth,
                        loadingBuilder: (BuildContext context, Widget child,
                            ImageChunkEvent? loadingProgress) {
                          if (loadingProgress == null) {
                            return child;
                          } else {
                            return Center(
                              child: CircularProgressIndicator(
                                value: loadingProgress.expectedTotalBytes != null
                                    ? loadingProgress.cumulativeBytesLoaded /
                                        (loadingProgress.expectedTotalBytes ?? 1)
                                    : null,
                              ),
                            );
                          }
                        },
                      )
                    : OverflowBox(
                        alignment: Alignment.topCenter,
                        maxHeight: double.infinity,
                        child: Image(
                          image: imageProvider,
                          fit: BoxFit.fitWidth,
                          key: _imageKey,
                          loadingBuilder: (BuildContext context, Widget child,
                              ImageChunkEvent? loadingProgress) {
                            if (loadingProgress == null) {
                              _getImageSize();
                              return child;
                            } else {
                              return Center(
                                child: CircularProgressIndicator(
                                  value: loadingProgress.expectedTotalBytes != null
                                      ? loadingProgress.cumulativeBytesLoaded /
                                          (loadingProgress.expectedTotalBytes ?? 1)
                                      : null,
                                ),
                              );
                            }
                          },
                        ),
                      ),
              ),
            ),
            if (!isExpanded)
              Positioned(
                left: 0,
                right: 0,
                bottom: 0,
                child: Container(
                  height: widget.gradientHeight,
                  decoration: BoxDecoration(
                    gradient: LinearGradient(
                      begin: Alignment.topCenter,
                      end: Alignment.bottomCenter,
                      colors: [
                        widget.gradientColor.withOpacity(0.0),
                        widget.gradientColor,
                      ],
                    ),
                  ),
                ),
              ),
          ],
        ),
        if (!isExpanded)
          widget.expandWidget ??
              Padding(
                padding: const EdgeInsets.symmetric(horizontal: 15),
                child: InkWell(
                  onTap: _toggleExpand, // 내부에서 상태 전환
                  child: Container(
                    alignment: Alignment.center,
                    padding: const EdgeInsets.symmetric(vertical: 14),
                    width: double.maxFinite,
                    decoration: BoxDecoration(
                      border: Border.all(color: Colors.grey),
                      borderRadius: BorderRadius.circular(widget.imageBorderRadius),
                    ),
                    child: Row(
                      mainAxisAlignment: MainAxisAlignment.center,
                      children: [
                        Text(
                          widget.expandText,
                          style: widget.expandTextStyle ??
                              Theme.of(context).textTheme.bodyLarge,
                        ),
                        widget.expandIcon ??
                            const Icon(Icons.keyboard_arrow_down_sharp),
                      ],
                    ),
                  ),
                ),
              ),
      ],
    );
  }
}

2. 코드 설명

이미지 부분적으로 표시 및 확장

ImageExpandableWidget은 기본적으로 이미지의 일부분만 보여주고, 사용자가 더보기 버튼을 누르면 전체 이미지를 보여줍니다. 이미지는 OverflowBox를 사용하여 잘리는 부분을 처리하고, 확장되면 원래 크기의 이미지를 표시합니다.

이미지 로드 상태 처리

이미지가 로드 중일 때 로딩 인디케이터를 표시하고, 이미지가 로드된 후에만 표시하는 방식으로 loadingBuilder를 사용합니다.

그라데이션 효과

이미지의 하단에 그라데이션을 추가하여 사용자가 이미지가 확장될 수 있다는 시각적 힌트를 줍니다. 이 그라데이션은 사용자가 원하는 대로 높이와 색상을 조절할 수 있습니다.

확장 버튼 대신 커스터마이징된 위젯 사용 가능

기본 확장 버튼 대신 expandWidget을 통해 사용자가 완전히 커스터마이징된 확장 위젯을 제공할 수 있습니다. 이를 통해 확장 버튼에 대한 완벽한 제어가 가능합니다.

3. 사용 방법

ImageExpandableWidget을 사용하여 간단하게 이미지 확장 기능을 추가할 수 있습니다. 기본 확장 버튼과 스타일을 사용할 수도 있고, 필요에 따라 커스터마이징할 수도 있습니다.

import 'package:flutter/material.dart';
import 'package:your_package/image_expandable_widget.dart';

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: ImageExpandableWidget(
        imageUrl: 'https://example.com/sample-image.jpg',
        initialMaxHeight: 500,
        gradientHeight: 150,
        gradientColor: Colors.black.withOpacity(0.5),
        expandText: "더보기",
        expandTextStyle: TextStyle(color: Colors.blue),
        expandIcon: Icon(Icons.expand_more),
        expandWidget: ElevatedButton(
          onPressed: () {},
          child: Text('더 많은 이미지 보기'),
        ), // 커스터마이징된 확장 위젯 사용 가능
        imageBorderRadius: 8.0,
      ),
    );
  }
}

4. 결론

ImageExpandableWidget을 사용하면 Flutter 앱에서 간편하게 이미지 확장 기능을 구현할 수 있습니다. 사용자는 확장 버튼, 그라데이션, 이미지 모서리 둥글기 등 다양한 옵션을 커스터마이징할 수 있으며, 필요에 따라 기본 제공 확장 버튼 대신 자신만의 위젯을 사용할 수도 있습니다.

이 위젯은 이미지가 잘리는 효과를 제공하며, 확장 후에는 전체 이미지를 보여줍니다. 앱에서 이미지 갤러리나 상세 설명을 보여줄 때 유용하게 사용할 수 있습니다.

profile
마음맘은 히피인 일꾼러

0개의 댓글