效果圖依次如下:
一步一步摸索、查找資源,最後實現登錄界面,登錄成功後跳轉到首頁,從我的界面退出登錄,這個過程涉及到了:Widgets的使用(TextField、RaisedButton、SizedBox、Row……)、路由、交互……等相關知識,代碼有相應的註釋(有些是根據自己的理解來寫,會有不太貼切的地方,諒解一下),
完整代碼如下:
1.main.dar
import 'package:flutter/material.dart';
import 'package:flutter_app/screen/Home.dart';
import 'package:flutter_app/screen/LoginScreen.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter Demo',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
routes: {
/**
* 命名導航路由,啓動程序默認打開的是以'/'對應的界面LoginScreen()
* 凡是後面使用Navigator.of(context).pushNamed('/Home'),都會跳轉到Home(),
*/
'/': (BuildContext context) => new LoginScreen(),
'/Home': (BuildContext context) => new Home(),
},
}
}
2.1 LoginScreen.dart(類似LoginActivity、login.xml)
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class LoginScreen extends StatefulWidget {
@override
State<LoginScreen> createState() {
return new _LoginScreenState();
}
}
class _LoginScreenState extends State<LoginScreen> {
//用於登錄時判斷輸入的賬號、密碼是否符合要求
static bool _accountState, _passwordState = false;
//提示語
static String _checkHint;
//監聽賬號輸入框的文字變化
static TextEditingController _accountController = new TextEditingController();
//監聽密碼輸入框的文字變化
static TextEditingController _passwordController =
new TextEditingController();
//用於路由(就是界面的跳轉),當跳轉的事件沒有寫在build裏面時用到(我這裏抽到了loginButton裏面)
static BuildContext context1;
//校驗賬號是否符合條件
static void _checkAccount() {
//校驗賬號不爲空且長度大於7(自定義校驗條件)
if (_accountController.text.isNotEmpty &&
_accountController.text.trim().length > 7) {
_accountState = true;
} else {
_accountState = false;
}
}
//校驗密碼是否符合條件
static void _checkPassword() {
//校驗密碼不爲空且長度大於8小於等於15(自定義校驗條件)
if (_passwordController.text.isNotEmpty &&
_passwordController.text.length > 8 &&
_passwordController.text.length <= 15) {
_passwordState = true;
} else {
_passwordState = false;
}
}
//賬號輸入框樣式
static Widget buildAccountTextFied(TextEditingController controller) {
/**
*需要定製一下某些顏色時返回Theme,不需要時返回TextField(如後面的密碼)
* 修改輸入框顏色:沒有獲取焦點時爲hintColor,獲取焦點後爲:primaryColor
*/
return Theme(
data: new ThemeData(
primaryColor: Colors.amber, hintColor: Colors.greenAccent),
child: new TextField(
//鍵盤的樣式
keyboardType: TextInputType.text,
//監聽
controller: controller,
//最大長度
maxLength: 30,
//顏色跟hintColor
//最大行數
maxLines: 1,
//是否自動更正
autocorrect: true,
//是否自動化對焦
autofocus: false,
//是否是密碼格式(輸入的內容不可見)
obscureText: false,
//文本對齊方式
textAlign: TextAlign.start,
//輸入文本的樣式
style: TextStyle(fontSize: 20, color: Colors.black),
//允許輸入的格式(digitsOnly數字)
inputFormatters: [WhitelistingTextInputFormatter.digitsOnly],
//內容改變回調
onChanged: (account) {
print('change $account');
},
//提交觸發回調
onSubmitted: (account) {
print('submit $account');
},
//是否禁用
enabled: true,
decoration: InputDecoration(
fillColor: Colors.blue[50],
//底色
filled: true,
//有聚焦,labelText就會縮小到輸入框左上角,顏色primaryColor,沒聚焦前顏色跟hintColor
labelText: '賬號',
//聚焦時才顯示,顏色跟hintColor
hintText: '請輸入賬號',
//紅色
// errorText: '輸入錯誤',
//紅色,現在在輸入框的左下角,跟errorText位置一樣(優先顯示errorText)
// helperText: 'acount',
//輸入框內左側,有聚焦,顏色跟primaryColor
prefixIcon: Icon(Icons.person),
//輸入框左側的widget(可是text、icon……)
icon: Text(
'賬號:',
style: TextStyle(fontSize: 20, color: Colors.black),
),
//輸入框內右側的widget
suffixIcon: Icon(Icons.account_circle),
// 有聚焦顯示顏色跟hintColor,顯示在輸入框的右邊
suffixText: "後綴",
contentPadding: EdgeInsets.all(5),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(21.11), //邊框裁剪成11.11°角
borderSide: BorderSide(
color: Colors.black,
width: 25.0), //邊框顏色、大小沒有效果,所以使用返回的是Theme,
)),
),
);
}
//密碼輸入框樣式
static Widget buildPasswordTextFied(TextEditingController controller) {
return TextField(
//鍵盤的樣式
keyboardType: TextInputType.number,
//監聽
controller: controller,
//最大長度
maxLength: 30,
//顏色跟hintColor
//最大行數
maxLines: 1,
//是否自動更正
autocorrect: true,
//是否自動化對焦
autofocus: false,
//是否是密碼格式(輸入的內容不可見)
obscureText: true,
//文本對齊方式
textAlign: TextAlign.start,
//輸入文本的樣式
style: TextStyle(fontSize: 20, color: Colors.black),
//允許輸入的格式(digitsOnly數字)
inputFormatters: [WhitelistingTextInputFormatter.digitsOnly],
//內容改變回調
onChanged: (password) {
print('change $password');
},
//提交觸發回調
onSubmitted: (password) {
print('submit $password');
},
//是否禁用
enabled: true,
decoration: InputDecoration(
//底色配合filled:true纔有效
fillColor: Colors.blue[50],
filled: true,
//輸入聚焦以後,labelText就會縮小到輸入框左上角,紅色,沒聚焦前顏色跟hintColor
labelText: '密碼',
//聚焦時才顯示,顏色跟hintColor
hintText: '請輸入密碼',
//紅色
// errorText: '輸入錯誤',
//紅色,現在在輸入框的左下角,跟errorText位置一樣(優先顯示errorText)
// helperText: 'password',
//輸入框內左側widget,輸入聚焦時,顏色跟primaryColor
prefixIcon: Icon(Icons.lock),
//輸入框左側的widget(可是text、icon……)
icon: Text(
'密碼:',
style: TextStyle(fontSize: 20, color: Colors.black),
),
//輸入框內右側的widget
suffixIcon: Icon(Icons.remove_red_eye),
//聚焦時才顯示顏色跟hintColor,顯示在輸入框的右邊
suffixText: '後綴',
contentPadding: EdgeInsets.all(5),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(21.11), //邊框裁剪成11.11°角
borderSide: BorderSide(
color: Colors.black, width: 25.0), //沒有效果,想要修改就返回Theme(如前面賬號樣式)
)),
);
}
//賬號、密碼輸入框
Widget textSection = new Container(
padding: const EdgeInsets.all(32.0),
child: new Column(
//主軸Flex的值
mainAxisSize: MainAxisSize.max,
//MainAxisAlignment:主軸方向上的對齊方式,會對child的位置起作用
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
buildAccountTextFied(_accountController),
buildPasswordTextFied(_passwordController),
],
),
);
//登錄進行佈局及“賬號”、“密碼”格式校驗、彈窗的提示、路由(寫在build裏面太長了,抽出來)
Widget loginButton = new Container(
margin: const EdgeInsets.only(left: 35, right: 35),//這個widget距離父控件左右35(還有個all就是距離左上右下四個方向)
child: new SizedBox(
//用來設置寬高,如直接使用RaisedButton則不能設置
height: 50,
child: new RaisedButton(//一個凸起的材質矩形按鈕
color: Colors.red,
child: new Text(
'登錄',
style: TextStyle(color: Colors.white, fontSize: 20),
),
onPressed: () {//按下時的事件
_checkAccount();//校驗賬號格式,以此來更新_accountState
_checkPassword();//校驗賬號格式,以此來更新_passwordState
if (_accountState) {
if (_passwordState) {
_checkHint =
'恭喜賬號:' + _accountController.text.toString() + "登錄成功";
} else {
_checkHint = '請輸入8~15位密碼!';
}
} else {
_checkHint = '請輸入不低於7位賬號!';
}
showDialog(
context: context1,
barrierDismissible: true, //點擊彈窗外部是否消失
child: new AlertDialog(
title: new Text(//標題
'提示',
style:
new TextStyle(color: Colors.red[300], fontSize: 18),
),
content: new Text(_checkHint),//提示語
actions: <Widget>[
new FlatButton(//一個扁平的Material按鈕
onPressed: () {
Navigator.of(context1).pop();//彈窗消失
},
child: Text('取消')),
new FlatButton(
//對話框按鈕
onPressed: () {
if (_accountState && _passwordState) {//賬號密碼都符合條件
Navigator.pushNamed(
context1, '/Home'); //使用的是“命名導航路由”,具體去哪個界面,看main.dart 對應routeName('/Home')的界面
} else {
Navigator.of(context1).pop();//彈窗消失
}
},
child: Text('確定')),
],
),
);
})));
@override
Widget build(BuildContext context) {
context1 = context;
return Scaffold(
appBar: new AppBar(
title: new Text('登錄'),
),
body: new ListView(
children: [
new Image.asset(
'images/lake.jpg',
width: 600,
height: 240,
//cover(充滿容器)、fill(充滿父容器)、contain(總有寬或高跟父一樣)、none(原圖居中顯示)、fitWidth(寬度跟父一樣)、fitHeight(高度跟父一樣)
fit: BoxFit.contain,
),
textSection,
loginButton,
],
));
}
}
2.2圖片資源的添加
- 根目錄建立images文件夾,把準備好的lake.jpg圖片放進去,如下圖
- pubspec.yaml,增加的圖片資源,需要加進去,如下:
flutter: assets: - images/lake.jpg
3.Home.dart(類似MainActivity、main.xml,用來切換fragment)
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_app/screen/HomePage.dart';
import 'package:flutter_app/screen/MineScreen.dart';
class Home extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return new _HomeState();
}
}
class _HomeState extends State<Home> {
int _currentIndex = 0;
final List<Widget> _children = [
new HomeScreen(),//首頁界面
new MineScreen(),//我的界面
];
@override
Widget build(BuildContext context) {
return Scaffold(
body: _children[_currentIndex],
bottomNavigationBar: new BottomNavigationBar(
onTap: onTabTapped, //點擊切換
currentIndex: _currentIndex,
items: [
new BottomNavigationBarItem(
icon: new Icon(Icons.home),
title: new Text('首頁'),
),
new BottomNavigationBarItem(
icon: new Icon(Icons.person),
title: new Text('我的'),
),
],
),
);
}
void onTabTapped(int index) {
setState(() {
_currentIndex = index;
});
}
}
4.HomeScreen.dart(類似HomeFragment、homefragment.xml)
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
class HomeScreen extends StatelessWidget {
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('首頁'),
),
body: new Center(
child: new Text(
'我是Home界面',
style: TextStyle(color: Colors.red, fontSize: 20),
)
),
);
}
}
5.MineScreen.dart((類似MineFragment、minefragment.xml))
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
class MineScreen extends StatelessWidget {
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('我的'),
),
body: new Center(
child: new RaisedButton(
child: new Text('退出登錄'),
onPressed: () {
Navigator.of(context).pushNamed('/');//跳轉到main.dart對routeName('/')的界面
}),
),
);
}
}
剛剛想到一個問題:當你實現上面的效果後,回顧看一下“賬號”、“密碼”TextField重複寫了,增加了代碼量,我們是不是可以封裝一下,傳參數過去進行分別實現想要的效果,eg:obscureText(是否是密碼格式),賬號時傳個false、密碼時傳個true。
其它的也是同樣的情況,同樣的佈局,可以抽出來,進行調用即可,自己動手去實現一下哈,我就不重新貼代碼了。後期如果有優化或者補充的,我會繼續完善博文的。
補充:
1. 2020.3.18,經過後面的學習,可以優化底部導航欄,自定義選中、未選中的icon、text顏色,看效果圖、代碼:
//底部導航欄
class MainNavigator extends StatefulWidget {
@override
_MainNavigatorState createState() => new _MainNavigatorState();
}
class _MainNavigatorState extends State<MainNavigator> {
final _defaultColor = Colors.grey;//沒有選中時icon、文字的顏色
final _activeColor = Colors.red;//選中的icon、文字的顏色
int _currentIndex = 0;//默認顯示第一個
final List<Widget> _children = [
new HomePage(),
new DancePage(),
new MinePage(),
];
@override
Widget build(BuildContext context) {
return Scaffold(
body: _children[_currentIndex],
bottomNavigationBar: new BottomNavigationBar(
onTap: onTabTapped, //點擊切換
currentIndex: _currentIndex,//當前顯示的界面下標
type: BottomNavigationBarType.fixed, //文字的顯示,shifting選中時顯示,fixed不選中也顯示
items: [
BottomNavigationBarItem(
icon: Icon(Icons.home, color: _defaultColor), //未選中的icon顏色
activeIcon: Icon(Icons.home, color: _activeColor), //選中時的icon顏色
title: new Text(
'首頁',
style: TextStyle(
color: _currentIndex != 0
? _defaultColor
: _activeColor //不爲o表示未選中
),
),
),
BottomNavigationBarItem(
icon: Icon(Icons.directions_run, color: _defaultColor),
activeIcon: Icon(Icons.directions_run, color: _activeColor),
title: Text(
'舞蹈',
style: TextStyle(
color: _currentIndex != 1
? _defaultColor
: _activeColor //不爲1表示未選中
),
),
),
BottomNavigationBarItem(
icon: Icon(Icons.person, color: _defaultColor),
activeIcon: Icon(Icons.person, color: _activeColor),
title: Text(
'我的',
style: TextStyle(
color: _currentIndex != 2
? _defaultColor
: _activeColor //不爲2表示未選中
),
),
),
],
),
);
}
//點擊切換時,更新當前選中的界面下標
void onTabTapped(int index) {
setState(() {
_currentIndex = index;
});
}
}