Flutter (20) Calling UI

Huisu·2023년 3월 11일
0

Flutter

목록 보기
20/21
post-thumbnail

FirstOnboarding

  • 앱을 처음 시작했을 때 보이는 온보딩 페이지의 디자인은 다음과 같다
  • 먼저 새로운 플러터 프로젝트를 시작한다
  • 첫 페이지의 회원가입 부분을 FirstOnboardind 페이지로 지정하고 이를 stateless widget으로 만든다
  • scaffold의 body를 container로 반환하고 그 안에 세로인 column으로 위젯들을 엮어서 리턴
  • Text 위젯으로 리플로우 로고 띄우기
  • Icon 위젯으로 회원가입 전에 보이는 사용자 프로필 그리기
  • Textbutton 위젯으로 회원가입 버튼 만들기
    • onpressed 안에는 버튼을 눌렀을 때 어떤 행동을 취할 것인지 써야 하는데, 이는 추후에 다른 페이지를 만들고 네비게이터 추가
  • 구현 화면
  • firstonboarding.dart
    import 'package:callingapp/onboarding/second.dart';
    import 'package:flutter/material.dart';
    
    class FirstOnboarding extends StatelessWidget {
      const FirstOnboarding({Key? key}) : super(key: key);
    
      
      Widget build(BuildContext context) {
        return Scaffold(
          body: Container(
            alignment: Alignment.center,
            padding: EdgeInsets.symmetric(vertical: 100),
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                Text(
                  "LIFLOW",
                  style: TextStyle(
                    fontWeight: FontWeight.w900,
                    fontSize: 50,
                    color: Colors.indigo
                  ),
                ),
                Text("\n\n"),
                Icon(Icons.person, size: 150,),
                Text("\n\n"),
                TextButton(
                    onPressed: () {
                      Navigator.push(context, MaterialPageRoute(builder: (context) => SecondOnboarding()));
                    },
                    child: Text(
                      "회원가입",
                      style: TextStyle(
                        fontWeight: FontWeight.w500,
                        fontSize: 25,
                        color: Colors.blue
                      ),
                    ),)
              ],
            ),
          ),
        );
      }
    }
  • 의문점 회원가입 버튼만 있고 왜 로그인 버튼 없는지 회원가입 후에 무슨 정보를 입력하고 어떤 회원가입을 진행하는지

