플러터 버전업이 되면서 ScaffoldMessenger.of(context)를 사용함

onPressed: (){ //버튼을 눌렀을때
  ScaffoldMessenger.of(context).showSnackBar(
    //SnackBar 구현하는법 context는 위에 BuildContext에 있는 객체를 그대로 가져오면 됨.
      SnackBar(
        content: Text('Like a new Snack bar!'), //snack bar의 내용. icon, button같은것도 가능하다.
        duration: Duration(seconds: 5), //올라와있는 시간
        action: SnackBarAction( //추가로 작업을 넣기. 버튼넣기라 생각하면 편하다.
          label: 'Undo', //버튼이름
          onPressed: (){}, //버튼 눌렀을때.
        ),
      )
  );
}

 

출처: https://learncom1234.tistory.com/23

'플로터(Flutter) > CheetSheet' 카테고리의 다른 글

이미지 처리  (0) 2023.01.17
focus  (0) 2023.01.17

 

확인할 것

- setState사용법 확인

- FloatingActionButton은 여러개 표현할 때 Stack으로 표현

 

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  void _decrementCounter(){
    setState(() {
      _counter--;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.headline4,
            ),
          ],
        ),

      ),
      floatingActionButton: Stack(
        children:[
          Align(
            alignment: Alignment(
              Alignment.center.x-0.15, Alignment.center.y + 0.35
            ),
            child: FloatingActionButton(
              onPressed: _incrementCounter,
              tooltip: 'Increment',
              child: const Icon(Icons.add),
            ),
          ),
          Align(
            alignment: Alignment(
                Alignment.center.x+0.4, Alignment.center.y + 0.35
            ),
            child: FloatingActionButton(
              onPressed: _decrementCounter,
              tooltip: 'Decrement',
              child: const Icon(Icons.remove),
            )
          )
        ],
      )
    );
  }
}

 

상속 문법(생성자에 유의해서 보길)

class Person{
  String? name;
  int? money;
  Person(this.name, this.money){}

  void status(){
    print("이름: $name , 골드: $money");
  }
}

class Hero extends Person{
  int? hp;
  int? mp;
  Hero(this.hp, this.mp, super.name, super.money);
  //생성자에서 중괄호 안에 내용이 없을 경우 생략해도 됨
  //단 생략할 경우 위 예시처럼 세미콜론 필수
  @override
  void status(){
    print("이름: $name , 골드: $money");
    print("hp: $hp , gold: $mp");
  }
}

class Magician extends Hero{
  String? skill;
  int? damage;
  Magician(this.skill, this.damage, super.hp, super.mp, super.name, super.money){}

  void magic(){
    print("Magic: $skill ,Dagmage: $damage!!");
  }
}


void main(){
  Person p1 = Person("Tim", 100);
  p1.status();
  Hero h1 = Hero(1,2,"Tim",100);
  h1.status();
  Magician m1 = Magician("Meteor", 999, 10,30,"Harry", 999);
  m1.status();
  m1.magic();
  m1.magic();
  m1.magic();
  m1.magic();
}

 

아래는 옛날 방식

class DialPhone{
  int? year;
  DialPhone(){
    print('이 전화기는 다이얼을 돌려서 전화를 겁니다.');
  }

  void whenCame(){
    print("1889년 발명");
  }
}

class ButtonPhone extends DialPhone{
  ButtonPhone(){
    print("이 전화기는 버튼을 눌러서 전화를 겁니다.");
  }

  @override
  void whenCame(){
    print("1963년 발명");
  }
}

class SmartPhone extends ButtonPhone{
  String? manufacturer;
  String? model;

  SmartPhone(String manufacturer, String model, int year){
    this.manufacturer = manufacturer;
    this.model = model;
    this.year = year;

    print("이 전화기는 터치를 해서 전화를 겁니다.");
  }

  @override
  void whenCame(){
    print("1993년 처음 등장");
  }
}

void main(){
  SmartPhone s1 = SmartPhone("제조사:삼성", "모델명:겔럭시 S20", 2020);
  print(s1.manufacturer.toString() + s1.model.toString() + s1.year.toString());

  s1.whenCame();
}

 

 

이번에는 유튜브 모바일과 데스크톱의 레이아웃이 다른 것처럼 반응형으로 웹앱의 Layout을 만드는 강의였다.

 

 

main.dart

