Flutter - 특정 영역만 페이지 이동하기 (특정 부분 화면 전환)

반응형

개요

여러 프레임으로 구성된 하나의 화면에서 요구사항에 맞게 특정의 영역만 화면 전환이 되어야할 때가 있다.

Header와 Footer, 그리고 메인 영역이 표시될 Body를 영역별로 나눠서 작업한 적이 있었는데

보통 Navigator.push()를 사용하면 화면 전체가 전환되기 때문에 다소 난감하다.

또한, 매 페이지마다 같은 Header와 Footer 내용의 코드를 재사용하기도 좀 그렇다.

단지 내가 원하는 것은 중앙의 Body 영역의 화면 전환일뿐인데 말이다.

이번 포스팅에서는 특정 영역에서만 화면을 다르게 보여주는 내용에 대해 설명한다.

<flutter 특정 영역 페이지 전환 예시>

 

포스팅에서 설명하는 프로젝트는 깃허브에서 다운로드 가능하다.


기본 구조

Header 영역과  Footer 영역이 각 위와 아래에 위치하고 중앙에는 Body 영역이 존재한다.

이를 코드로 풀어보면 아래와 같다.

  • 식별이 잘 되도록 Container에 색을 부여하였다.
  • 코드 가독성과 조금 더 원활한 포스팅 설명을 위해 BodyPage는 MainPage와 따로 나누었다.
import 'package:flutter/material.dart';

void main() {
  runApp(
    const MaterialApp(
      home: Scaffold(body: MainPage()),
    ),
  );
}

// 메인 페이지
class MainPage extends StatelessWidget {
  const MainPage({super.key});

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        // header
        Container(color: Colors.red, height: 100),
        // body
        const Expanded(child: BodyPage()),
        // footer
        Container(color: Colors.green, height: 100),
      ],
    );
  }
}

// 바디 페이지
class BodyPage extends StatefulWidget {
  const BodyPage({super.key});

  @override
  State<BodyPage> createState() => _BodyPageState();
}

class _BodyPageState extends State<BodyPage> 
  @override
  Widget build(BuildContext context) {
    return Stack(
      children: [
        // 보여지는 화면 (노란색)
        Container(color: Colors.yellow),
        Padding(
          padding: const EdgeInsets.all(8.0),
          child: Align(
            alignment: Alignment.bottomCenter,
            child: Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                // 페이지1 이동 버튼
                ElevatedButton(
                    onPressed: () {},
                    child: const Text("페이지1 이동")),
                // 페이지2 이동 버튼
                Padding(
                  padding: const EdgeInsets.fromLTRB(8, 0, 0, 0),
                  child: ElevatedButton(
                      onPressed: () {},
                      child: const Text("페이지2 이동")),
                ),
              ],
            ),
          ),
        ),
      ],
    );
  }
}

 

보여질 바디 페이지의 부분을 노란색의 컨테이너로 지정하였다.

페이지 이동 버튼은 어디에 있든 상관이 없으나 최대한 코드를 간소화하기 위해 같은 BodyPage 내에 위치하였다.

때문에 코드 내에서 Stack 위젯을 이용하여 컨테이너와 버튼을 같이 배치하였다.

코드의 UI는 아래와 같다.

<MainPage 기본 구조>


특정 영역만 화면 전환을 하려면?

우리가 필요한 것은 화면 전체가 전환되는 것이 아니다.

포스팅에서는 중앙의 BodyPage 부분이 다른 화면으로 전환되기를 바란다.

플러터를 처음 배울 때의 기억을 되살려보자.

플러터의 구조는 위젯들의 집합으로 화면이 구성된다.

이를 바꿔 말하면, 특정 영역 또는 보여지는 화면 또한 위젯으로 구성된다는 의미이다.

포스팅에서 구성한 화면을 보면 Header, Body, Footer 를 구성하는 영역 또한 위젯이다.

이 말은 즉, 사용자의 요구사항에 맞게 Body영역에서 보여지는 위젯을 다른 위젯으로 바꾸면 된다는 소리이다.

우선 위의 BodyPage의 보여지는 컨테이너를 위젯으로 코드를 수정해보자.

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

  @override
  State<BodyPage> createState() => _BodyPageState();
}

class _BodyPageState extends State<BodyPage> {
  // 노란색 컨테이너 위젯 인스턴스 생성
  Widget bodyPage = Container(color: Colors.yellow);

  @override
  Widget build(BuildContext context) {
    return Stack(
      children: [
        bodyPage, // 컨테이너 위젯 변수
		/* 버튼 코드 생략 */
      ],
    );
  }
}

 

이제 답은 다 나왔다.

사용자의 요구사항에 맞게 위젯만 변경해주면 된다.


특정 영역만 화면 전환하기

추가 페이지 정의

우선, 화면이 전환될 페이지를 정의해보자.

  • 페이지1 : 파란색 화면
  • 페이지2 : 보라색 화면
// 페이지1 : 파란색 화면
class Page1 extends StatelessWidget {
  const Page1({super.key});

  @override
  Widget build(BuildContext context) {
    return Container(color: Colors.blue);
  }
}

// 페이지2 : 보라색 화면
class Page2 extends StatelessWidget {
  const Page2({super.key});

  @override
  Widget build(BuildContext context) {
    return Container(color: Colors.deepPurple);
  }
}

화면 전환을 위한 버튼 터치 이벤트 정의

버튼이 터치되었을 때, onPressed 메소드에서 화면 전환할 페이지를 위젯 변수에 저장한다.

그 후 위젯 변수의 값을 setState로 업데이트 한다.

그럼 리렌더링 되는 과정에서 지정한 화면으로 새롭게 화면이 구성된다.

return Stack(
	children: [
    	// 코드 생략
        ElevatedButton(
            onPressed: () => setState(() => bodyPage = const Page1()),
            child: const Text("페이지1 이동")),
        ElevatedButton(
            onPressed: () => setState(() => bodyPage = const Page2()),
            child: const Text("페이지2 이동")),        
    ],
);

전체 소스 코드

더보기
import 'package:flutter/material.dart';

void main() {
  runApp(
    const MaterialApp(
      home: Scaffold(body: MainPage()),
    ),
  );
}

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

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        // header
        Container(color: Colors.red, height: 100),
        // body
        const Expanded(child: BodyPage()),
        // foot
        Container(color: Colors.green, height: 100),
      ],
    );
  }
}

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

  @override
  State<BodyPage> createState() => _BodyPageState();
}

class _BodyPageState extends State<BodyPage> {
  Widget bodyPage = Container(color: Colors.yellow);

  @override
  Widget build(BuildContext context) {
    return Stack(
      children: [
        bodyPage,
        Padding(
          padding: const EdgeInsets.all(8.0),
          child: Align(
            alignment: Alignment.bottomCenter,
            child: Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                ElevatedButton(
                    onPressed: () => setState(() => bodyPage = const Page1()),
                    child: const Text("페이지1 이동")),
                Padding(
                  padding: const EdgeInsets.fromLTRB(8, 0, 0, 0),
                  child: ElevatedButton(
                      onPressed: () => setState(() => bodyPage = const Page2()),
                      child: const Text("페이지2 이동")),
                ),
              ],
            ),
          ),
        ),
      ],
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return Container(color: Colors.blue);
  }
}

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

  @override
  Widget build(BuildContext context) {
    return Container(color: Colors.deepPurple);
  }
}
반응형