SecondOnboarding

  • 리플로우의 말벗 찾기 기능으로 옮겨질 메뉴판들이 보이는 페이지를 구현하기 위해 새로운 페이지 생성

  • 첫 번째 온보딩 페이지에서 회원가입 누르면 해당 페이지로 이동할 수 있도록 네비게이터 생성하기

  • Container에 Column으로 엮어서 위젯 그리기

    • 현재 모든 버튼을 누르면 (onPressed) SecondOnboarding 페이지로 이동하게 되어 있지만 추후에 페이지를 추가로 구성하면 버튼에 맞는 페이지로 이동할 수 있도록 수정
  • secondonboarding.dart

    import 'package:flutter/material.dart';
    
    class SecondOnboarding extends StatefulWidget {
      const SecondOnboarding({Key? key}) : super(key: key);
    
      
      State<SecondOnboarding> createState() => _SecondOnboardingState();
    }
    
    class _SecondOnboardingState extends State<SecondOnboarding> {
      
      Widget build(BuildContext context) {
        return Scaffold(
          body: Container(
            alignment: Alignment.center,
            padding: EdgeInsets.symmetric(vertical: 100),
            child: Column(
              mainAxisAlignment: MainAxisAlignment.start,
              children: <Widget>[
                Text(
                  "LIFLOW",
                  style: TextStyle(
                      fontWeight: FontWeight.w900,
                      fontSize: 50,
                      color: Colors.indigo
                  ),
                ),
                Text("\n\n"),
                Container(
                  width: 350,
                  height: 200,
                  margin: const EdgeInsets.all(10.0),
                  padding: const EdgeInsets.all(10.0),
                  decoration: BoxDecoration(
                    border: Border.all(
                      width: 3,
                      color: Colors.indigo,
                    ),
                    borderRadius: BorderRadius.circular(10.0),
                  ),
                  child: TextButton(
                    onPressed: () {
                      Navigator.push(context, MaterialPageRoute(builder: (context) => SecondOnboarding()));
                    },
                    child: Text(
                      "말벗 찾기",
                      style: TextStyle(
                          fontWeight: FontWeight.w600,
                          fontSize: 25,
                          color: Colors.indigo
                      ),
                    ),
                  ),
                ),
                Container(
                  width: 350,
                  height: 100,
                  alignment: Alignment.center,
                  margin: const EdgeInsets.all(10.0),
                  padding: const EdgeInsets.all(10.0),
                  decoration: BoxDecoration(
                    border: Border.all(
                      width: 3,
                      color: Colors.indigo,
                    ),
                    borderRadius: BorderRadius.circular(10.0),
                  ),
                  child: TextButton(
                    onPressed: () {
                      Navigator.push(context, MaterialPageRoute(builder: (context) => SecondOnboarding()));
                    },
                    child: Text(
                      "즐겨찾기",
                      style: TextStyle(
                          fontWeight: FontWeight.w600,
                          fontSize: 25,
                          color: Colors.indigo
                      ),
                    ),
                  ),
                ),
                Container(
                  width: 350,
                  height: 100,
                  alignment: Alignment.center,
                  margin: const EdgeInsets.all(10.0),
                  padding: const EdgeInsets.all(10.0),
                  decoration: BoxDecoration(
                    border: Border.all(
                      width: 3,
                      color: Colors.indigo,
                    ),
                    borderRadius: BorderRadius.circular(10.0),
                  ),
                  child: TextButton(
                    onPressed: () {
                      Navigator.push(context, MaterialPageRoute(builder: (context) => SecondOnboarding()));
                    },
                    child: Text(
                      "통계",
                      style: TextStyle(
                          fontWeight: FontWeight.w600,
                          fontSize: 25,
                          color: Colors.indigo
                      ),
                    ),
                  ),
                ),
              ],
            ),
          ),
        );
      }
    }
  • 구현 화면

    • 의문점 카드 크기를 통일하는 게 낫지 않나?

