배운것들을 정리합니다.
三昧境

Flutter/App Making Learn

[플러터 Flutter] 약관 동의 페이지 만들기2 (접히는 TextBox "ExpansionTile")

ujo_orr 2024. 6. 12. 21:59

위 GIF처럼 터치했을 때 TextBox가 펼쳐지며 내부의 내용을 볼 수 있고 다시 터치하면 접히는 걸 구현해 보겠습니다.

  • Flutter v3.22.2
  • Dart v 3.4.3
  • DevTools 2.34.3

우선 저기 있는 이용약관 자체는 굉장히 길기 때문에 파일을 하나 따로 만들어두도록 하겠습니다.
제 생각에는 지금만 이렇게 주저리주저리 글을 적어놓지 나중에 제대로 된 앱을 만들려면 서버든 DB든 JSON형태로 파싱 해서 텍스트를 쓰지 않으려나.. 하는 추측이 있습니다..

왜냐하면 저는 아직 초보기 때문에 모릅니다..

String agreeConditionText =
    '이용약관 동의 내용\n...';
    
String personalAgreeText =
    '개인정보수집이용 동의 내용\n...';

String thirdAgreeText =
    '개인정보 제 3자 동의 내용\n...';

사실 이렇게 작은 문자열들을 넣을 경우에는 굳이 따로 리팩터링 하여 파일을 따로 만들 필요는 없으나..
언젠간 MVVM이나 cleanArchitecture 같은 것도 도전해보고 싶어서 조금이나마 습관화하려고 일부러 파일을 나누고 있습니다.


ExpansionTile

ExpansionTile(
 title: Text("내용보기"),
 children: [
  SizedBox(
   height: 400,
   child: SingleChildScrollView(
    child: Text(
     agreeConditionText,
     style: TextStyle(color: Colors.black54),
    ),
   ),
  )
 ],
)

ExpansionTile은 title을 필수로 받고 있습니다.
여기서 title이라 함은

title : Text("내용보기"),
처럼 커버제목을 뜻합니다.

제목을 입력하고 하위로 children을 생성하여
"SizeBox"를 높이 400으로 맞춰줍니다.
만약 높이를 400으로 맞춰주지 않는다면

이러한 형태처럼 텍스트의 길이만큼 한정 없이 늘어납니다.
그렇게 되면 사용감에 불편을 야기할 수 있겠다 생각이 들어 높이를 400으로 고정해준 뒤
이 "SizedBox"에서만 해당하는 "SingleChildScrollView"를 부여해 주어 스크롤이 가능하게 해 줍니다.

그리고

Text내부에다 아까 String으로 선언해 두었던 그 긴 문장들을 데려오도록 합니다.
만약 그 긴 문장을 Text("제 1조 어쩌구 저쩌구...") 이런 식으로 길어졌으면 코드길이가 길어져 가독성이 안 좋아졌을 겁니다.


ExpansionTile의 속성

 

backgroundColor : Colors.blue,

title을 포함한 색상을 변경합니다.

 


 

shape: OutlineInputBorder(
    borderRadius: BorderRadius.circular(20)),

모서리의 둥근 정도를 변경합니다.


leading: Icon(CupertinoIcons.doc),

왼쪽 부분의 아이콘이나 위젯을 넣을 수 있습니다.
활성화 시 현재 적용되어 있는 Theme의 색상으로 표기됩니다.


trailing: Icon(CupertinoIcons.doc),

오른쪽에 있는 화살표의 아이콘 및 위젯을 변경합니다.
리딩과 동일하게 활성화 시 현재 Theme의 색상으로 표기됩니다.

 


 

 

childrenPadding: EdgeInsets.all(50),

내부 Text에 Padding을 적용시킵니다.

 


 

 

subtitle: Text("data"),

서브타이틀을 추가합니다.

 

등등 여러 가지 속성을 가지고 있으니 한 번씩 써보면서 적용시켜도 좋을 것 같지만
기본적으로 제공해주고 있는 것만으로도 충분히 가독성 좋은 ExpansionTile이라 굳이 많은 속성들을 넣지 않아도 될 것 같습니다.

이상 아래로는 최종코드 올리면서 마무리하도록 하겠습니다.

블로그에 글을 쓰는 게 목적이 아닌지라 이래저래 공부한다고 엄청 잡다한 코드가 많이 들어가 있으니 그냥 그렇구나 하고 넘어가시면 좋을 것 같습니다..

// agreement.dart

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:social_login_namap/sign_in.dart';

import 'agreement_condition_text.dart';
import 'login_page.dart';

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

  @override
  State<Agreement> createState() => AgreementState();
}