import 'package:flutter/material.dart';
import 'my_page.dart';
void main() {
  runApp(const MyApp());
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData(
        primarySwatch: Colors.deepPurple,

      ),
      home: MyPage(),
    );
  }
}

 

my_page.dart

- body를 ResponsiveLayout 위젯을 만들어 반응형으로 화면 구성

- ResponsiveLayout은 생성자에서 2개의 변수를 초기화 해야하므로 mobileBody, desktopBody를 매개변수로 넘겨줌

import 'package:flutter/material.dart';
import 'package:mild_2_youtube/responsive/desktop_body.dart';
import 'package:mild_2_youtube/responsive/mobile_body.dart';
import 'package:mild_2_youtube/responsive/responsive.dart';

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

  @override
  State<MyPage> createState() => _MyPageState();
}

class _MyPageState extends State<MyPage> {
  @override
  Widget build(BuildContext context) {
    return const Scaffold(
      body: ResponsiveLayout(
        mobileBody: MobileBody(),
        desktopBody: DesktopBody(),
      ),
    );
  }
}

 

 

responsive.dart

- LayoutBuilder클래스를 사용해서 화면너비에 따라 모바일, 데스크탑용으로 화면layout을 바꿔주기 위해 사용

참고: https://api.flutter.dev/flutter/widgets/LayoutBuilder-class.html

- 제약조건을 거는데 최대 너비가 800이하일 경우 모바일, 이상일 경우 데스크탑 위젯 화면 보여줌

import 'package:flutter/material.dart';

class ResponsiveLayout extends StatelessWidget {
  const ResponsiveLayout(
      {Key? key, required this.mobileBody, required this.desktopBody})
      : super(key: key);

  final Widget mobileBody;
  final Widget desktopBody;

  @override
  Widget build(BuildContext context) {
    return LayoutBuilder(builder: (context, constraints) {
      if (constraints.maxWidth < 800) {
        return mobileBody;
      } else {
        return desktopBody;
      }
    }); //기기 전체가 아니라 위젯의 크기를 알아내기
  }
}

 

mobile_body.dart

- 모바일 화면구성

- 컬럼 내에서 listview를 사용할 때 expanded를 감싸서 공간의 범위를 제한해줌

import 'package:flutter/material.dart';


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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Mobile'),
      ),
      backgroundColor: Colors.deepPurple,
      body: Column(
        children: [
          Padding(
            padding: const EdgeInsets.all(16.0),
            child: Container(
              height: 300,
              color: Colors.deepPurple[400],
            ),
          ),
          Expanded( //컬럼 내에서 Listview사용할 때는 Expanded로 감싸줘야함, 안그러면 container의 height를 쓸 수 없음
            child: ListView.builder(
                itemCount: 10,
                itemBuilder: (context, index){
                  return Padding(
                    padding: const EdgeInsets.all(8.0),
                    child: Container(
                      color: Colors.deepPurple[300],
                      height: 120,
                    ),
                  );
                }),
          )
        ],
      ),
    );
  }
}

 

 

desktop_body.dart

- sidePanel의 너비를 300으로 고정하고 나머지는 너비를 주지않았고 리스트뷰를 expanded로 감쌈으로 해서 화면 크기 변화에 따라 1열의 너비만 변하는 것을 볼 수 있다.

import 'package:flutter/material.dart';

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Desktop'),
      ),
      backgroundColor: Colors.deepPurple[200],
      body: Row(
        children: [
          Expanded(
            child: Column(
              children: [
                Padding(
                  padding: const EdgeInsets.all(16.0),
                  child: Container(
                    height: 300,
                    color: Colors.deepPurple[400],
                  ),
                ),
                Expanded( //컬럼 내에서 Listview사용할 때는 Expanded로 감싸줘야함, 안그러면 container의 height를 쓸 수 없음
                  child: ListView.builder(
                      itemCount: 10,
                      itemBuilder: (context, index){
                        return Padding(
                          padding: const EdgeInsets.all(8.0),
                          child: Container(
                            color: Colors.deepPurple[300],
                            height: 120,
                          ),
                        );
                      }),
                ),
              ],
            ),
          ),
          //side Panel
          Container(
            width: 300,
            child: ListView.builder(
                itemCount: 10,
                itemBuilder: (context, index){
                  return Padding(
                    padding: const EdgeInsets.all(8.0),
                    child: Container(
                      color: Colors.deepPurple[300],
                      height: 120,
                    ),
                  );
                }),
          )
        ],
      ),
    );
  }
}

 

 

 

 