FindingPage

  • 말벗 찾기 기능을 이용하기 위해 finding page 생성

  • 키워드를 받아서 container에 실어 카드 형태로 반환하는 위젯 생성하기

    • 해당 그림에 나타나는 부분
    • String 형태의 keyword parameter를 받아서 해당 키워드를 텍스트로 써서 반환하는 keywordcard 위젯 생성
    • width, length, padding, alignment 를 통해 카드 사이즈와 정렬을 재구성
    • border의 박스 데코레이션을 통해 둥글둥글한 형태로 반환
    • 추후에 onpressed 버튼을 누르면 버튼 모양이 바뀌도록 코드 수정 예정
  • 키워드 카드를 row로 두 개 엮은 행을 column으로 엮어서 격자 모양으로 보일 수 있도록 그리기

  • findingpage.dart

    import 'package:flutter/material.dart';
    
    class FindingPage extends StatefulWidget {
      const FindingPage({Key? key}) : super(key: key);
    
      
      State<FindingPage> createState() => _FindingPageState();
    }
    
    class _FindingPageState extends State<FindingPage> {
      
      Widget build(BuildContext context) {
        return Scaffold(
          body: Container(
            alignment: Alignment.center,
            padding: EdgeInsets.symmetric(vertical: 100),
            child: Column(
              mainAxisAlignment: MainAxisAlignment.start,
              children: <Widget>[
                Text(
                  "오늘의 관심사 (중복 가능)                    ",
                  style: TextStyle(
                      fontWeight: FontWeight.w700,
                      fontSize: 23,
                      color: Colors.black
                  ),
                ),
                Column(
                  children: <Widget>[
                    Row(
                      mainAxisAlignment: MainAxisAlignment.center,
                      children: <Widget>[
                        keywordcard(keyword: '경제',),
                        keywordcard(keyword: '건강',),
                      ],
                    ),
                    Row(
                      mainAxisAlignment: MainAxisAlignment.center,
                      children: <Widget>[
                        keywordcard(keyword: '문화',),
                        keywordcard(keyword: '여가',),
                      ],
                    ),
                    keywordcard(keyword: '종교')
                  ],
                ),
                Text('\n'),
                Text(
                  "오늘의 관심사 (중복 가능)                    ",
                  style: TextStyle(
                      fontWeight: FontWeight.w700,
                      fontSize: 23,
                      color: Colors.black
                  ),
                ),
                Column(
                  children: <Widget>[
                    Row(
                      mainAxisAlignment: MainAxisAlignment.center,
                      children: <Widget>[
                        keywordcard(keyword: '조용한',),
                        keywordcard(keyword: '외향적인',),
                      ],
                    ),
                    Row(
                      mainAxisAlignment: MainAxisAlignment.center,
                      children: <Widget>[
                        keywordcard(keyword: '밝은',),
                        keywordcard(keyword: '재미있는',),
                      ],
                    ),
                    Row(
                      mainAxisAlignment: MainAxisAlignment.center,
                      children: <Widget>[
                        keywordcard(keyword: '차분한',),
                        keywordcard(keyword: '믿음직한',),
                      ],
                    ),
                  ],
                ),
                Container(
                  width: 185,
                  height: 65,
                  alignment: Alignment.center,
                  margin: const EdgeInsets.all(3.0),
                  padding: const EdgeInsets.all(8.0),
                  decoration: BoxDecoration(
                    color: Colors.indigo,
                    borderRadius: BorderRadius.circular(10.0),
                  ),
                  child: TextButton(
                    onPressed: () {
                    },
                    child: Text(
                      "등록 완료하기",
                      style: TextStyle(
                          fontWeight: FontWeight.w600,
                          fontSize: 20,
                          color: Colors.white
                      ),
                    ),
                  ),
                )
              ],
            ),
          ),
        );
      }
    }
    
    class keywordcard extends StatelessWidget {
      final String keyword;
    
      const keywordcard({
        Key? key,
        required this.keyword,
      }) : super(key: key);
    
      
      Widget build(BuildContext context) {
        return Container(
          width: 185,
          height: 67,
          alignment: Alignment.center,
          margin: const EdgeInsets.all(2.0),
          padding: const EdgeInsets.all(10.0),
          decoration: BoxDecoration(
            border: Border.all(
              width: 3,
              color: Colors.indigo,
            ),
            borderRadius: BorderRadius.circular(30.0),
          ),
          child: TextButton(
            onPressed: () {
            },
            child: Text(
              keyword,
              style: TextStyle(
                  fontWeight: FontWeight.w600,
                  fontSize: 20,
                  color: Colors.indigo
              ),
            ),
          ),
        );
      }
    }
  • 의문점

    두 번째 카테고리에 오늘의 관심사보다 오늘의 기분이 더 맞지 않을지!!

