Flutter를 사용한 반응형 웹 및 데스크탑 개발
게시 됨: 2022-03-10이 튜토리얼은 Flutter 자체에 대한 소개가 아닙니다. Flutter의 기본을 배우는 데 도움이 될 간단한 소개와 함께 온라인에서 많은 기사, 비디오 및 여러 책을 사용할 수 있습니다. 대신 다음 두 가지 목표를 다룹니다.
- Flutter 비모바일 개발의 현재 상태와 브라우저, 데스크톱 또는 노트북 컴퓨터에서 Flutter 코드를 실행하는 방법
- Flutter를 사용하여 반응형 앱을 만드는 방법, 특히 웹 프레임워크로서의 기능을 전체 화면에서 볼 수 있도록 하고 URL 기반 라우팅에 대한 메모로 끝납니다.
그 속으로 들어가 보자!
Flutter가 무엇인지, 왜 중요한지, 무엇으로 진화했는지, 어디로 가는지
Flutter는 Google의 최신 앱 개발 프레임워크입니다. Google은 모든 것을 포괄하는 것으로 생각합니다. 모든 브랜드의 스마트폰, 태블릿, 데스크톱 및 랩톱 컴퓨터에서 기본 앱이나 웹 페이지로 동일한 코드를 실행할 수 있습니다.
그것은 매우 야심 찬 프로젝트이지만 Google은 특히 두 가지 측면에서 지금까지 매우 성공적이었습니다. Android 및 iOS 기본 앱을 위한 진정으로 플랫폼 독립적인 프레임워크를 만들고 훌륭하게 작동하고 프로덕션 사용에 사용할 준비가 된 것과 인상적인 전면을 만드는 것입니다. -호환되는 Flutter 앱과 코드의 100%를 공유할 수 있는 엔드 웹 프레임워크.
다음 섹션에서는 앱을 호환 가능하게 만드는 요소와 현재 비모바일 Flutter 개발 현황을 살펴보겠습니다.
Flutter를 사용한 비모바일 개발
Flutter를 사용한 비모바일 개발은 Google I/O 2019에서 처음으로 중요한 방식으로 공개되었습니다. 이 섹션에서는 작동하게 하는 방법과 작동 시기에 대해 설명합니다.
웹 및 데스크탑 개발을 활성화하는 방법
웹 개발을 활성화하려면 먼저 Flutter의 베타 채널에 있어야 합니다. 그 지점에 도달하는 두 가지 방법이 있습니다.
- SDK 아카이브에서 적절한 최신 베타 버전을 다운로드하여 베타 채널에 Flutter를 직접 설치하세요.
- 이미 Flutter가 설치되어 있다면
$ flutter channel beta
를 사용하여 베타 채널로 전환한 다음$ flutter upgrade
로 Flutter 버전(실제로 Flutter 설치 폴더의git pull
)을 업데이트하여 전환 자체를 수행하십시오.
그 후에 다음을 실행할 수 있습니다.
$ flutter config --enable-web
데스크톱 지원은 특히 Linux 및 Windows용 도구 부족으로 인해 훨씬 더 실험적이며 플러그인 개발을 특히 큰 고통으로 만들고 여기에 사용되는 API가 개념 증명 용도가 아니라 생산. 이것은 Windows 및 Linux 기본 데스크톱 앱에서도 지원되지 않는 릴리스 빌드에 대해 검증된 dart2js 컴파일러를 사용하는 웹 개발과 다릅니다.
참고 : macOS에 대한 지원은 Windows 및 Linux에 대한 지원보다 약간 낫지만 여전히 웹에 대한 지원만큼 좋지 않으며 모바일 플랫폼에 대한 전체 지원만큼 좋지는 않습니다.
데스크톱 개발 지원을 활성화하려면 beta
채널에 대해 앞에서 설명한 것과 동일한 단계에 따라 master
릴리스 채널로 전환해야 합니다. 그런 다음 <OS_NAME>
을 linux
, windows
또는 macos
로 바꿔 다음을 실행합니다.
$ flutter config --enable-<OS_NAME>-desktop
이 시점에서 Flutter 도구가 내가 말해야 하는 작업을 수행하지 않기 때문에 설명할 다음 단계 중 하나에 문제가 있는 경우 몇 가지 일반적인 문제 해결 단계는 다음과 같습니다.
-
flutter doctor
를 실행하여 문제를 확인합니다. 이 Flutter 명령의 부작용은 필요하지 않은 도구를 다운로드해야 한다는 것입니다. -
flutter upgrade
를 실행합니다. - 껐다가 다시 켜십시오. 컴퓨터를 다시 시작하라는 오래된 Tier-1 기술 지원 답변은 Flutter의 모든 기능을 즐기기 위해 필요한 것일 수 있습니다.
Flutter 웹 앱 실행 및 빌드
Flutter 웹 지원은 전혀 나쁘지 않은데, 이는 웹 개발 용이성에 반영됩니다.
실행 중…
$ flutter devices
... 다음과 같은 항목을 즉시 표시해야 합니다.
Web Server • web-server • web-javascript • Flutter Tools
또한 Chrome 브라우저를 실행하면 Flutter가 이에 대한 항목도 표시해야 합니다. "연결된 장치"가 웹 서버뿐일 때 호환되는 Flutter 프로젝트(나중에 자세히 설명)에서 flutter run
을 실행하면 Flutter가 localhost:<RANDOM_PORT>
에서 웹 서버를 시작하여 Flutter에 액세스할 수 있습니다. 모든 브라우저에서 웹 앱.
Chrome을 설치했지만 표시되지 않으면 CHROME_EXECUTABLE
환경 변수를 Chrome 실행 파일의 경로로 설정해야 합니다.
Flutter 데스크톱 앱 실행 및 빌드
Flutter 데스크톱 지원을 활성화한 후 Flutter flutter run -d <OS_NAME>
을 사용하여 개발 워크스테이션에서 기본적으로 Flutter 앱을 실행할 수 있습니다. <OS_NAME>
을 데스크톱 지원을 활성화할 때 사용한 것과 동일한 값으로 바꿉니다. 또한 flutter build <OS_NAME>
을 사용하여 build
디렉토리에 바이너리를 빌드할 수 있습니다.
하지만 그 전에 Flutter가 플랫폼을 위해 구축해야 하는 것이 포함된 디렉토리가 있어야 합니다. 이것은 새 프로젝트를 생성할 때 자동으로 생성되지만, flutter create .
. 또한 Linux 및 Windows API는 불안정하므로 Flutter 업데이트 후 앱 작동이 중지되면 해당 플랫폼에 대해 API를 재생성해야 할 수 있습니다.
앱은 언제 호환됩니까?
Flutter 앱이 데스크탑이나 웹에서 작동하려면 "호환되는 프로젝트"여야 한다고 언급할 때 제가 말하고자 하는 바는 무엇입니까? 간단히 말해서, 빌드하려는 플랫폼에 대한 플랫폼별 구현이 없는 플러그인을 사용해서는 안 됩니다.
이 점을 모든 사람에게 절대적으로 명확하게 하고 오해를 피하기 위해 Flutter 플러그인 은 기능을 제공하는 데 필요한 플랫폼별 코드가 포함된 특정 Flutter 패키지 입니다.
예를 들어 Google에서 개발한 url_launcher
패키지를 원하는 만큼 사용할 수 있습니다(웹이 하이퍼링크를 기반으로 하는 경우 원할 수도 있음).
웹 개발을 방해하는 Google 개발 패키지의 예로는 파일을 저장할 로컬 저장소 경로를 가져오는 데 사용되는 path_provider
가 있습니다. 이것은 부수적으로 웹 앱에 아무 소용이 없는 패키지의 예입니다. 사용하는 경우 웹에서 작동합니다.
예를 들어 웹의 HTML localStorage
에 의존하는 shared_preferences 패키지를 사용할 수 있습니다.
데스크톱 플랫폼과 관련하여 유사한 경고가 유효합니다. 데스크톱 플랫폼과 호환되는 플러그인은 거의 없으며, 반복되는 테마이므로 웹용 Flutter에서 실제로 필요한 것보다 데스크톱 측에서 훨씬 더 많은 작업을 수행해야 합니다.
Flutter에서 반응형 레이아웃 만들기
위에서 설명한 내용과 단순성을 위해 이 게시물의 나머지 부분에서는 대상 플랫폼이 웹이라고 가정하지만 기본 개념은 데스크톱 개발에도 적용됩니다.
웹 지원에는 이점과 책임이 있습니다. 서로 다른 화면 크기를 지원해야 한다는 것은 단점처럼 들릴 수 있지만 웹 브라우저에서 앱을 실행하면 별도로 실행할 필요 없이 다양한 크기와 종횡비의 화면에서 앱이 어떻게 보이는지 매우 쉽게 볼 수 있다는 점을 고려하십시오. 모바일 장치 에뮬레이터.
이제 코드에 대해 이야기해 봅시다. 어떻게 앱을 반응형으로 만들 수 있습니까?
이 분석이 수행되는 두 가지 관점이 있습니다.
- "다양한 크기의 화면에 적응할 수 있거나 조정해야 하는 어떤 위젯을 사용 중이거나 사용할 수 있습니까?"
- "화면 크기에 대한 정보는 어떻게 얻을 수 있고, UI 코드를 작성할 때 어떻게 사용할 수 있나요?"
우리는 나중에 첫 번째 질문에 답할 것입니다. 후자에 대해 먼저 이야기합시다. 매우 쉽게 다룰 수 있고 문제의 핵심이기 때문입니다. 두 가지 방법이 있습니다.
- 한 가지 방법은
MediaQuery
루트InheritedWidget
의MediaQueryData
에서 정보를 가져오는 것입니다. 이 정보는 Flutter 앱이 작동하기 위해 위젯 트리에 존재해야 합니다(MaterialApp/WidgetsApp/CupertinoApp
의 일부임). 다음과 같이 얻을 수 있습니다. 다른 모든InheritedWidget
,MediaQuery.of(context)
는size
속성이 있고Size
유형이므로double
유형의 두width
및height
속성이 있습니다. - 다른 방법은
minHeight
,maxHeight
,minWidth
및maxWidth
속성이 있는BoxConstraints
객체를 (context
와 함께)builder
함수에 전달하는 빌더 위젯(StreamBuilder 또는StreamBuilder
와FutureBuilder
)인LayoutBuilder
를 사용하는 것입니다.
다음은 제약 조건을 가져오기 위해 MediaQuery를 사용하는 MediaQuery
의 예입니다. 코드는 다음과 같습니다.
import 'package:flutter/material.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(context) => MaterialApp( home: MyHomePage() ); } class MyHomePage extends StatelessWidget { @override Widget build(context) => Scaffold( body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ Text( "Width: ${MediaQuery.of(context).size.width}", style: Theme.of(context).textTheme.headline4 ), Text( "Height: ${MediaQuery.of(context).size.height}", style: Theme.of(context).textTheme.headline4 ) ] ) ) ); }
다음은 동일한 작업에 LayoutBuilder
를 사용하는 것입니다.
import 'package:flutter/material.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(context) => MaterialApp( home: MyHomePage() ); } class MyHomePage extends StatelessWidget { @override Widget build(context) => Scaffold( body: LayoutBuilder( builder: (context, constraints) => Center( child: Column( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ Text( "Width: ${constraints.maxWidth}", style: Theme.of(context).textTheme.headline4 ), Text( "Height: ${constraints.maxHeight}", style: Theme.of(context).textTheme.headline4 ) ] ) ) ) ); }
이제 어떤 위젯이 제약 조건에 적응할 수 있는지 생각해 봅시다.
먼저 화면 크기에 따라 여러 위젯을 배치하는 다양한 방법을 생각해 봅시다.
가장 쉽게 적응하는 위젯은 GridView
입니다. 실제로 GridView
생성자를 사용하여 빌드된 GridView.extent
는 다음과 같은 매우 간단한 예에서 볼 수 있듯이 반응형으로 만들기 위해 사용자의 개입이 필요하지 않습니다.
import 'package:flutter/material.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(context) => MaterialApp( home: MyHomePage() ); } class MyHomePage extends StatelessWidget { final List elements = [ "Zero", "One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "A Million Billion Trillion", "A much, much longer text that will still fit" ]; @override Widget build(context) => Scaffold( body: GridView.extent( maxCrossAxisExtent: 130.0, crossAxisSpacing: 20.0, mainAxisSpacing: 20.0, children: elements.map((el) => Card(child: Center(child: Padding(padding: EdgeInsets.all(8.0), child: Text(el))))).toList() ) ); }
import 'package:flutter/material.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(context) => MaterialApp( home: MyHomePage() ); } class MyHomePage extends StatelessWidget { final List elements = [ "Zero", "One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "A Million Billion Trillion", "A much, much longer text that will still fit" ]; @override Widget build(context) => Scaffold( body: GridView.extent( maxCrossAxisExtent: 130.0, crossAxisSpacing: 20.0, mainAxisSpacing: 20.0, children: elements.map((el) => Card(child: Center(child: Padding(padding: EdgeInsets.all(8.0), child: Text(el))))).toList() ) ); }
maxCrossAxisExtent
를 변경하여 다양한 크기의 콘텐츠를 수용할 수 있습니다.
이 예제는 주로 GridView.extent
GridView
생성자의 존재를 보여주는 목적을 제공했지만 훨씬 더 현명한 방법은 SliverGridDelegateWithMaxCrossAxisExtent
와 함께 GridView.builder
를 사용하는 것입니다. 이 경우 위젯이 그리드에 표시되는 경우 이 예에서 볼 수 있듯이 다른 데이터 구조에서 동적으로 생성됩니다.
import 'package:flutter/material.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(context) => MaterialApp( home: MyHomePage() ); } class MyHomePage extends StatelessWidget { final List<String> elements = ["Zero", "One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "A Million Billion Trillion", "A much, much longer text that will still fit"]; @override Widget build(context) => Scaffold( body: GridView.builder( itemCount: elements.length, gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent( maxCrossAxisExtent: 130.0, crossAxisSpacing: 20.0, mainAxisSpacing: 20.0, ), itemBuilder: (context, i) => Card( child: Center( child: Padding( padding: EdgeInsets.all(8.0), child: Text(elements[i]) ) ) ) ) ); }
다른 화면에 적응하는 GridView의 예는 내 개인 방문 페이지입니다. 이 페이지는 Cards
가 조금 더 복잡하고 더 크다는 점을 제외하고는 이전 예제 코드와 마찬가지로 많은 Cards
가 있는 GridView
로 구성된 매우 간단한 Flutter 웹 앱입니다. .
휴대전화용으로 설계된 앱에서 수행할 수 있는 매우 간단한 변경은 공간이 있을 때 Drawer
을 왼쪽에 영구적인 메뉴로 교체하는 것입니다.
예를 들어 탐색에 사용되는 다음과 같은 위젯의 ListView
를 가질 수 있습니다.
class Menu extends StatelessWidget { @override Widget build(context) => ListView( children: [ FlatButton( onPressed: () {}, child: ListTile( leading: Icon(Icons.looks_one), title: Text("First Link"), ) ), FlatButton( onPressed: () {}, child: ListTile( leading: Icon(Icons.looks_two), title: Text("Second Link"), ) ) ] ); }
스마트폰에서 일반적으로 사용되는 Drawer
(햄버거 메뉴라고도 함) 내부입니다.
이에 대한 대안은 TabBarView
와 함께 BottomNavigationBar
또는 TabBar
가 될 수 있지만 둘 모두를 사용하면 서랍에 필요한 것보다 더 많은 변경을 해야 하므로 서랍을 계속 사용할 것입니다.
작은 화면에서 앞에서 본 Menu
가 포함된 Drawer
만 표시하려면 다음 스니펫과 같은 코드를 작성하고 MediaQuery.of(context)
Drawer
사용하여 Scaffold
를 확인하고 앱에 적합하다고 생각되는 너비 값보다 작습니다.
Scaffold( appBar: AppBar(/* ... \*/), drawer: MediaQuery.of(context).size.width < 500 ? Drawer( child: Menu(), ) : null, body: /* ... \*/ )
이제 Scaffold
의 body
에 대해 생각해 봅시다. 앱의 샘플 기본 콘텐츠로 이전에 빌드한 GridView
를 사용합니다. 이 GridView는 혼동을 피하기 위해 Content
라는 별도의 위젯에 보관합니다.
class Content extends StatelessWidget { final List elements = ["Zero", "One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "A Million Billion Trillion", "A much, much longer text that will still fit"]; @override Widget build(context) => GridView.builder( itemCount: elements.length, gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent( maxCrossAxisExtent: 130.0, crossAxisSpacing: 20.0, mainAxisSpacing: 20.0, ), itemBuilder: (context, i) => Card( child: Center( child: Padding( padding: EdgeInsets.all(8.0), child: Text(elements[i]) ) ) ) ); }
class Content extends StatelessWidget { final List elements = ["Zero", "One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "A Million Billion Trillion", "A much, much longer text that will still fit"]; @override Widget build(context) => GridView.builder( itemCount: elements.length, gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent( maxCrossAxisExtent: 130.0, crossAxisSpacing: 20.0, mainAxisSpacing: 20.0, ), itemBuilder: (context, i) => Card( child: Center( child: Padding( padding: EdgeInsets.all(8.0), child: Text(elements[i]) ) ) ) ); }
더 큰 화면에서 본문 자체는 고정 너비로 제한된 Menu
와 나머지 화면을 채우는 Content
라는 두 개의 위젯을 표시하는 Row
수 있습니다.
작은 화면에서는 전체 body
이 Content
가 됩니다.
때때로 Flutter 웹 앱 위젯, 특히 Row
와 Column
을 사용할 때 보이는 화면 영역 외부로 끝나기 때문에 SafeArea
및 Center
위젯으로 모든 것을 래핑합니다. 이는 SafeArea
및/또는 Center
로 수정됩니다.
이것은 Scaffold
의 body
가 다음과 같다는 것을 의미합니다:
SafeArea( child:Center( child: MediaQuery.of(context).size.width < 500 ? Content() : Row( children: [ Container( width: 200.0, child: Menu() ), Container( width: MediaQuery.of(context).size.width-200.0, child: Content() ) ] ) ) )
다음은 그 모든 것을 정리한 것입니다.
import 'package:flutter/material.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(context) => MaterialApp( home: HomePage() ); } class HomePage extends StatelessWidget { @override Widget build(context) => Scaffold( appBar: AppBar(title: Text("test")), drawer: MediaQuery.of(context).size.width < 500 ? Drawer( child: Menu(), ) : null, body: SafeArea( child:Center( child: MediaQuery.of(context).size.width < 500 ? Content() : Row( children: [ Container( width: 200.0, child: Menu() ), Container( width: MediaQuery.of(context).size.width-200.0, child: Content() ) ] ) ) ) ); } class Menu extends StatelessWidget { @override Widget build(context) => ListView( children: [ FlatButton( onPressed: () {}, child: ListTile( leading: Icon(Icons.looks_one), title: Text("First Link"), ) ), FlatButton( onPressed: () {}, child: ListTile( leading: Icon(Icons.looks_two), title: Text("Second Link"), ) ) ] ); } class Content extends StatelessWidget { final List<String> elements = ["Zero", "One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "A Million Billion Trillion", "A much, much longer text that will still fit"]; @override Widget build(context) => GridView.builder( itemCount: elements.length, gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent( maxCrossAxisExtent: 130.0, crossAxisSpacing: 20.0, mainAxisSpacing: 20.0, ), itemBuilder: (context, i) => Card( child: Center( child: Padding( padding: EdgeInsets.all(8.0), child: Text(elements[i]) ) ) ) ); }
이것은 Flutter의 반응형 UI에 대한 일반적인 소개로 필요한 대부분의 내용입니다. 해당 애플리케이션의 대부분은 앱의 특정 UI에 따라 달라지며 앱을 반응형으로 만들기 위해 무엇을 할 수 있는지 정확히 지적하기 어렵고 선호도에 따라 다양한 접근 방식을 취할 수 있습니다. 하지만 이제 일반적인 앱 요소와 UI 흐름에 대해 생각하면서 반응형 앱으로 더 완전한 예를 만드는 방법을 살펴보겠습니다.
상황에 맞게 활용하기: 반응형 앱 만들기
지금까지는 화면만 있습니다. URL 기반 탐색이 작동하는 2화면 앱으로 확장해 봅시다!
반응형 로그인 페이지 만들기
앱에 로그인 페이지가 있을 가능성이 있습니다. 반응형으로 만들려면 어떻게 해야 할까요?
모바일 장치의 로그인 화면은 일반적으로 서로 매우 유사합니다. 사용 가능한 공간은 많지 않습니다. 그것은 일반적으로 위젯 주위에 약간의 Padding
이 있는 Column
이며 사용자 이름과 비밀번호를 입력하기 위한 TextField
및 로그인 버튼을 포함합니다. , 각 TextField
에 대한 TextEditingController
) 모바일 앱의 로그인 페이지는 다음과 같을 수 있습니다.
Scaffold( body: Container( padding: const EdgeInsets.symmetric( vertical: 30.0, horizontal: 25.0 ), child: Column( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ Text("Welcome to the app, please log in"), TextField( decoration: InputDecoration( labelText: "username" ) ), TextField( obscureText: true, decoration: InputDecoration( labelText: "password" ) ), RaisedButton( color: Colors.blue, child: Text("Log in", style: TextStyle(color: Colors.white)), onPressed: () {} ) ] ), ), )
모바일 장치에서는 괜찮아 보이지만 매우 넓은 TextField
가 더 큰 화면은 물론 태블릿에서는 거슬리기 시작합니다. 그러나 휴대폰마다 화면 크기가 다르기 때문에 고정 너비를 결정할 수 없으며 어느 정도의 유연성을 유지해야 합니다.
예를 들어, 실험을 통해 최대 너비가 500이어야 함을 알 수 있습니다. 음, Container
의 constraints
을 500으로 설정합니다(이전 예제에서 Padding
대신 Container
를 사용했습니다. ) 그리고 우리는 갈 수 있습니다. 그렇지 않습니다. 그렇게 하면 로그인 위젯이 화면 왼쪽에 고정되어 전체를 늘리는 것보다 더 나쁠 수 있기 때문입니다. 따라서 다음과 같이 Center
위젯을 래핑합니다.
Center( child: Container( constraints: BoxConstraints(maxWidth: 500), padding: const EdgeInsets.symmetric( vertical: 30.0, horizontal: 25.0 ), child: Column(/* ... \*/) ) )
이미 괜찮아 LayoutBuilder
또는 MediaQuery.of(context).size
를 사용할 필요조차 없었습니다. 하지만 한 단계 더 나아가 이것을 아주 좋게 보이게 합시다. 내 보기에는 전경 부분이 배경과 어떤 식으로든 분리되어 있으면 더 좋아 보일 것입니다. 입력 위젯을 사용하여 Container
뒤에 있는 배경색을 지정하고 전경 Container
를 흰색으로 유지하여 이를 달성할 수 있습니다. 좀 더 보기 좋게 만들기 위해 대형 기기에서 Container
가 화면 상단과 하단으로 늘어나지 않도록 하고 모서리를 둥글게 하고 두 레이아웃 간에 멋진 애니메이션 전환을 제공합니다.
이 모든 것은 이제 배경색을 설정하고 더 큰 화면의 측면뿐만 아니라 Container
주위에 패딩을 추가하기 위해 LayoutBuilder
와 외부 Container
가 필요합니다. 또한 패딩 양의 변경을 애니메이션으로 만들려면 해당 외부 Container
를 AnimatedContainer
로 전환하면 됩니다. 여기에는 애니메이션 duration
이 필요합니다. 이 시간은 0.5초로 설정됩니다. 이는 Duration(milliseconds: 500)
입니다. 암호.
반응형 로그인 페이지의 예는 다음과 같습니다.
class LoginPage extends StatelessWidget { @override Widget build(context) => Scaffold( body: LayoutBuilder( builder: (context, constraints) { return AnimatedContainer( duration: Duration(milliseconds: 500), color: Colors.lightGreen[200], padding: constraints.maxWidth < 500 ? EdgeInsets.zero : EdgeInsets.all(30.0), child: Center( child: Container( padding: EdgeInsets.symmetric( vertical: 30.0, horizontal: 25.0 ), constraints: BoxConstraints( maxWidth: 500, ), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(5.0), ), child: Column( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ Text("Welcome to the app, please log in"), TextField( decoration: InputDecoration( labelText: "username" ) ), TextField( obscureText: true, decoration: InputDecoration( labelText: "password" ) ), RaisedButton( color: Colors.blue, child: Text("Log in", style: TextStyle(color: Colors.white)), onPressed: () { Navigator.pushReplacement( context, MaterialPageRoute( builder: (context) => HomePage() ) ); } ) ] ), ), ) ); } ) ); }
보시다시피 RaisedButton
의 onPressed
를 HomePage
라는 화면으로 이동하는 콜백으로 변경했습니다(예: GridView
및 메뉴 또는 서랍으로 이전에 빌드한 보기일 수 있음). 하지만 이제 그 탐색 부분에 초점을 맞출 것입니다.
명명된 경로: 앱의 탐색을 적절한 웹 앱처럼 만들기
웹 앱의 일반적인 기능은 URL을 기반으로 화면을 변경하는 기능입니다. 예를 들어 https://appurl/login
으로 이동하면 https://appurl/somethingelse 와 다른 것을 제공해야 https://appurl/somethingelse
. 사실 Flutter는 두 가지 목적을 가진 명명된 route 를 지원합니다.
- 웹 앱에는 이전 문장에서 언급한 바로 그 기능이 있습니다.
- 모든 앱에서 앱의 경로를 미리 정의하고 이름을 지정한 다음 이름을 지정하는 것만으로 경로를 탐색할 수 있습니다.
그렇게 하려면 MaterialApp
생성자를 다음과 같이 변경해야 합니다.
MaterialApp( initialRoute: "/login", routes: { "/login": (context) => LoginPage(), "/home": (context) => HomePage() } );
그런 다음 Navigator.push(context, route) 및 Navigator.pushReplacement(context, route)
(context, route) 대신 Navigator.push(context, route)
Navigator.pushNamed(context, routeName)
및 Navigator.pushReplacementNamed(context, routeName)
) 를 사용하여 다른 경로로 전환할 수 있습니다.
다음은 이 기사의 나머지 부분에서 구축한 가상 앱에 적용된 것입니다. DartPad에서는 명명된 경로가 실제로 작동하는 것을 볼 수 없으므로 flutter run
을 사용하여 자신의 컴퓨터에서 이것을 시도하거나 작동 중인 예제를 확인해야 합니다.
import 'package:flutter/material.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(context) => MaterialApp( initialRoute: "/login", routes: { "/login": (context) => LoginPage(), "/home": (context) => HomePage() } ); } class LoginPage extends StatelessWidget { @override Widget build(context) => Scaffold( body: LayoutBuilder( builder: (context, constraints) { return AnimatedContainer( duration: Duration(milliseconds: 500), color: Colors.lightGreen[200], padding: constraints.maxWidth < 500 ? EdgeInsets.zero : const EdgeInsets.all(30.0), child: Center( child: Container( padding: const EdgeInsets.symmetric( vertical: 30.0, horizontal: 25.0 ), constraints: BoxConstraints( maxWidth: 500, ), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(5.0), ), child: Column( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ Text("Welcome to the app, please log in"), TextField( decoration: InputDecoration( labelText: "username" ) ), TextField( obscureText: true, decoration: InputDecoration( labelText: "password" ) ), RaisedButton( color: Colors.blue, child: Text("Log in", style: TextStyle(color: Colors.white)), onPressed: () { Navigator.pushReplacementNamed( context, "/home" ); } ) ] ), ), ) ); } ) ); } class HomePage extends StatelessWidget { @override Widget build(context) => Scaffold( appBar: AppBar(title: Text("test")), drawer: MediaQuery.of(context).size.width < 500 ? Drawer( child: Menu(), ) : null, body: SafeArea( child:Center( child: MediaQuery.of(context).size.width < 500 ? Content() : Row( children: [ Container( width: 200.0, child: Menu() ), Container( width: MediaQuery.of(context).size.width-200.0, child: Content() ) ] ) ) ) ); } class Menu extends StatelessWidget { @override Widget build(context) => ListView( children: [ FlatButton( onPressed: () {}, child: ListTile( leading: Icon(Icons.looks_one), title: Text("First Link"), ) ), FlatButton( onPressed: () {}, child: ListTile( leading: Icon(Icons.looks_two), title: Text("Second Link"), ) ), FlatButton( onPressed: () {Navigator.pushReplacementNamed( context, "/login");}, child: ListTile( leading: Icon(Icons.exit_to_app), title: Text("Log Out"), ) ) ] ); } class Content extends StatelessWidget { final List<String> elements = ["Zero", "One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "A Million Billion Trillion", "A much, much longer text that will still fit"]; @override Widget build(context) => GridView.builder( itemCount: elements.length, gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent( maxCrossAxisExtent: 130.0, crossAxisSpacing: 20.0, mainAxisSpacing: 20.0, ), itemBuilder: (context, i) => Card( child: Center( child: Padding( padding: EdgeInsets.all(8.0), child: Text(elements[i]) ) ) ) ); }
Flutter Adventure와 함께 앞으로
이것은 더 큰 화면, 특히 웹에서 Flutter로 무엇을 할 수 있는지에 대한 아이디어를 제공합니다. 그것은 사용하기 매우 쉬운 멋진 프레임워크이며 극단적인 크로스 플랫폼 지원으로 인해 배우고 사용을 시작하는 것이 더 중요합니다. 그러니 웹 앱용 Flutter도 믿고 시작하세요!
추가 리소스
- "데스크탑 셸", GitHub
데스크탑에서 Flutter의 현재, 항상 최신 상태 - Flutter에 대한 "데스크톱 지원", Flutter
완전히 지원되는 데스크탑 플랫폼에 대한 정보 - Flutter에 대한 웹 지원", Flutter
웹용 Flutter에 대한 정보 - "모든 샘플", Flutter
Flutter 샘플 및 앱의 선별된 목록