Movatterモバイル変換


[0]ホーム

URL:


Skip to content
DEV Community
Log in Create account

DEV Community

Touré Holder
Touré Holder

Posted on

     

Creating adaptive layouts with Flutter

In this tutorial you'll learn how to easily make a UI layout for your flutter app that adapts to different screen sizes with theadaptive_layout package.

The next few sections show how to build a simple UI that displays a list of movie posters and titles and allows the user to see more details of the movie when they tap an item in the list.

Small screen UI

On small, phone-sized, screens when the user taps an item they will navigate to a different screen with the details.

Large screen UI

On large screens, when the user taps a list item the details are displayed on the right side of the list and there is no navigation.

Live demo

Even better than an image,see a live demo of what we'll be building.

Let's go!

We'll take care of the behavior expected for small screens first, then adapt our implementation for larger screens. These are the steps:

1. Create a new app

Go ahead andcreate a new flutter app.

flutter create adaptive_layout_example
Enter fullscreen modeExit fullscreen mode

2. Provide the data set

We'll be using a hard-coded data set, so we can focus our efforts on the UI layout.

  1. Under thelib folder, create a folder calleddata with a file nameddata.dart.
  2. Copy the contents ofthis file into your file.

See the changes

3. Create the model

Create aMovie class with a static function that parses the data into a list ofMovies and returns the list.

  1. Under thelib folder, create a folder calledmodel with a filemovie.dart.
  2. Paste the following content into the file.
import'../data/data.dart';classMovie{finalintid;finalStringtitle;finalStringoverview;finalStringposterPath;Movie({requiredthis.id,requiredthis.title,requiredthis.overview,requiredthis.posterPath,});staticList<Movie>getList()=>data.map((element)=>Movie(id:element['id'],title:element['title'],overview:element['overview'],posterPath:element['poster_path'],),).toList();}constStringLARGE_POSTER_BASE_URL="https://image.tmdb.org/t/p/w300";constStringSMALL_POSTER_BASE_URL="https://image.tmdb.org/t/p/w92";
Enter fullscreen modeExit fullscreen mode

See the changes

4. Create the movie list page

Now let's create a page that will display the list of movies:

  1. Create a new folder underlib calledpages.
  2. Within thepages folder create a file namedmovie_list_page.dart.
  3. Inmovie_list_page.dart create twoStatelessWidgets: one calledMovieListPage and another calledMovieListView, with this content.
