티스토리 뷰

 지금까지 Flutter의 기본 구성 요소들에 대하여 살펴보았으니, 간단하게 아주 기초적인 UI 프로젝트를 하나 만들어 보면서 화면 이동이나 화면 간에 데이터를 주고받는 연습을 좀 해보려고 한다.

 

아주 기본적인 list - detail 형태의 ui 프로젝트를 만들 것이기 때문에 따로 DB연결이나 기능에 대하여는 신경 쓰지 않는다.

 

바로 시작해보도록 하자.


우선, list와 detail에 모두 사용할 model을 하나 생성해보자.

 

◎lib > models > book.dart

class Book {
  final String title;
  final String subtitle;
  final String description;
  final String image;

  Book({
    required this.title,
    required this.subtitle,
    required this.description,
    required this.image
  });
}

 

다음으로 이 model 객체를 초기화하고 더미 데이터 생성 및 get 메서드를 제공할 repository 또한 생성한다.

 

◎lib > repositories > book_repository.dart


import '../models/book.dart';

class BookRepository {
  final List<Book> _dummyBooks = [
    Book(
      title: 'MS 팀즈로 소통하고 오피스 365로 만드는 미래수업',
      subtitle: '대충 부제',
      description: '영화에서 자주 등장하는 히어로들은 위기 상황에서 기가 막힌 순발력을 발휘하여 위기를 극복한다. 교사들이 수업을 할 때도 생각치도 못한 위기 상황이 등장하기 마련이다. 다양한 수업 방법과 도구들로 무장한다면 누구나 영화 속 히어로들처럼 멋지게 수업을 이어갈 수 있는 교육의 히어로가 될 수 있다.',
      image: 'https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbIXFaT%2Fbtq3c9f4ZeP%2F02ZkK5m3omPEBt6FPgSBH1%2Fimg.png',
    ),
    Book(
      title: '아파치 카프카 애플리케이션 프로그래밍 with 자바',
      subtitle: '대충 부제',
      description: '영화에서 자주 등장하는 히어로들은 위기 상황에서 기가 막힌 순발력을 발휘하여 위기를 극복한다. 교사들이 수업을 할 때도 생각치도 못한 위기 상황이 등장하기 마련이다. 다양한 수업 방법과 도구들로 무장한다면 누구나 영화 속 히어로들처럼 멋지게 수업을 이어갈 수 있는 교육의 히어로가 될 수 있다.',
      image: 'https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdI6o0h%2Fbtq2bILTJXb%2FUFqmeh0l8LqWfIdZPxk9uk%2Fimg.png',
    ),
    Book(
      title: '웹 디자인 & 웹 퍼블리싱을 위한 피그마 완벽 활용서',
      subtitle: '대충 부제',
      description: '영화에서 자주 등장하는 히어로들은 위기 상황에서 기가 막힌 순발력을 발휘하여 위기를 극복한다. 교사들이 수업을 할 때도 생각치도 못한 위기 상황이 등장하기 마련이다. 다양한 수업 방법과 도구들로 무장한다면 누구나 영화 속 히어로들처럼 멋지게 수업을 이어갈 수 있는 교육의 히어로가 될 수 있다.',
      image: 'https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fyvsr9%2Fbtq9t7p8rFt%2Fx98J2ZBgA6wuQZWUffvDUK%2Fimg.png',    ),
    Book(
      title: 'Vue.js 프로젝트 투입 일주일 전',
      subtitle: '대충 부제',
      description: '영화에서 자주 등장하는 히어로들은 위기 상황에서 기가 막힌 순발력을 발휘하여 위기를 극복한다. 교사들이 수업을 할 때도 생각치도 못한 위기 상황이 등장하기 마련이다. 다양한 수업 방법과 도구들로 무장한다면 누구나 영화 속 히어로들처럼 멋지게 수업을 이어갈 수 있는 교육의 히어로가 될 수 있다.',
      image: 'https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbGOxQ8%2Fbtq6imQpvmA%2FEY3gKphHOPQbk8KT5FOm8K%2Fimg.jpg',    ),

  ];

  List<Book> getBooks() {
    return _dummyBooks;
  }
}

book 객체는 위와 같이 제목, 부제, 설명, 이미지 URL로 4개의 string data를 가지고 있으며, 간단하게 더미 데이터까지 생성을 했다.


이제 ListView를 사용한 리스트 형태의 첫번째 화면을 구성한 뒤 main.dart가 해당 화면을 띄우도록 설정해야 한다.

 

◎lib > screens > list_screen.dart

import 'package:flutter/material.dart';
import 'package:flutter_book_list/repositories/book_repository.dart';
import 'package:flutter_book_list/screens/detail_screen.dart';

import '../models/book.dart';

class BookTile extends StatelessWidget {

  final Book book;