Recommand Page

  • 키워드에 맞는 말벗 추천해 주는 페이지

  • 추천으로 넘어가기 전에 waiting page 구현을 위해 파일 생성

    • 아래 그림과 같은 페이지 구현
  • findingpage에서 등록 완료하기 버튼을 눌렀을 때 waitingpage로 이동할 수 있도록 네비게이터 생성

  • 안내문을 줄 단위로 작성할 위젯 생성하기

  • 해당 위젯을 column으로 엮고 container에 border decoration 설정하여 구분선 그리기

  • waiting.dart

    import 'package:flutter/material.dart';
    
    class WaitingPage extends StatelessWidget {
      const WaitingPage({Key? key}) : super(key: key);
    
      
      Widget build(BuildContext context) {
        return Scaffold(
          body:
            Container(
              padding: const EdgeInsets.only(left: 50, right: 50, top: 100, bottom: 100),
              child: Container(
                alignment: Alignment.center,
                decoration: BoxDecoration(
                  border: Border.all(
                    width: 3,
                    color: Colors.indigo,
                  ),
                  borderRadius: BorderRadius.circular(30.0),
                ),
               child: Column(
                 mainAxisAlignment: MainAxisAlignment.center,
                 children: <Widget>[
                   notice(line: '제공자'),
                   notice(line: '전화를 희망하시면'),
                   notice(line: '우측 하단의'),
                   notice(line: '좋아요를'),
                   notice(line: '눌러 주세요'),
                   notice(line: ' '),
                   notice(line: '연결을 위하여'),
                   notice(line: '일정 시간이'),
                   notice(line: '소요될 수 있습니다'),
                   notice(line: ' '),
                   notice(line: '(아무 곳이나 누르세요)'),
                 ],
               ),
              ),
            )
        );
      }
    }
    
    class notice extends StatelessWidget {
      final String line;
      const notice({
        Key? key,
        required this.line
      }) : super(key: key);
    
      
      Widget build(BuildContext context) {
        return TextButton(
          onPressed: () {
          },
          child: Center(
            child: Text(
              line,
              style: TextStyle(
                  fontWeight: FontWeight.w700,
                  fontSize: 20,
                  color: Colors.black,
              ),
            ),
          ),
        );
      }
    }
  • 구현 화면

    • 의문점 아무곳이나 누르는 것보다 다음 단계로 가기를 누르는 게 더 정확한 유아이가 아닌가?
  • 누를 때마다 다음 추천 페이지로 이동할 수 있도록 네비게이터 생성

  • 제공자를 추천하는 ServicePage 생성하기

  • 아래의 페이지 구현

  • 이런 사람은 어때요 부분을 구현하기 위해서 container와 text를 column과 row로 엮어서 나열하기

    • 까만 부분에 프로필 삽입
    • 빨간 부분에 특징 삽입
  • textstyle과 border decoration으로 디테일한 설정값 마치기 (글자 크기, 두께, 박스가 둥글거리는 정도)

  • 프로필에 이미지 사진을 첨가하기 위해 assets 파일 생성

  • 해당 폴더에 파일별로 이미지를 정리

  • pubyaml파일에서 assets에 대한 접근성을 허용한 뒤 pub get

  • 프로필 넣는 container에 image 삽입

  • 이런 사람입니다 페이지 구현을 위해 container 세 개를 엮어 원 모양으로 된 위젯을 row 형태로 반환하기

  • 같은 형식으로 container 세 개를 row로 엮은 위젯을 반환하고 양쪽 끝에는 추천과 비추천 아이콘을 삽입

  • findingpage.dart에서 등록 완료하기 버튼과 동일한 형식으로 textbutton 생성

  • service.dart

    import 'package:flutter/material.dart';
    
    class ServiceOne extends StatefulWidget {
      const ServiceOne({Key? key}) : super(key: key);
    
      
      State<ServiceOne> createState() => _ServiceOneState();
    }
    
    class _ServiceOneState extends State<ServiceOne> {
      
      Widget build(BuildContext context) {
        return Scaffold(
          body: Container(
            alignment: Alignment.center,
            padding: EdgeInsets.symmetric(vertical: 100),
            child: Column(
              children: <Widget>[
                Text("이런 사람은 어때요?",
                  style: TextStyle(
                      fontWeight: FontWeight.w900,
                      fontSize: 25,
                      color: Colors.black
                  ),),
                Container(
                  padding: EdgeInsets.all(20),
                  height: 200,
                  child: Row(
                    children: <Widget>[
                      Container(
                        child: Image.asset('assets/images/profile_two.png'),
                        decoration: BoxDecoration(
                          border: Border.all(
                            width: 5,
                            color: Colors.indigo,
                          ),
                          borderRadius: BorderRadius.circular(50.0),
                        ),
                        width: 160,
                        height: 160,
                      ),
                      Container(
                        margin: const EdgeInsets.all(30),
                        child:
                          Text("23/여\n\n고민 상담\n\n자격증 미보유",
                            style: TextStyle(
                                fontWeight: FontWeight.w900,
                                fontSize: 15,
                                color: Colors.black
                            ),
                          ),
                      )
                    ],
                  ),
                ),
                Text("이런 사람입니다!                             ",
                  style: TextStyle(
                      fontWeight: FontWeight.w900,
                      fontSize: 25,
                      color: Colors.black
                  ),),
                Row(
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: <Widget>[
                    Container(
                      alignment: Alignment.center,
                      child: Text("활발한",
                        style: TextStyle(
                            fontWeight: FontWeight.w500,
                            color: Colors.black
                        ),),
                      margin: const EdgeInsets.all(10),
                      width: 100,
                      height: 100,
                      decoration: BoxDecoration(
                        border: Border.all(
                          width: 15,
                          color: Colors.indigo,
                        ),
                        borderRadius: BorderRadius.circular(70.0),
                      ),
                    ),
                    Container(
                      alignment: Alignment.center,
                      child: Text("친절한",
                        style: TextStyle(
                            fontWeight: FontWeight.w500,
                            color: Colors.black
                        ),),
                      margin: const EdgeInsets.all(10),
                      width: 100,
                      height: 100,
                      decoration: BoxDecoration(
                        border: Border.all(
                          width: 15,
                          color: Colors.indigo,
                        ),
                        borderRadius: BorderRadius.circular(70.0),
                      ),
                    ),
                    Container(
                      alignment: Alignment.center,
                      child: Text("따뜻한",
                        style: TextStyle(
                            fontWeight: FontWeight.w500,
                            color: Colors.black
                        ),),
                      margin: const EdgeInsets.all(10),
                      width: 100,
                      height: 100,
                      decoration: BoxDecoration(
                        border: Border.all(
                          width: 15,
                          color: Colors.indigo,
                        ),
                        borderRadius: BorderRadius.circular(70.0),
                      ),
                    )
                  ],
                ),
                Row(
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: <Widget>[
                    Container(
                      alignment: Alignment.center,
                      child: Icon(Icons.thumb_up_alt_rounded, size: 80, color: Colors.indigo,),
                      margin: const EdgeInsets.all(10),
                      width: 100,
                      height: 100,
                    ),
                    Container(
                      alignment: Alignment.center,
                      margin: const EdgeInsets.all(10),
                      width: 100,
                      height: 100,
                    ),
                    Container(
                      alignment: Alignment.center,
                      child: Icon(Icons.thumb_down_alt_rounded, size: 80, color: Colors.indigo,),
                      margin: const EdgeInsets.all(10),
                      width: 100,
                      height: 100,
                    )
                  ],
                ),
                Container(
                  width: 350,
                  height: 65,
                  alignment: Alignment.center,
                  margin: const EdgeInsets.all(3.0),
                  padding: const EdgeInsets.all(8.0),
                  decoration: BoxDecoration(
                    color: Colors.indigo,
                    borderRadius: BorderRadius.circular(10.0),
                  ),
                  child: TextButton(
                    onPressed: () {},
                    child: Text(
                      "추천 중단하기",
                      style: TextStyle(
                          fontWeight: FontWeight.w600,
                          fontSize: 20,
                          color: Colors.white
                      ),
                    ),
                  ),
                )
              ],
            ),
          ),
        );
      }
    }
  • 의문점

    이런 사람은 어때요와 이런 사람입니다 정렬을 굳이 안 맞추는 이유가 뭔지

    사용자에게 엄지 아이콘과 추천 중단하기 중 엄지 아이콘이 전화 걸리는 기능이라고 인식될 가능성

    추천 중단하기는 시스템 종료 느낌이지 않나 싶음

1개의 댓글

comment-user-thumbnail
2025년 9월 3일

Really great post, thanks for sharing! I’ve been working with Flutter for a while, and I’m always impressed with how flexible it is for building modern apps. Your explanation of the calling UI is clear and practical, which makes it super helpful for developers at any level. For those who want to dive deeper into flutter ui, here’s a detailed article that explains why this framework is such a powerful choice for cross-platform development.

답글 달기