요약 - 리스트 아이템을 선택하면 동물페이지가 뜨고 좋아요를 누를 수 있게 됨

주요 기능

 

 

main.dart

- 메인 페이지이며 리스트 제너레이터 사용법 확인

- navigator를 통해 animal_page로 넘어갈 때 animalData를 넘겨줌

import 'package:flutter/material.dart';
import 'package:mild_2_listview/animal_page.dart';
import 'package:mild_2_listview/model.dart';

void main() {
  runApp(const MyApp());
}

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

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: MyPage(),
    );
  }
}

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

  @override
  State<MyPage> createState() => _MyPageState();
}

class _MyPageState extends State<MyPage> {
  static List<String> animalName = [
    'Bear',
    'Camel',
    'Deer',
    'Fox',
    'Kangaroo',
    'Koala',
    'Lion',
    'Tiger'
  ];

  static List<String> animalImagePath = [
    'image/bear.png',
    'image/camel.png',
    'image/deer.png',
    'image/fox.png',
    'image/kangaroo.png',
    'image/koala.png',
    'image/lion.png',
    'image/tiger.png'
  ];

  static List<String> animalLocation = [
    'forest and mountain',
    'dessert',
    'forest',
    'snow mountain',
    'Australia',
    'Australia',
    'Africa',
    'Korea'
  ];

  final List<Animal> animalData = List.generate(
      animalLocation.length,
      (index) => Animal(
          animalName[index], animalImagePath[index], animalLocation[index]));

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: const Text("ListView"),
        ),
        body: ListView.builder(
            itemCount: animalData.length,
            itemBuilder: (context, index) {
              //debugPrint(index.toString()+" : "+animalData[index].imgPath.toString());
              return Card(
                  child: ListTile(
                      title: Text(animalData[index].name),
                      leading: SizedBox(
                        height: 50,
                        width: 50,
                        child: Image.asset(animalData[index].imgPath),
                      ),
                    onTap: (){
                        Navigator.of(context).push(MaterialPageRoute(
                            builder: (context)=>AnimalPage(animal: animalData[index],)));
                        debugPrint(animalData[index].name);
                    },
                  ));
            }));
  }
}

 

animal_page.dart

- 넘겨받음 animal데이터로 page구현

- like_button패키기 사용(https://pub.dev/packages/like_button/install)

import 'package:flutter/material.dart';
import 'package:mild_2_listview/model.dart';
import 'package:like_button/like_button.dart';

class AnimalPage extends StatefulWidget {
  const AnimalPage({Key? key, required this.animal}) : super(key: key);
  //null 값을 가질 수 없는 this.animal에는 required를 붙여준다
  final Animal animal;

  @override
  State<AnimalPage> createState() => _AnimalPageState();
}

class _AnimalPageState extends State<AnimalPage> {
  bool isLiked = false;
  int likeCount = 0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.animal.name),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            SizedBox(
                height: 200, width: 200, child: Image.asset(widget.animal.imgPath)),
            const SizedBox(
              height: 20,
            ),
            Text('It lives in ' + widget.animal.location,
            style: const TextStyle(
              fontSize: 18
            ),),
            const SizedBox(
              height: 20,
            ),
            LikeButton(
              size: 30,
                isLiked: isLiked,
                likeCount: likeCount,
            )
          ],
        ),
      ),
    );
  }
}

 

model.dart

- Animal클래스

import 'package:flutter/material.dart';

class Animal{
  final String name;  //final 은 runtime시에 값이 정해질 수 있기 때문에
  final String imgPath;
  final String location;

  Animal(this.name, this.imgPath, this.location);
}

 

 

 

1. Alt + Enter - 라이브러리 추가 등

2. Shift + Enter - 다음칸으로 커서 이동

 

 

1. 리스트뷰 vs 리스트뷰빌더

  - 리스트뷰와 리스트뷰빌더가 있는데 리스트뷰는 모든 리스트를 한 번에 불러오기 때문에 로딩이슈가 발생 할 수 있다.

  - 리스트뷰빌더는 필요한 것만 그때 그때 불러서 사용