classMovieListPageextendsStatelessWidget{constMovieListPage({Key?key}):super(key:key);@overrideWidgetbuild(BuildContextcontext){finalmovies=Movie.getList();returnScaffold(appBar:AppBar(title:Text('Movies'),),body:MovieListView(movies:movies,onTapItem:(Moviemovie){// TODO: Navigate to detail screen},),);}}classMovieListViewextendsStatelessWidget{finalList<Movie>movies;finalFunction(Movie)onTapItem;constMovieListView({Key?key,requiredthis.movies,requiredthis.onTapItem,}):super(key:key);@overrideWidgetbuild(BuildContextcontext){returnListView.builder(itemCount:movies.length,itemBuilder:(context,index){finalmovie=movies[index];returnListTile(key:Key('list_item_$index'),leading:Image.network(SMALL_POSTER_BASE_URL+movie.posterPath),title:Text('${movie.title}'),contentPadding:EdgeInsets.all(12.0),onTap:(){onTapItem.call(movie);},);},);}}
Enter fullscreen modeExit fullscreen mode

Observe thatMovieListPage is a container widget for the presentationalMovieListView widget.

Now, remove the framework generatedMyHomePage and_MyHomePageState classes fromlib/main.dart and useMovieListPage() as thehome widget for theMaterialApp. Thelib/main.dart file should now look like this:

voidmain(){runApp(MyApp());}classMyAppextendsStatelessWidget{@overrideWidgetbuild(BuildContextcontext){returnMaterialApp(title:'Adaptive Layout Tutorial',theme:ThemeData(primarySwatch:Colors.blue,),// Use `MovieListPage()` as the `home` widgethome:MovieListPage(),);}}
Enter fullscreen modeExit fullscreen mode

The list page should now be displayed when the app starts:

See the changes

5. Create the movie detail page

Now let's create a page that will display more details of a given movie:

  1. Within thepages folder create a file namedmovie_detail_page.dart.
  2. Similarly, inmovie_detail_page.dart create twoStatelessWidgets: a container widget calledMovieDetailPage and a presentationalMovieDetailView widget, with this content:
classMovieDetailPageextendsStatelessWidget{finalMoviemovie;constMovieDetailPage(this.movie,{Key?key,}):super(key:key);@overrideWidgetbuild(BuildContextcontext){returnScaffold(appBar:AppBar(),body:MovieDetailView(movie),);}}classMovieDetailViewextendsStatelessWidget{finalMoviemovie;constMovieDetailView(this.movie,{Key?key,}):super(key:key);@overrideWidgetbuild(BuildContextcontext){returnSingleChildScrollView(padding:EdgeInsets.all(24.0),child:Column(crossAxisAlignment:CrossAxisAlignment.center,children:[ClipRRect(borderRadius:BorderRadius.circular(10.0),child:Image.network(LARGE_POSTER_BASE_URL+movie.posterPath),),SizedBox(height:24.0),Text(movie.title,style:Theme.of(context).textTheme.headline4,textAlign:TextAlign.center,),SizedBox(height:24.0),Text(movie.overview,style:Theme.of(context).textTheme.subtitle1,textAlign:TextAlign.center,),],),);}}
Enter fullscreen modeExit fullscreen mode

For simplicity, we are just displaying the poster image, title and overview of the movie in the detail view.

See the changes

6. Navigate to the detail screen when an item is tapped

Update theonTapItem() callback of theMovieListView in theMovieListPage widget, so we can navigate to the detail screen and back.

// Within the `MovieListPage` widgetonTapItem:(Moviemovie){Navigator.push(context,MaterialPageRoute(builder:(context)=>MovieDetailPage(movie)),);},
Enter fullscreen modeExit fullscreen mode

With that, our small screen UI is done! You should now be able to navigate to the detail screen and back:

See the changes

7. Convert movie list page into home page

As a first step to making our layout adaptive to different screen sizes:

  1. ConvertMovieListPage to aStatefulWidget. (In VS Code place the cursor onStatelessWidget, press CTRL/CMD + . and select "Convert to StatefulWidget".)
  2. For semantic's sake rename it toHomePage and the file tohome_page.dart.

On small screens theHomePage widget will contain only theMovieListView. On large screens theHomePage widget will contain theMovieListView and theMovieDetailView side by side. You'll understand why we converted the widget to aStatefulWidget in just a bit.

See the changes

8. Make the home page adaptive with theadaptive_layout package

To help us implement us display different layouts on different screen sizes we'll use theadaptive_layout package.

  1. Install it with
$flutter pub add adaptive_layout
Enter fullscreen modeExit fullscreen mode

This will add a line like this to your package's pubspec.yaml (and run an implicit dart pub get).

dependencies:adaptive_layout:^0.1.3
Enter fullscreen modeExit fullscreen mode
  1. Now inhome_page.dart, import the package:
import'package:adaptive_layout/adaptive_layout.dart';
Enter fullscreen modeExit fullscreen mode
  1. Update thebody of theScaffold ofHomePage to be anAdaptiveLayout with asmallLayout and alargeLayout like so:
classHomePageextendsStatefulWidget{constHomePage({Key?key}):super(key:key);@override_HomePageStatecreateState()=>_HomePageState();}class_HomePageStateextendsState<HomePage>{@overrideWidgetbuild(BuildContextcontext){finalmovies=Movie.getList();returnScaffold(appBar:AppBar(title:Text('Movies'),),// Now using an `AdaptiveLayout` as the `body`body:AdaptiveLayout(// Provide `MovieListView` as the `smallLayout`smallLayout:MovieListView(movies:movies,onTapItem:(Moviemovie){Navigator.push(context,MaterialPageRoute(builder:(context)=>MovieDetailPage(movie)),);},),// Provide a `Row` as the `largeLayout`largeLayout:Row(children:[Expanded(child:MovieListView(movies:movies,onTapItem:(Moviemovie){// TODO: Update detail view to the right},),),Expanded(child:Container(child:Center(child:Text('Select a movie from the list on the left to see the details here.',),),),)],),),);}}
Enter fullscreen modeExit fullscreen mode

ThesmallLayout is just ourMovieListView and thelargeLayout is a 2-columnRow with theMovieListView in the firstExpanded column.

At this point, on small screens the app should have the same behavior as before of navigating to the detail page when a list item is tapped. On large screens we should now see the list on the left-hand side of the screen and the message "Select a movie from the list on the left to see the details here" in the center of the right-hand side of the screen:

However, nothing happens when we tap a list item on large screens. Let's tackle that next.

9. Display the detail view when an item is tapped on large screens

With a few new lines of code in thehome_page.dart file we can display the detail view on the right hand side when an item is tapped on large screens.

  1. Create a private property of the_HomePageState of typeMovie? named_selectedMovie.
class_HomePageStateextendsState<HomePage>{Movie?_selectedMovie;...
Enter fullscreen modeExit fullscreen mode
  1. Update theonTapItem() callback of theMovieListView in thelargeLayout of theAdaptiveLayout widget to update the_selectedMovie property withsetState. This is why we converted the widget to aStatefulWidget a few steps ago.
// Within the `largeLayout`onTapItem:(Moviemovie){setState((){_selectedMovie=movie;});},
Enter fullscreen modeExit fullscreen mode
  1. Update the secondExpandable of theRow to display the message if_selectedMovie isnull and theMovieDetailView otherwise.
Expanded(child:Container(child:_selectedMovie==null?Center(child:Text('Select a movie from the list on the left to see the details here.',),):MovieDetailView(_selectedMovie!,),),)
Enter fullscreen modeExit fullscreen mode

And... we're done! 🎉

On small, phone-sized, screens when the user taps an item they will navigate to a different screen with the details and on larger screens the details are displayed on the right side of the list and there is no navigation.

See the changes


BONUS: Make it look a little better on large screens

To make our app look a little better, let's leave the selected list item highlighted and put a divider line between the list view and the detail view with a few new lines of code in thehome_page.dart file.

  1. Add a new attributeselectedId attribute toMovieListView, so it knows which item a selected.
classMovieListViewextendsStatelessWidget{finalList<Movie>movies;finalFunction(Movie)onTapItem;finalint?selectedId;constMovieListView({Key?key,requiredthis.movies,requiredthis.onTapItem,this.selectedId,}):super(key:key);...
Enter fullscreen modeExit fullscreen mode
  1. Update theitemBuilder of theListView.builder to wrap theListTile in aContainer and give theContainer color if theListTile's movie is the selected one.
itemBuilder:(context,index){finalmovie=movies[index];finalcolor=movie.id==selectedId?Theme.of(context).primaryColor.withOpacity(0.25):Colors.transparent;returnContainer(color:color,child:ListTile(key:Key('list_item_$index'),leading:Image.network(SMALL_POSTER_BASE_URL+movie.posterPath),title:Text('${movie.title}'),contentPadding:EdgeInsets.all(12.0),onTap:(){onTapItem.call(movie);},),);},
Enter fullscreen modeExit fullscreen mode
  1. Update the firstExpandable of theRow in thelargeLayout of theAdaptiveLayout to provide the selected movie'sid to theMovieListView.
Expanded(child:MovieListView(movies:movies,onTapItem:(Moviemovie){setState((){_selectedMovie=movie;});},selectedId:_selectedMovie?.id,),),
Enter fullscreen modeExit fullscreen mode
  1. Lastly, let's add a border to the left-hand side of the detail view'sContainer with someBoxDecoration.
Expanded(child:Container(decoration:BoxDecoration(border:Border(left:BorderSide(color:Colors.grey[300]!,width:1.0,),),),child:_selectedMovie==null?Center(child:Text('Select a movie from the list on the left to see the details here.',),):MovieDetailView(_selectedMovie!,),),)
Enter fullscreen modeExit fullscreen mode

Of course the sky's the limit with the design, but I think our app is minimally presentable now:

See the changes

BONUS: Tests

I chose not to TDD this so we could focus on the layout, but you can findthe tests here in the source code.

Note in the tests that I useWidgetTester to specify a small screen size in asetUp function like this:

setUp((){binding.window.physicalSizeTestValue=Size(AdaptiveLayout.getBreakpoints().mediumScreenMinWidth-10,500,);binding.window.devicePixelRatioTestValue=1.0;});
Enter fullscreen modeExit fullscreen mode

and to specify a large screen size like this:

setUp((){binding.window.physicalSizeTestValue=Size(AdaptiveLayout.getBreakpoints().largeScreenMinWidth+10,500,);binding.window.devicePixelRatioTestValue=1.0;});
Enter fullscreen modeExit fullscreen mode

Head over the theGithub repo to see the complete source code with a separate commit for each step 😉.

Congrats on making it to the end and thanks for following along! 👏 ❤️ ⭐ 🦄 🔖

Top comments(3)

Subscribe
pic
Create template

Templates let you quickly answer FAQs or store snippets for re-use.

Dismiss
CollapseExpand
 
mvolpato profile image
Michele Volpato
I'm a full stack developer who loves to program using Swift or Flutter.
  • Location
    Utrecht, the Netherlands
  • Work
    Software engineer at Touchwonders
  • Joined

I added this story to my newsletterishouldgotosleep.com/this-week-in-...

CollapseExpand
 
toureholder profile image
Touré Holder
Yet another indie hacking second-career coder
  • Location
    Brasília, Brazil
  • Joined

Awesome newsletter! And thanks for adding the article ❤️

CollapseExpand
 
mirxtremapps profile image
mirxtrem-apps
I am frontend developer with Flutter & Angular
  • Location
    Colombia
  • Work
    Junior frontend dev at 4-72
  • Joined

excelent!!

Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment'spermalink.

For further actions, you may consider blocking this person and/orreporting abuse

Yet another indie hacking second-career coder
  • Location
    Brasília, Brazil
  • Joined

More fromTouré Holder

DEV Community

We're a place where coders share, stay up-to-date and grow their careers.

Log in Create account

[8]ページ先頭

©2009-2025 Movatter.jp