class AgreementState extends State<Agreement> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.white,
      body: SafeArea(
        child: Padding(
          padding: const EdgeInsets.all(40.0),
          child: SingleChildScrollView(
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Title(
                  color: Colors.black,
                  child: Text(
                    "약관 동의",
                    style: TextStyle(
                      fontWeight: FontWeight.bold,
                      fontSize: 25,
                    ),
                  ),
                ),
                Divider(
                  color: Colors.black,
                ),
                Row(
                  mainAxisAlignment: MainAxisAlignment.spaceBetween,
                  children: [
                    Text(
                      "회원가입 약관에 모두 동의합니다",
                      style: TextStyle(fontSize: 15),
                    ),
                    CupertinoSwitch(
                      value: isAllChecked,
                      onChanged: (bool value) {
                        setState(() {
                          isAllChecked = value;
                          isConditionChecked = value;
                          isInfoChecked = value;
                          isThirdChecked = value;
                          isRequireChecked = value;
                        });
                      },
                    )
                  ],
                ),
                Divider(),
                Row(
                  children: [
                    Text(
                      "이용약관 동의",
                      style: TextStyle(fontSize: 15),
                    ),
                    Text(
                      "(필수)",
                      style: TextStyle(fontSize: 15, color: Colors.red),
                    ),
                    Spacer(),
                    CupertinoSwitch(
                      value: isConditionChecked,
                      onChanged: (bool value) {
                        setState(() {
                          isConditionChecked = value;
                          updateRequireChecked();
                          updateAllChecked();
                        });
                      },
                    ),
                  ],
                ),
                ExpansionTile(
                  subtitle: Text("data"),
                  title: Text("내용보기"),
                  children: [
                    SizedBox(
                      height: 400,
                      child: SingleChildScrollView(
                        child: Text(
                          agreeConditionText,
                          style: TextStyle(color: Colors.black54),
                        ),
                      ),
                    )
                  ],
                ),
                Divider(),
                Row(
                  children: [
                    Text(
                      "개인정보 수집 및 이용 동의",
                      style: TextStyle(fontSize: 15),
                    ),
                    Text(
                      "(필수)",
                      style: TextStyle(fontSize: 15, color: Colors.red),
                    ),
                    Spacer(),
                    CupertinoSwitch(
                      value: isInfoChecked,
                      onChanged: (bool value) {
                        setState(() {
                          isInfoChecked = value;
                          updateRequireChecked();
                          updateAllChecked();
                        });
                      },
                    ),
                  ],
                ),
                ExpansionTile(
                  title: Text("내용보기"),
                  children: [
                    SizedBox(
                      height: 400,
                      child: SingleChildScrollView(
                        child: Text(
                          personalAgreeText,
                          style: TextStyle(color: Colors.black54),
                        ),
                      ),
                    )
                  ],
                ),
                Divider(),
                Row(
                  children: [
                    Text(
                      "개인정보 제3자 제공 동의",
                      style: TextStyle(fontSize: 15),
                    ),
                    Text(
                      "(선택)",
                      style: TextStyle(fontSize: 15, color: Colors.grey),
                    ),
                    Spacer(),
                    CupertinoSwitch(
                      value: isThirdChecked,
                      onChanged: (bool value) {
                        setState(() {
                          isThirdChecked = value;
                          updateAllChecked();
                        });
                      },
                    ),
                  ],
                ),
                ExpansionTile(
                  title: Text("내용보기"),
                  children: [
                    SizedBox(
                      child: SingleChildScrollView(
                        child: Text(
                          thirdAgreeText,
                          style: TextStyle(color: Colors.black54),
                        ),
                      ),
                    )
                  ],
                ),
                SizedBox(height: 10),
                Row(
                  mainAxisAlignment: MainAxisAlignment.spaceAround,
                  children: [
                    Expanded(
                      child: ElevatedButton(
                        onPressed: () {
                          resetState();
                          Navigator.pushAndRemoveUntil(
                            context,
                            MaterialPageRoute(
                              builder: (context) => LoginPage(),
                            ),
                            (Route<dynamic> route) => false,
                          );
                        },
                        style: ButtonStyle(
                            overlayColor:
                                WidgetStatePropertyAll(Colors.grey[200]),
                            backgroundColor:
                                WidgetStatePropertyAll(Colors.white)),
                        child:
                            Text("취소", style: TextStyle(color: Colors.black)),
                      ),
                    ),
                    SizedBox(width: 10),
                    Expanded(
                      child: ElevatedButton(
                        onPressed: isRequireChecked
                            ? () {
                                Navigator.push(
                                  context,
                                  MaterialPageRoute(
                                      builder: (context) => SignIn()),
                                );
                              }
                            : null,
                        style: ButtonStyle(
                            overlayColor:
                                WidgetStatePropertyAll(Colors.indigo[600]),
                            backgroundColor: isRequireChecked
                                ? WidgetStatePropertyAll(Colors.indigo[900])
                                : WidgetStatePropertyAll(Colors.grey)),
                        child:
                            Text("확인", style: TextStyle(color: Colors.white)),
                      ),
                    ),
                  ],
                ),
              ],
            ),
          ),
        ),
      ),
    );
  }
}