2. 리스트뷰빌더예제

  - 리스트뷰형식에 터치를 하면 상세화면이 나옴

  - 주요기능은

    1) 리스트뷰 구현 - 리스트뷰빌더사용하였고 터치를 위해 GestureDetector사용

    2) 팝업창(상세화면) 구현 - showpopup이라는 함수를 만들어서 구현

    3)가로 세로윈도우가 바뀌었을 때도 반응형으로 동작 - description을 구현할 때 mediaquery 클래스 사용

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

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

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: ListViewPage(),
    );
  }
}

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

  @override
  State<ListViewPage> createState() => _ListViewPageState();
}

class _ListViewPageState extends State<ListViewPage> {
  var titleList = [
    'Dentist',
    'Pharmacist',
    'School teacher',
    'IT manager',
    'Account',
    'Lawyer',
    'Hairdresser',
    'Physician',
    'Web developer',
    'Medical Secretary'
  ];

  var imageList = [
    'image/1.png',
    'image/2.png',
    'image/3.png',
    'image/4.png',
    'image/5.png',
    'image/6.png',
    'image/7.png',
    'image/8.png',
    'image/9.png',
    'image/10.png'
  ];

  var description = [
    '1.There are different types of careers you can pursue in your life. Which one will it be?',
    '2.There are different types of careers you can pursue in your life. Which one will it be?',
    '3.There are different types of careers you can pursue in your life. Which one will it be?',
    '4.There are different types of careers you can pursue in your life. Which one will it be?',
    '5.There are different types of careers you can pursue in your life. Which one will it be?',
    '6.There are different types of careers you can pursue in your life. Which one will it be?',
    '7.There are different types of careers you can pursue in your life. Which one will it be?',
    '8.There are different types of careers you can pursue in your life. Which one will it be?',
    '9.There are different types of careers you can pursue in your life. Which one will it be?',
    '10.There are different types of careers you can pursue in your life. Which one will it be?'
  ];

  void showPopup(context, title, image, description) {
    showDialog(
        context: context,
        builder: (context) {
          return Dialog(
            child: Container(
              width: MediaQuery.of(context).size.width * 0.7,
              height: 380,
              decoration: BoxDecoration(
                  borderRadius: BorderRadius.circular(10), color: Colors.white),
              child: Column(
                children: [
                  ClipRRect(
                      borderRadius: BorderRadius.circular(10),
                      child: Image.asset(
                        image,
                        width: 200,
                        height: 200,
                      )),
                  const SizedBox(
                    height: 10,
                  ),
                  Text(
                    title,
                    style: const TextStyle(
                      fontSize: 25,
                      fontWeight: FontWeight.bold,
                      color: Colors.grey,
                    ),
                  ),
                  Padding(
                    padding: const EdgeInsets.all(8),
                    child: Text(
                      description,
                      maxLines: 3, //최대 3줄까지 표현
                      style: TextStyle(fontSize: 15, color: Colors.grey[500]),
                      textAlign: TextAlign.center,
                    ),
                  ),
                  ElevatedButton.icon(
                      onPressed: () {
                        Navigator.pop(context);
                      },
                      icon: const Icon(Icons.close),
                      label: const Text('close')),
                ],
              ),
            ),
          );
        });
  }

  @override
  Widget build(BuildContext context) {
    double width =
        MediaQuery.of(context).size.width * 0.6; //반응형을 위해 디스크립션을 60%크기로
    return Scaffold(
      appBar: AppBar(
        title: const Text(
          'ListView',
          style: TextStyle(color: Colors.grey),
        ),
        backgroundColor: Colors.white,
        elevation: 0,
      ),
      body: ListView.builder(
        itemCount: titleList.length,
        itemBuilder: (context, index) {
          return GestureDetector(  //InkWell 도 사용가능
            onTap: () {
              debugPrint(titleList[index]);
              showPopup(context, titleList[index], imageList[index],
                  description[index]);
            },
            child: Card(
                child: Row(children: [
              SizedBox(
                width: 100,
                height: 100,
                child: Image.asset(imageList[index]),
              ),
              Padding(
                  padding: const EdgeInsets.all(10),
                  child: Column(
                    children: [
                      Text(titleList[index],
                          style: const TextStyle(
                              fontSize: 22,
                              fontWeight: FontWeight.bold,
                              color: Colors.grey)),
                      const SizedBox(
                        height: 10,
                      ),
                      SizedBox(
                          width: width,
                          child: Text(description[index],
                              style: TextStyle(
                                fontSize: 15,
                                color: Colors.grey[500],
                              )))
                    ],
                  ))
            ])),
          );
        },
      ),
    );
  }
}

 

 

