本文是medium的一篇文章的翻譯,再加上自己的一點理解,已得到作者的同意。
主要講的是在平板和手機中,處理適配不同螢幕的問題。
原文地址:medium.com/flutter-community/developing-for-multiple-screen-sizes-and-orientations-in-flutter-fragments-in-flutter-a4c51b849434
在Android中,我們處理比較大尺寸的螢幕,例如平板電腦。我們可以用過最小寬度限定符來定義相應尺寸的佈局檔案的名稱。
Android中的Fragment本質上是可重用的元件,可以在螢幕中使用。Fragment有自己的佈局檔案,並且Java/Kotlin的類會去控制Fragment的生命週期。這是一項相當大的工作,需要大量程式碼才能開始工作。
下面,我們先來看看在Flutter中處理螢幕方向,然後處理Flutter的螢幕尺寸。
當我們使用螢幕方向的時候,我們希望使用螢幕的全部寬度和顯示儘可能的最大資訊量。
下面的示例,在兩個方向上建立一個基本的配置檔案頁面,並根據不同的方向去構建佈局,以最大限度地使用螢幕寬度。
在概念上,解決方法跟Android的方法是類似的。我們也要弄兩個佈局(這裡的佈局並不是Android中的佈局檔案,因為在Flutter中沒有佈局檔案),一個用於縱向,一個用於橫向。然後當裝置的方向改變的時候,rebuild更新我們的佈局。
Flutter中提供了一個OrientationBuilder的小部件。OrientationBuilder可以在裝置的方向發生改變的時候,重新構建佈局。
typedef OrientationWidgetBuilder = Widget Function(BuildContext context, Orientation orientation);class OrientationBuilder extends StatelessWidget { /// Creates an orientation builder. const OrientationBuilder({ Key key, @required this.builder, }) : assert(builder != null), super(key: key); /// Builds the widgets below this widget given this widget's orientation. /// A widget's orientation is simply a factor of its width relative to its /// height. For example, a [Column] widget will have a landscape orientation /// if its width exceeds its height, even though it displays its children in /// a vertical array. final OrientationWidgetBuilder builder; Widget _buildWithConstraints(BuildContext context, BoxConstraints constraints) { // If the constraints are fully unbounded (i.e., maxWidth and maxHeight are // both infinite), we prefer Orientation.portrait because its more common to // scroll vertically then horizontally. final Orientation orientation = constraints.maxWidth > constraints.maxHeight ? Orientation.landscape : Orientation.portrait; return builder(context, orientation); } @override Widget build(BuildContext context) { return LayoutBuilder(builder: _buildWithConstraints); } }複製程式碼
OrientationBuilder有一個builder函式來構建我們的佈局。當裝置的方向發生改變的時候,就會呼叫builder函式。orientation的值有兩個,Orientation.landscape和Orientation.portrait。
@overrideWidget build(BuildContext context){ return Scaffold( appBar:AppBar(), body:OrientationBuilder( builder :( context,orientation){ return orientation == Orientation. portrait ?_buildVerticalLayout() :_ buildHorizontalLayout(); }, ), ); }複製程式碼
在上面的例子中,我們檢查螢幕是否處於豎屏模式並構建豎屏的佈局,否則我們為螢幕構建橫屏的佈局。_buildVerticalLayout()和_buildHorizontalLayout()是編寫的用於建立相應佈局的方法。
當我們處理更大的螢幕尺寸的時候,我們希望螢幕適應地去使用螢幕上的可用空間。最直接的方法就是為平板電腦和手機建立兩種不同的佈局(這裡的的佈局,表示螢幕的可視部分)。然而,這裡會涉及許多不必要的程式碼,並且程式碼需要被重用。
那麼我們如何解決這個問題呢?
首先,讓我們來看看它最常見的用例。
這裡討論下“Master-Detail Flow”。對於應用程式來說,你會看到一個常見的場景。其中有一個Master的列表,然後當你點選列表項Item的時候,就會跳到另一個顯示Detail詳細資訊的螢幕。以Gmail為例,我們有一個電子郵件的列表,當我們點選其中一個的時候,會開啟一個顯示詳細資訊的頁面,其中包含郵件內容。
這個應用程式只儲存一個數字列表,並在點選的時候,跳轉到只顯示一個數字的詳細檢視。
如果我們在平板電腦中使用相同的佈局,那將是一個相當大的空間浪費。那麼我們可以做些什麼來解決它呢?我們可以在同一螢幕上同時擁有主列表和詳細檢視,因為我們有足夠可用的螢幕空間。
因此FragmentA是顯示主列表的fragment,FragmentB是顯示詳細資訊的fragment。在較小寬度的佈局中,單擊列表項Item,會導航到單獨的頁面來顯示詳細檢視FragmentB,而在平板電腦中,fragmentB將跟fragmentA顯示在同一螢幕上。
This is where the power of Flutter comes in.
Every widget in Flutter is by nature, reusable.
Every widget in Flutter is like a Fragment.
我們需要做的就是定義兩個Widget,一個用於顯示主列表,一個用於顯示詳細檢視。實際上,這些就是類似的fragments。
我們只需要檢查裝置是否具有足夠的寬度來處理列表檢視和詳細檢視。如果是,我們在同一螢幕上顯示兩個widget。如果裝置沒有足夠的寬度來包含兩個介面,那我們只需要在螢幕中展示主列表,點選列表項後導航到獨立的螢幕來顯示詳細檢視。
首先,我們需要檢查裝置的寬度,看看我們是否可以使用更大的佈局,而不是使用更小的佈局。為了獲得寬度,我們使用MediaQuery來獲取寬度,Size中的寬度和高度的單位是dp。
MediaQuery.of(context).size.width複製程式碼
讓我們將最小寬度設定為600dp,以切換到第二種佈局。
程式碼的實現我是用自己的程式碼進行說明,跟原作者的程式碼實現的思路和結果是一樣的。下面實現的是,有一個數字列表,點選後顯示詳細檢視。
請注意,這些Widget不是螢幕,只是我們將在螢幕上使用的小部件。
這是應用程式的主頁面。有兩個變數:selectedValue用於儲存選定的列表項,isLargeScreen表示螢幕是否足夠大。
這裡還用了OrientatinBuilder包裹在最外面,所以當如果手機被旋轉到橫屏的時候,並且有足夠的寬度來顯示兩個Widget的話,那它將以這種方式重建。(如果不需要這功能,那可以把OrientatinBuilder去掉就行)。
isLargeScreen ? new Expanded(child: new DetailWidget(selectValue)) : new Container()複製程式碼
如果isLargeScreen為true,則新增一個Expanded控制元件內部包裹DetailWidget。Expanded允許每個小部件通過設定Flex屬性來填充螢幕。
如果isLargeScreen為false,則返回一個空的Container就行了,ListWidget所在的Expanded會自動填充滿螢幕。
//定義列表項的點選回撥 if (isLargeScreen) { selectValue = value; setState(() { }); } else { Navigator.of(context) .push(new MaterialPageRoute(builder: (context) { return new DetailWidget(value); })); }複製程式碼
定義列表項的點選回撥,如果螢幕較小,我們需要導航到不同的頁面。如果螢幕較大,就不需要導航到不同的螢幕,因為DetailWidget就在這個螢幕裡面,只需呼叫setState()去重新整理介面就行。
現在我們有一個功能正常的應用程式,能夠適應不同大小的螢幕和方向。
if (MediaQuery.of(context).size.width > 600) { isLargeScreen = true; } else { isLargeScreen = false; }return isLargeScreen? _buildTabletLayout() : _buildMobileLayout();複製程式碼
Size size = MediaQuery.of(context).size;double width = size.width > size.height ? size.height : size.width;if(width > 600) { // Do something for tablets here } else { // Do something for phones }複製程式碼
//強制豎屏 SystemChrome.setPreferredOrientations([ DeviceOrientation.portraitUp, DeviceOrientation.portraitDown ]); //強制橫屏 SystemChrome.setPreferredOrientations([ DeviceOrientation.landscapeLeft, DeviceOrientation.landscapeRight ]);複製程式碼
自己也跟著原作者擼了下demo,順便加了點註釋,方便自己理解。
自己的Github連結:github.com/LXD31256949…
原作者的Github連線:github.com/deven98/Flu…
來源:https://juejin.im/post/5c45abc0f265da617265c656#comment