본문 바로가기

Flutter/12 Clone 'Used Goods app'

[Flutter] Clone - 당근마켓6(PageView - address page)

이번에는 PageView 의 내부에 address page 를 추가해보겠습니다.

개발환경 : 윈도우11, 안드로이드 스튜디오, flutter 3.0.1

 

 

 

화면 구성은 아래와 같습니다. 자세한 기능은 모델링도 필요하여 차후에 구현하겠습니다.

상단부터 TextFormField, TextButton.icon, Expanded&ListView,builder&ListTile 로 구성되어 있습니다.

 

 

 

 

주요 파일은 아래와 같습니다.

./src/screens/auth_screen.dart

./src/screens/start/intro_page.dart

./src/screens/start/address_page.dart

 

 

 

./src/screens/auth_screen.dart

 

import 'package:flutter/material.dart';

import 'start/address_page.dart';
import 'start/intro_page.dart';

class AuthScreen extends StatelessWidget {
  AuthScreen({Key? key}) : super(key: key);

  final PageController _pageController = PageController();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: PageView(
        controller: _pageController,
        // 이부분이 활성화 되면 사용자가 화면을 좌/우로 스크롤하지 못하게 설정 가능
        // physics: const NeverScrollableScrollPhysics(),
        children: <Widget>[
          IntroPage(pageController: _pageController),
          const AddressPage(), // 새로 추가된 화면을 호출하는 부분
          Container(color: Colors.accents[4]),
        ],
      ),
    );
  }
}

 

 

./src/screens/start/intro_page.dart - 약간의 화면수정과 PageController 를 인자로 전달받기 위해 수정함

 

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

import '../../constants/common_size.dart';
import '../../utils/logger.dart';

class IntroPage extends StatelessWidget {
  final PageController pageController;

  const IntroPage({Key? key, required this.pageController}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    FocusScope.of(context).unfocus();

    // 모바일 화면의 비율을 정할때 편한 위젯
    return LayoutBuilder(
      builder: (context, constraints) {
        Size size = MediaQuery.of(context).size;

        final imgSize = size.width - padding_16 * 2;
        final sizeOfPosImg = imgSize * 0.1;

        return SafeArea(
          // 상태바 아래부터 , 하단 버튼위로 위젯이 위치시킴
          child: Padding(
            padding: const EdgeInsets.symmetric(horizontal: padding_16),
            child: Column(
              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
              children: <Widget>[
                Text(
                  'Apple Market',
                  style: Theme.of(context)
                      .textTheme
                      .headline3!
                      .copyWith(color: Theme.of(context).colorScheme.primary),
                ),
                SizedBox(
                  width: imgSize,
                  height: imgSize,
                  child: Stack(
                    children: <Widget>[
                      ExtendedImage.asset('assets/imgs/carrot_intro.png'),
                      // Stack 안에서만 사용 가능함
                      Positioned(
                          // 가로 세로 길이 설정, 사이즈 설정
                          width: sizeOfPosImg,
                          height: sizeOfPosImg,
                          // 왼쪽과 위쪽의 간격 설정
                          left: imgSize * 0.45,
                          top: imgSize * 0.45,
                          child: ExtendedImage.asset(
                              'assets/imgs/carrot_intro_pos.png')),
                    ],
                  ),
                ),
                Text(
                  '우리 동네 중고 직거래 사과마켓',
                  style: Theme.of(context).textTheme.headline6,
                ),
                Text(
                  '사과 마켓은 동네 직거래 마켓이에요\n'
                  '내 동네를 설정하고 시작해보세요',
                  style: Theme.of(context).textTheme.subtitle1,
                ),
                Column(
                  // 버튼을 글씨에 맞추지않고 최대한 늘리는 설정
                  crossAxisAlignment: CrossAxisAlignment.stretch,
                  children: [
                    Padding(
                      padding:
                          const EdgeInsets.symmetric(horizontal: padding_16),
                      child: TextButton(
                        onPressed: () async {
                          logger.d('on Intro page, text Button Clicked !!!');
                          pageController.animateToPage(1,
                              duration: const Duration(milliseconds: 500),
                              curve: Curves.ease);
                        },
                        style: TextButton.styleFrom(
                            backgroundColor: Theme.of(context).primaryColor),
                        child: Text(
                          '내 동네 설정하고 시작하기',
                          style: Theme.of(context).textTheme.button,
                        ),
                      ),
                    ),
                  ],
                ),
              ],
            ),
          ),
        );
      },
    );
  }
}

 

 

./src/screens/start/address_page.dart

 

import 'package:extended_image/extended_image.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';

import '../../constants/common_size.dart';
import '../../utils/logger.dart';

class AddressPage extends StatefulWidget {
  const AddressPage({Key? key}) : super(key: key);

  @override
  State<AddressPage> createState() => _AddressPageState();
}

class _AddressPageState extends State<AddressPage> {
  final TextEditingController _addressController = TextEditingController();

  final _isGettingLocation = false;

  @override
  void dispose() {
    _addressController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    logger.d("AddressPage >> build");

    return SafeArea(
      // padding 대신에 minimum 으로 설정 가능, 위/아래 글씨가 잘리는것도 방지하자
      minimum: const EdgeInsets.symmetric(horizontal: padding_16),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.stretch,
        children: <Widget>[
          TextFormField(
            controller: _addressController,
            onFieldSubmitted: onClickTextField,
            decoration: InputDecoration(
              // icon 으로도 가능하지만 여기서는 prefixIcon 으로 설정함
              prefixIcon: const Icon(
                Icons.search,
                color: Colors.grey,
              ),
              // 아이콘 주변 공간을 조절 가능
              prefixIconConstraints:
                  const BoxConstraints(minWidth: 24, maxHeight: 24),
              // 문자 입력후 밑줄이 생기게 설정
              border: const UnderlineInputBorder(
                  borderSide: BorderSide(color: Colors.grey)),
              // focusedBorder: const UnderlineInputBorder(borderSide: BorderSide(color: Colors.grey)),
              hintText: '도로명으로 검색...',
              hintStyle: TextStyle(color: Theme.of(context).hintColor),
            ),
          ),
          const SizedBox(height: padding_08),
          TextButton.icon(
            label: Text(
              _isGettingLocation ? '위치 찾는중 ~~' : '현재위치 찾기',
              style: Theme.of(context).textTheme.button,
            ),
            onPressed: () {},
            icon: _isGettingLocation
                ? const SizedBox(
                    height: 20,
                    width: 20,
                    child: CircularProgressIndicator(color: Colors.white))
                : const Icon(CupertinoIcons.compass,
                    color: Colors.white, size: 20),
          ),
          Expanded(
            child: ListView.builder(
              padding: const EdgeInsets.symmetric(vertical: padding_16),
              itemCount: 30,
              itemBuilder: (context, index) {
                debugPrint('index: $index');
                return ListTile(
                  leading: const Icon(Icons.image_aspect_ratio),
                  trailing: ExtendedImage.asset('assets/imgs/apple.jpg'),
                  title: Text('address $index'),
                  subtitle: Text('address $index'),
                );
              },
            ),
          ),
        ],
      ),
    );
  }

  void onClickTextField(text) async {
    setState(() {});
  }

}