온보딩 스크린은 앱을 처음 설치했을 때 간단한 안내사항 및 튜토리얼 같은 것을 안내하는 페이지

 

1. 구현을 위해 다음 패키지를 설치해야한다

introduction_screen: ^3.1.2

https://pub.dev/packages/introduction_screen

 

introduction_screen | Flutter Package

Introduction/Onboarding package for flutter app with some customizations possibilities

pub.dev

 

2. 파일은

main.dart (기본 화면) - 앱의 메인화면이 시작 될 것

onboarding.dart (온보딩 스크린 꾸미기)

 

3. main.dart

Elevated버튼 부분에서 push 대신 pushReplacement를 사용했는데 뒤로가기 버튼을 없앨 수 있다.

MaterialApp의 홈부분에서 MyPage로 가는게 아니라 OnBoardingPage로 연결되는 것이 포인트

(사실 온보딩페이지는 1번만 보여주고 그 다음부터는 보여주면 안되는 것이지만 우리는 연습을 위해서 이렇게 함)

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

void main() {
  runApp(const MyApp());
}

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

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: OnBoardingPage(),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Main page'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Text(
              'Main Screen',
              style: TextStyle(fontWeight: FontWeight.bold, fontSize: 25),
            ),
            ElevatedButton(
                onPressed: () {
                  Navigator.of(context).pushReplacement(
                      MaterialPageRoute(builder: (context) => const OnBoardingPage()));
                },
                child: const Text('Go to onboarding Screen'))
          ],
        ),
      ),
    );
  }
}

 

4. onboarding.dart

우선 IntroductionScreen에 보면 아주 다양한 속성들을 가지고 있는데 여기서 우리는 아래 속성들을 편집할 예정

page: 배치할 페이지 꾸미는 부분

done: 마지막 화면에 띄워줄 글씨

onDone: 마지막 화면에서 메인 페이지로 연결

next: 다음 화면 표시할 것 (우리는 forward 아이콘 사용)

showSkipButton (skip버튼 사용 여부)

skip: 화면에 표시할 것

dotsDecoration: 화면 진행상황 표시 점을 꾸미기

curve: 에니메이션 효과 넣기

 

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

import 'main.dart';

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

  @override
  Widget build(BuildContext context) {
    return IntroductionScreen(
      pages: [
        PageViewModel(
            title: 'Welcome to my app',
            body: 'This is the first Page',
            image: Image.asset('image/page1.png'),
            decoration: getPageDecoration()),
        PageViewModel(
            title: 'Welcome to my app',
            body: 'This is the second Page',
            image: Image.asset('image/page2.png'),
            decoration: getPageDecoration()),
        PageViewModel(
            title: 'Welcome to my app',
            body: 'This is the third Page',
            image: Image.asset('image/page3.png'),
            decoration: getPageDecoration()),
      ],
      done: const Text('Done!'),
      onDone: () {
        Navigator.of(context)
            .pushReplacement(MaterialPageRoute(builder: (context) => const MyPage()));
      },
      next: const Icon(Icons.arrow_forward),
      showSkipButton: true,
      skip: const Text("Skip"),
      dotsDecorator: DotsDecorator(
          color: Colors.cyan,
          size: const Size(10, 10),
          activeSize: const Size(22, 10),
          activeShape:
              RoundedRectangleBorder(borderRadius: BorderRadius.circular(24))),
      curve: Curves.bounceOut,
    );
  }
}

PageDecoration getPageDecoration() {
  return const PageDecoration(
    titleTextStyle: TextStyle(fontSize: 28, fontWeight: FontWeight.bold),
    bodyTextStyle: TextStyle(
      fontSize: 18,
      color: Colors.blue,
    ),
    imagePadding: EdgeInsets.only(top: 40),
    pageColor: Colors.orange,
  );
}

 

페이지별로 동일한 데코레이션을 모든 페이지마다 따로 코드를 작성한다면 너무 비효율적임

그래서 우리는 pageDecoration메서드를 만들어서 decoration을 함수 호출방식으로 사용

반복작업을 효과적으로 바꿈

+ Recent posts