확장 메서드는 기존 라이브러리에 기능을 추가하는 방법

https://dart.dev/guides/language/extension-methods

 

Extension methods

Learn how to add to existing APIs.

dart.dev

lib/string_extension.dart

// string 객체 활장 클래스 파일

import 'package:intl/intl.dart';

extension StringExtension on String {
  String numberFormat() {
    final formatter = NumberFormat("#,###");
    return formatter.format(int.parse(this));
  }
}

확장 메서드 사용 방법 예시

import 'string_extension.dart';

print('5000'.numberFormat());
// 5,000

print('1000000'.numberFormat());
// 1,000,000

String 객체 뒤에 . 을 표시하고 정의한 확장 메서드인 numberFormat() 함수를 사용하면 됨

728x90

1. 스택으로 밑에 깔릴 이미지와 버튼아이콘 추가

2. positioned로 배경이미지 위에 올릴 아이콘 위치 고정

Stack(
  children: [
    SizedBox(
      width: 65,
      height: 65,
      child: ClipRRect(
        borderRadius: BorderRadius.circular(32.5),
        child: Image.network(
          'https://placeimg.com/200/100/people',
          fit: BoxFit.cover,
        ),
      ),
    ),
    Positioned(
      bottom: 0,
      right: 0,
      child: Container(
        width: 20,
        height: 20,
        decoration: BoxDecoration(
          borderRadius: BorderRadius.circular(15),
          color: Colors.grey[100],
        ),
        child: Icon(
          Icons.camera_alt_outlined,
          size: 15,
        ),
      ),
    ),
  ],
)

 

728x90

indexedStack 사용법

  1. class 내에서 int _selectedIndex = 0; 선언
  2. index: _selectedIndex, 으로 보여줄 children 세팅
  3. bottomNavigation의 ontap으로 클릭시 보여줄 index 설정
class MainScreens extends StatefulWidget {
  const MainScreens({Key? key}) : super(key: key);

  @override
  _MainScreensState createState() => _MainScreensState();
}

class _MainScreensState extends State<MainScreens> {
  int _selectedIndex = 0;
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: IndexedStack(
        index: _selectedIndex,
        children: [
          Container(
            color: Colors.orange[100],
            child: Center(
              child: Text(
                'IndexedStack 1',
                style: TextStyle(fontSize: 20, color: Colors.black),
              ),
            ),
          ),
          Container(
            color: Colors.redAccent[100],
            child: Center(
              child: Text(
                'IndexedStack 2',
                style: TextStyle(fontSize: 20, color: Colors.black),
              ),
            ),
          )
        ],
      ),
      bottomNavigationBar: BottomNavigationBar(
        items: [
          BottomNavigationBarItem(
            icon: Icon(CupertinoIcons.home),
            label: '홈',
          ),
          BottomNavigationBarItem(
            icon: Icon(CupertinoIcons.chat_bubble),
            label: '채팅',
          )
        ],
        onTap: (index) {
          setState(() {
            _selectedIndex = index;
          });
        },
        currentIndex: _selectedIndex,
      ),
    );
  }
}
728x90

SingleGetTickerProviderMixin 를 mixin한 GetxController를 상속받은 컨트롤러 만들기

animation을 사용하기위해 SingleGetTickerProviderMixin를 가져옴

class TextAnimation extends GetxController with SingleGetTickerProviderMixin {
  Map<String, String?>? post;

  AnimationController? animationController;
  Animation<Offset>? animationOffset;

  @override
  void onInit() {
    super.onInit();
    animationController = AnimationController(
      vsync: this,
      duration: const Duration(milliseconds: 1000),
    );
    final curve =
        CurvedAnimation(parent: animationController!, curve: Curves.bounceIn);
    animationOffset = Tween<Offset>(
            begin: const Offset(0.0, -1.0), end: const Offset(0.0, 0.0))
        .animate(curve);
    animationController!.repeat();
    _loadData();
  }

  void _loadData() {
    post = Get.parameters;
  }
}

