本文是medium的一篇文章的翻譯,再加上本身的一點理解,已獲得做者的贊成。android
主要講的是在平板和手機中,處理適配不一樣屏幕的問題。git
在Android中,咱們處理比較大尺寸的屏幕,例如平板電腦。咱們能夠用過最小寬度限定符來定義相應尺寸的佈局文件的名稱。 bash
Android中的Fragment本質上是可重用的組件,能夠在屏幕中使用。Fragment有本身的佈局文件,而且 Java/Kotlin的類會去控制Fragment的生命週期。這是一項至關大的工做,須要大量代碼才能開始工做。app
下面,咱們先來看看在Flutter中處理屏幕方向,而後處理Flutter的屏幕尺寸。less
當咱們使用屏幕方向的時候,咱們但願使用屏幕的所有寬度和顯示儘量的最大信息量。ide
下面的示例,在兩個方向上建立一個基本的配置文件頁面,並根據不一樣的方向去構建佈局,以最大限度地使用屏幕寬度。 函數
在概念上,解決方法跟Android的方法是相似的。咱們也要弄兩個佈局(這裏的佈局並非Android中的佈局文件,由於在Flutter中 沒有佈局文件),一個用於縱向,一個用於橫向。而後當設備的方向改變的時候,rebuild更新咱們的佈局。佈局
Flutter中提供了一個OrientationBuilder的小部件。OrientationBuilder能夠在設備的方向發生改變的時候,從新構建佈局。ui
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。
@override
Widget 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爲例,咱們有一個電子郵件的列表,當咱們點擊其中一個的時候, 會打開一個顯示詳細信息的頁面,其中包含郵件內容。
若是咱們在平板電腦中使用相同的佈局,那將是一個至關大的空間浪費。那麼咱們能夠作些什麼來解決它呢? 咱們能夠在同一屏幕上同時擁有主列表和詳細視圖,由於咱們有足夠可用的屏幕空間。
先看看Android中是如何解決這個問題的。Android從主列表和詳細信息視圖中建立稱爲Fragment的可重用組件。 Fragment能夠與屏幕分開定義,只是簡單地將fragment添加到屏幕中而不是重複的兩套代碼。
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,以切換到第二種佈局。
代碼的實現我是用本身的代碼進行說明,跟原做者的代碼實現的思路和結果是同樣的。 下面實現的是,有一個數字列表,點擊後顯示詳細視圖。
//須要定義一個回調,決定是在同一個屏幕上顯示更改詳細視圖仍是在較小的屏幕上導航到不一樣界面。
typedef Null ItemSelectedCallback(int value);
//列表的Widget
class ListWidget extends StatelessWidget {
ItemSelectedCallback itemSelectedCallback;
ListWidget({@required this.itemSelectedCallback});
@override
Widget build(BuildContext context) {
return new ListView.builder(
itemCount: 20,
itemBuilder: (context, index) {
return new ListTile(
title: new Text("$index"),
onTap: () {
//設置點擊事件
this.itemSelectedCallback(index);
},
);
});
}
}
複製代碼
//詳細視圖的Widget,簡單的顯示一個文本
class DetailWidget extends StatelessWidget {
final int data;
DetailWidget(this.data);
@override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
color: Colors.blue,
child: new Center(
child: new Text("詳細視圖:$data"),
),
),
);
}
}
複製代碼
請注意,這些Widget不是屏幕,只是咱們將在屏幕上使用的小部件。
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
bool isLargeScreen; //是不是大屏幕
var selectValue = 0; //保存選擇的內容
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text(widget.title),
),
body: new OrientationBuilder(builder: (context, orientation) {
print("width:${MediaQuery.of(context).size.width}");
//判斷屏幕寬度
if (MediaQuery.of(context).size.width > 600) {
isLargeScreen = true;
} else {
isLargeScreen = false;
}
//兩個widget是放在一個Row中進行顯示,若是是小屏幕的話,用一個空的Container進行佔位
//若是是大屏幕的話,則用Expanded進行屏幕的劃分並顯示詳細視圖
return new Row(
mainAxisSize: MainAxisSize.max,
children: <Widget>[
new Expanded(child: new ListWidget(
itemSelectedCallback: (value) {
//定義列表項的點擊回調
if (isLargeScreen) {
selectValue = value;
setState(() {});
} else {
Navigator.of(context)
.push(new MaterialPageRoute(builder: (context) {
return new DetailWidget(value);
}));
}
},
)),
isLargeScreen
? new Expanded(child: new DetailWidget(selectValue))
: new Container()
],
);
}),
);
}
}
複製代碼
這是應用程序的主頁面。有兩個變量: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…