  BookTile({
    required this.book
  });

  @override
  Widget build(BuildContext context) {

    return ListTile(
        title: Text(book.title),
        leading: Image.network(book.image),
        onTap: () {
          Navigator.of(context).push(
            MaterialPageRoute(
                builder: (context) => DetailScreen(
                  book: book
                )
            ),
          );
        }
    );
  }
}

class ListScreen extends StatelessWidget {

  final List<Book> books = BookRepository().getBooks();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text('도서 목록 앱'),
        ),
        body:
        ListView.builder(
          itemCount: books.length,
          itemBuilder: (context, index) {
            return BookTile(book: books[index]);
          },
        )
     );
  }
}

ListView에서 사용하는 ListTile 부분을 BookTile이라는 클래스를 하나 생성하여 해당 객체가 Book을 받아서 ListItem을 하나하나 구성하도록 작성하였다.

 

ListTile의 onTap 함수안에 화면 이동을 위한 코드를 작성하여 각 리스트 아이템을 터치하면 상세 보기 페이지로 Book 데이터와 함께 넘어간다.

 

◎lib > screens > detail_screen.dart


import 'package:flutter/material.dart';

import '../models/book.dart';

class DetailScreen extends StatelessWidget{

  final Book book;

  DetailScreen({
    required this.book,
  });

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(book.title),
      ),
      body: Column(
        children: [
          Image.network(
              book.image
          ),
          Padding(padding: EdgeInsets.all(3)),
          Row(
            crossAxisAlignment: CrossAxisAlignment.start,
            mainAxisAlignment: MainAxisAlignment.spaceBetween,
            children: [
              Container(
                width: MediaQuery.of(context).size.width * 0.8,
                padding: EdgeInsets.all(10),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Container(
                      child: Text(
                        book.title,
                        style: TextStyle(
                          fontSize: 23,
                          fontWeight: FontWeight.bold,
                        ),
                      ),
                    ),
                    Text(
                      book.subtitle,
                      style: TextStyle(fontSize: 18, color: Colors.grey),
                    )
                  ],
                ),
              ),
              Container(
                width: MediaQuery.of(context).size.width * 0.15,
                padding: EdgeInsets.all(10),
                child: Icon(
                  Icons.star,
                  color: Colors.red,
                ),
              )
            ],
          ),
          Padding(padding: EdgeInsets.all(3)),
          Row(
            mainAxisAlignment: MainAxisAlignment.spaceAround,
            children: [
              Column(
                children: [
                  Icon(
                    Icons.call,
                    color: Colors.blue,
                  ),
                  Text(
                    'Contact',
                    style: TextStyle(color: Colors.blue),
                  )
                ],
              ),
              Column(
                children: [
                  Icon(
                    Icons.near_me,
                    color: Colors.blue,
                  ),
                  Text(
                    'Contact',
                    style: TextStyle(color: Colors.blue),
                  )
                ],
              ),
              Column(
                children: [
                  Icon(
                    Icons.save,
                    color: Colors.blue,
                  ),
                  Text(
                    'Contact',
                    style: TextStyle(color: Colors.blue),
                  )
                ],
              ),
            ],
          ),
          Container(
            padding: EdgeInsets.all(15),
            child: Text(
                book.description
            ),
          )

        ],
      )
    );
  }

}

 

detail 부분이 아이콘이나 설명이 들어가 조금 코드가 복잡해 보일 수는 있으나, 한번 쭉 확인해보면 row와 column을 사용하여 레이아웃을 적절하게 구성한 내용이 전부이다. 

 

Container의 너비는

MediaQuery.of(context).size.width

를 사용하여 단말에 맞는 너비를 동적으로 조절할 수 있게끔 작성이 되어 있다.

 

그 외에는 따로 어려운 내용은 없으니, 이제 아래와 같이 list_screen을 main.dart에 띄워주고 실행해보자.

 

 

◎main.dart

import 'package:flutter/material.dart';
import 'package:flutter_book_list/screens/list_screen.dart';

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Book List App',
      home: ListScreen(),
    );
  }
}


디자인은 조금 투박하지만, 이 간단한 프로젝트를 구성하면서 Flutter가 얼마나 편리한지 확실하게 체감했다.

 

위 실행 화면에서 볼 수 있듯, 뒤로가기 기능은 따로 레이아웃을 작성하지도, 그 기능을 넣지도 않았는데 push로 넘어온 화면에서 다시 돌아가는 기능을 자동으로 지원한다.

 

UI 워낙 이미 만들어져있는 형태가 많아서 그리 복잡하지 않은 형태의 어플이라면 개발 시간을 획기적으로 줄일 수 있을 거 같다.

 

다음 포스팅에서는 State를 활용한 간단한 프로젝트를 만들어 보려고 한다.

 

 

끝!!

반응형
Comments