실제 사용

아래와 같이 statefull에서 사용하던 방법처럼 animation 부분에 controller에서 만든 animation을 가져오면 됨

class PostDetailView extends GetView<TextAnimation> {
  const PostDetailView();

  @override
  Widget build(BuildContext context) {
    Get.put(TextAnimation());
    return Scaffold(
      appBar: AppBar(
        title: Text(controller.post!['title']!),
      ),
      body: Column(
        children: [
          Hero(
              tag: controller.post!['uid']!,
              child: Image.asset(controller.post!['thumbnail']!)),
          Column(
            children: [
            // 아래와 같이 controller에서 애니메이션을 가져오면 됨
              SlideTransition(
                position: controller.animationOffset!,
                child: Text(
                  controller.post!['title']!,
                ),
              ),
              Text(
                controller.post!['description']!,
              ),
            ],
          ),
        ],
      ),
    );
  }
}
728x90

2021.07.01 - [Usage/Flutter] - [Flutter] Json 데이터를 GetX를 활용하여 list view, detail view 만들기

 

[Flutter] Json 데이터를 GetX를 활용하여 list view, detail view 만들기

Json 데이터 예시 [ { "uid" : 1, "thumbnail": "assets/images/1.jpg", "title" : "꼬북좌 이미지1", "description" : "남심 '저격'브레이브걸스 유정 근황" }, { "uid" : 2, "thumbnail": "assets/images/2.jpg"..

mugon-devlog.tistory.com

Hero 애니메이션 적용

Hero 애니메이션을 적용하기 위해서는 적용받는 대상의 부모와 자식 위젯의 태그를 동일하게 만들어줘야함

아래는 디테일 페이지로 이동하기위해 클릭하는 위젯

아래 위젯의 Image를 클릭하면 디테일 페이지로 이동하는데 이때의 이미지와 디테일 페이지의 이미지의 태그를 동일하게 만들어줘야함

class PostWidget extends StatelessWidget {
  final String uid;
  final String thumbnail;
  final String title;
  final String description;
  final PostClickFunction callBack;

  const PostWidget(
      {required this.uid,
      required this.thumbnail,
      required this.title,
      required this.description,
      required this.callBack});

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: callBack,
      child: Container(
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            // tag가 연결되는 이미지의 tag와 같아야함
            // 리스트의 이미지의 태그와 디테일 페이지의 이미지의 태그가 동일해야함
            Hero(tag: uid, child: Image.asset(thumbnail)),
            Padding(
              padding: const EdgeInsets.only(
                  top: 10, bottom: 20, left: 10, right: 10),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.stretch,
                children: [
                  Text(
                    title,
                    style: TextStyle(fontSize: 15, fontWeight: FontWeight.bold),
                  ),
                  Text(
                    description,
                    style: TextStyle(
                      fontSize: 12,
                    ),
                  )
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }
}

아래는 디테일 페이지

class PostDetailView extends GetView<TextAnimation> {
  const PostDetailView();

  @override
  Widget build(BuildContext context) {
    Get.put(TextAnimation());
    return Scaffold(
      appBar: AppBar(
        title: Text(controller.post!['title']!),
      ),
      body: Column(
        children: [
          Hero(
              tag: controller.post!['uid']!,
              child: Image.asset(controller.post!['thumbnail']!)),
          Column(
            children: [
              Text(
                controller.post!['title']!,
              ),
              Text(
                controller.post!['description']!,
              ),
            ],
          ),
        ],
      ),
    );
  }
}
728x90

Json 데이터 예시

[
    {
        "uid" : 1,
        "thumbnail": "assets/images/1.jpg",
        "title" : "꼬북좌 이미지1",
        "description" : "남심 '저격'브레이브걸스 유정 근황"
    },
    {
        "uid" : 2,
        "thumbnail": "assets/images/2.jpg",
        "title" : "꼬북좌 이미지2",
        "description" : "브레이브걸스 꼬북좌 유정 사진짤 방출"
    },
    {
        "uid" : 3,
        "thumbnail": "assets/images/3.jpg",
        "title" : "꼬북좌 이미지3",
        "description" : "예상치 못한 인스타에 팬들 반응 난리났다."
    },
    {
        "uid" : 4,
        "thumbnail": "assets/images/4.jpg",
        "title" : "꼬북좌 이미지4",
        "description" : "꼬북좌 인스타 이미지"
    },
    {
        "uid" : 5,
        "thumbnail": "assets/images/5.jpg",
        "title" : "진짜 꼬부기",
        "description" : "진짜 꼬부기 이미지"
    }
]

GetxController를 상속 받아 Json 데이터를 가져오는 컨트롤러

class JsonLoader extends GetxController {
  static JsonLoader get to => Get.find();
  var list = <Map<String, dynamic>>[].obs;
  @override
  void onInit() async {
    super.onInit();
    _loadJsonFile();
  }

  void _loadJsonFile() async {
    if (Get.context != null) {
      String data = await DefaultAssetBundle.of(Get.context!)
          .loadString("assets/json/post.json");
      list(json.decode(data).cast<Map<String, dynamic>>().toList());
    } else {
      Future.delayed(Duration(milliseconds: 200), _loadJsonFile);
    }
  }
}

SingleChildScrollView로 GetX의 Obx로 Json 데이터 리스트 위젯 만들기

SingleChildScrollView(
  child: Obx(
    () => Column(
      children: List.generate(
        JsonLoader.to.list.length,
        (index) {
        // 부모에서 JsonLoader binding
        // 컨트롤러에서 만든 리스트를 post에 저장
          var post = JsonLoader.to.list[index]
              .map<String, String>((key, value) {
            return MapEntry(key.toString(), value.toString());
          });
          return PostWidget(
            uid: post['uid']!,
            thumbnail: post['thumbnail']!,
            title: post['title']!,
            description: post['description']!,
            callBack: () {
            // detail 페이지로 이동시 파라미터로 해당 위젯의 post 넘겨줌
              Get.toNamed('/detail', parameters: post);
            },
          );
        },
      ),
    ),
  ),
)

리스트의 아이템 만들기

typedef PostClickFunction = void Function();

class PostWidget extends StatelessWidget {
  final String uid;
  final String thumbnail;
  final String title;
  final String description;
  final PostClickFunction callBack;

  const PostWidget(
      {required this.uid,
      required this.thumbnail,
      required this.title,
      required this.description,
      required this.callBack});

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: callBack,
      child: Container(
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            Image.asset(thumbnail),
            Padding(
              padding: const EdgeInsets.only(
                  top: 10, bottom: 20, left: 10, right: 10),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.stretch,
                children: [
                  Text(
                    title,
                    style: TextStyle(fontSize: 15, fontWeight: FontWeight.bold),
                  ),
                  Text(
                    description,
                    style: TextStyle(
                      fontSize: 12,
                    ),
                  )
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }
}

callback을 통해 디테일 페이지로 이동

리스트 위젯의 callback 부분에 Get.toNamed로 최상단에 지정한 라우팅 주소 넣고, 파라미터로 데이터 넘겨주기

GetxController를 통해 파라미터 받아오기

class TextAnimation extends GetxController {
  Map<String, String?>? post;

  @override
  void onInit() {
    super.onInit();
    _loadData();
  }

  void _loadData() {
    post = Get.parameters;
  }
}

위에서 만든 GetxController 형식의 GetView를 상속받아 파라미터의 데이터 가져오기

class PostDetailView extends GetView<TextAnimation> {
  const PostDetailView();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(controller.post!['title']!),
      ),
      body: Column(
        children: [
          Image.asset(controller.post!['thumbnail']!),
          Column(
            children: [
              Text(
                controller.post!['title']!,
              ),
              Text(
                controller.post!['description']!,
              ),
            ],
          ),
        ],
      ),
    );
  }
}
728x90

+ Recent posts