Tạo kiểu và Tạo ứng dụng đáp ứng trong Flutter
- 11-06-2024
- Toanngo92
- 0 Comments
Mục lục
Tạo kiểu trong Flutter
Tạo kiểu là một yêu cầu thiết yếu cho bất kỳ ứng dụng nào. Một giao diện người dùng đẹp có thể thu hút rất nhiều người dùng.
Flutter cung cấp nhiều phương pháp tạo kiểu và tùy chọn để tạo kiểu cho toàn bộ chủ đề của ứng dụng.
Tạo chủ đề cho ứng dụng trong Flutter
Tạo chủ đề cho ứng dụng trong Flutter rất đơn giản. MaterialApp
trong main.dart
là thành phần bọc toàn bộ ứng dụng và nó có một tham số gọi là theme
nơi các nhà phát triển có thể chỉ định chủ đề cho toàn bộ ứng dụng. Bất kỳ thay đổi nào ở đây sẽ được phản ánh xuyên suốt ứng dụng, do đó ngăn chặn mã trùng lặp. Đoạn mã ví dụ 1 cho thấy tham số chủ đề cho main.dart
được tự động tạo của một ứng dụng mẫu.
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatelessWidget {
final String title;
const MyHomePage({Key? key, required this.title}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'Hello, Flutter!',
),
],
),
),
);
}
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Styling Demo',
// theme
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: StylingExample(),
);
}
}
Ví dụ về giao diện người dùng (UI)
Đoạn bên dưới cho thấy một ví dụ về một giao diện người dùng đơn giản. Đây là lớp StylingExample()
được tham chiếu trong main.dart
.
Ở đây, văn bản, biểu tượng, nút, thanh trượt và hộp kiểm được sử dụng mà không có bất kỳ thuộc tính tạo kiểu nào. Mặc định, màu chủ đạo sẽ là màu xanh lam. Điều này có nghĩa là tất cả các widget được tạo mà không có kiểu sẽ có màu xanh lam.
import 'package:flutter/material.dart';
class StylingExample extends StatelessWidget {
const StylingExample({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('App Bar'),
),
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'Heading 4',
style: Theme.of(context).textTheme.headline4,
),
Slider(value: 0, onChanged: (s) {}),
Icon(Icons.collections_sharp),
Checkbox(value: true, onChanged: (s) {}),
TextButton(onPressed: () {}, child: const Text('Text Button')),
ElevatedButton(onPressed: () {}, child: const Text('Elevated Button')),
],
),
);
}
}
Hình bên dưới cho thấy kết quả của widget StylingExample
.
Trong ví dụ trên, chúng ta có một giao diện người dùng đơn giản với các thành phần mặc định theo chủ đề màu xanh lam. Tất cả các thành phần như thanh trượt, biểu tượng, hộp kiểm, nút văn bản, và nút nổi đều sẽ có màu chủ đạo theo chủ đề đã thiết lập trong ThemeData
.
Tạo kiểu cho một Widget cụ thể trong Flutter
a. Văn bản (Text)
Văn bản có thể được tạo kiểu bằng cách sử dụng phương thức TextStyle
để thay đổi các thuộc tính như màu sắc, màu nền, kích thước chữ, độ đậm chữ, chiều cao dòng, khoảng cách giữa các từ/chữ cái, gạch dưới, kiểu và nhiều thuộc tính khác.
Cú pháp của TextStyle
như sau:
Text(
'New Text',
style: TextStyle(
fontSize: 30,
fontWeight: FontWeight.w600,
color: Colors.teal,
height: 1.5,
wordSpacing: 2,
letterSpacing: 2,
backgroundColor: Colors.yellow,
),
)
Đoạn mã bên dưới minh họa cách sử dụng phương thức TextStyle
.
Text(
'New Text',
style: TextStyle(
fontSize: 30,
fontWeight: FontWeight.w600,
color: Colors.teal,
height: 1.5,
wordSpacing: 2,
letterSpacing: 2,
backgroundColor: Colors.yellow,
),
)
Hình bên dưới minh họa kết quả của văn bản đã được tạo kiểu.
b. Container:
Container
có thuộc tính decoration
có thể được sử dụng để chỉ định viền, borderRadius, bóng đổ (shadow), gradient, hình ảnh, và nhiều hơn nữa.
Cú pháp cho decoration
như sau:
Decoration? decoration,
Một Container
đơn giản với borderRadius
, boxShadow
, và border
được tạo trong Đoạn mã bên dưới đây.
Container(
height: 100,
width: 100,
decoration: BoxDecoration(
// màu sắc
color: Colors.orange,
// border radius
borderRadius: BorderRadius.circular(10),
// border với độ dày 5
border: Border.all(color: Colors.orange.shade900, width: 5),
// bóng đổ cho container
boxShadow: [
BoxShadow(
// offset nơi bóng đổ nên rơi
offset: Offset(5, 10),
// màu bóng đổ
color: Colors.grey,
// bán kính mờ
blurRadius: 5,
// bán kính lan tỏa
spreadRadius: 1,
),
],
),
)
Hình bên dưới hiển thị kết quả của việc thực thi Đoạn mã trên.
Lưu ý: Nếu sử dụng thuộc tính decoration
, thì màu sắc phải được chỉ định bên trong BoxDecoration
, nếu không sẽ gây ra lỗi.
c. RaisedButton:
RaisedButton
đã bị khai tử trong Flutter và không nên sử dụng nữa. Thay vào đó, có thể sử dụng ElevatedButton
.
Dưới đây là các thuộc tính của ElevatedButton
:
ButtonStyle styleFrom({
Color? primary,
Color? onPrimary,
Color? onSurface,
Color? shadowColor,
Color? surfaceTintColor,
double? elevation,
TextStyle? textStyle,
EdgeInsetsGeometry? padding,
Size? minimumSize,
Size? fixedSize,
Size? maximumSize,
BorderSide? side,
OutlinedBorder? shape,
MouseCursor? enabledMouseCursor,
MouseCursor? disabledMouseCursor,
VisualDensity? visualDensity,
MaterialTapTargetSize? tapTargetSize,
Duration? animationDuration,
bool? enableFeedback,
AlignmentGeometry? alignment,
InteractiveInkFeatureFactory? splashFactory,
})
Đoạn mã bên dưới và Hình tiếp theo cho thấy ví dụ và kết quả cho ElevatedButton
.
ElevatedButton(
onPressed: () {},
child: Text("Button"),
style: ElevatedButton.styleFrom(
// màu nút
primary: Colors.amber.shade700,
// hình dạng và border radius
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
// để chỉ định kích thước cố định cho nút
fixedSize: Size(100, 100),
),
)
Chuyển đổi giữa các giao diện trong Flutter
Nhiều ứng dụng hiện nay có tùy chọn chuyển đổi giữa giao diện sáng và tối. Một số ứng dụng thậm chí còn có tùy chọn đặt giao diện tùy chỉnh. Người dùng nên cảm thấy thoải mái khi sử dụng ứng dụng theo cách họ muốn. Các giao diện trong Flutter cung cấp một cách để đặt màu sắc tùy chỉnh, phông chữ, kích thước và viền cho nhiều widget, và sẽ được áp dụng cho toàn bộ ứng dụng.
Cũng có thể tạo các giao diện tùy chỉnh theo yêu cầu.
Tạo giao diện tối trong Flutter
Bước 1: Tạo một tệp lớp mới gọi là AppTheme
nơi các giao diện có thể được chỉ định. Tạo một biến tĩnh mới gọi là darkTheme
. Tham khảo Đoạn mã bên dưới.
class AppTheme {
static ThemeData darkTheme = ThemeData();
}
Bước 2: Gọi theme bên trong MaterialApp
. Đoạn mã bên dưới cho thấy cách gọi theme bên trong MaterialApp
trong tệp main.dart
.
MaterialApp(
darkTheme: AppTheme.darkTheme,
home: StylingExample(),
)
Đoạn mã tiếp theo cho thấy cách tạo kiểu giao diện tối cho ứng dụng Flutter.
class AppTheme {
static ThemeData darkTheme = ThemeData(
// Màu nền scaffold
scaffoldBackgroundColor: Colors.black54,
// Giao diện app bar
appBarTheme: AppBarTheme(color: Colors.purple),
// Giao diện elevated button
elevatedButtonTheme: ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(primary: Colors.red),
),
// Giao diện icon
iconTheme: IconThemeData(color: Colors.white),
// Giao diện text - heading 4
textTheme: TextTheme(
headline4: TextStyle(color: Colors.white70, fontWeight: FontWeight.w500),
),
);
}
Tạo giao diện sáng trong Flutter
Bước 1: Làm theo các bước tương tự khi tạo giao diện tối để tạo giao diện sáng trong cùng lớp. Đoạn mã bên dưới cho thấy cách tạo Giao diện Sáng.
class AppTheme {
static ThemeData lightTheme = ThemeData(
// Màu nền scaffold
scaffoldBackgroundColor: Colors.white,
// Giao diện app bar
appBarTheme: AppBarTheme(color: Colors.indigo),
// Giao diện elevated button
elevatedButtonTheme: ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(primary: Colors.red),
),
// Giao diện text - heading 4
textTheme: TextTheme(
headline4: TextStyle(color: Colors.blueGrey, fontWeight: FontWeight.w500),
),
);
}
Bước 2: Bên trong MaterialApp
, áp dụng giao diện sáng như sau:
MaterialApp(
theme: AppTheme.lightTheme,
)
Chuyển đổi giữa giao diện tối và sáng trong Flutter
Bước 1: Để chuyển đổi giữa các giao diện, tạo một nút mà khi nhấn sẽ chuyển đổi giao diện. Có hai giao diện được định nghĩa trong MaterialApp
. Thêm một thuộc tính khác gọi là themeMode
và thay đổi lớp AppTheme
bằng cách mở rộng ChangeNotifier
. Tham khảo Đoạn mã dưới đây. Đây là một tệp mới gọi là apptheme.dart
.
Bước 2: Tạo một biến boolean để chuyển đổi giữa các giao diện tối và sáng, sau đó là một hàm để thay đổi giao diện và thông báo cho các listener.
class AppTheme extends ChangeNotifier {
bool _isDarkTheme = true;
ThemeMode get currentTheme => _isDarkTheme ? ThemeMode.dark : ThemeMode.light;
void toggleTheme() {
_isDarkTheme = !_isDarkTheme;
notifyListeners();
}
}
Bước 3: Tạo một thể hiện toàn cục cho AppTheme
trong tệp main.dart
như sau:
final AppTheme appTheme = AppTheme();
Bước 4: Bây giờ, xác định các giao diện này trong MaterialApp
trong main.dart
. Thay đổi MyApp
thành một StatefulWidget
. Trong initState
, thêm một trình nghe cho appTheme
, để việc thay đổi chủ đề sẽ được cập nhật ngay lập tức. Tham khảo đoạn mã bên dưới.
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
@override
void initState() {
// Thêm một trình nghe
appTheme.addListener(() {
setState(() {});
});
super.initState();
}
@override
Widget build(BuildContext context) {
// Cập nhật giao diện dựa trên chủ đề hiện tại
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Styling Demo',
// Chủ đề
theme: AppTheme.lightTheme,
darkTheme: AppTheme.darkTheme,
// Chế độ chủ đề
themeMode: appTheme.currentTheme,
home: StylingExample(),
);
}
}
Bước 5: Cuối cùng, tạo một nút để chuyển đổi chủ đề. Trong appBar
của StylingExample
, thêm một IconButton
và cung cấp hàm toggleTheme
. Tham khảo mã đoạn mã bên dưới.
actions: [
IconButton(
onPressed: () {
appTheme.toggleTheme();
},
icon: Icon(Icons.brightness_4),
),
],
Với các bước trên, bạn đã tạo được khả năng chuyển đổi giữa các chủ đề sáng và tối khi người dùng nhấn vào nút.
Ứng dụng Flutter Responsive
Flutter là một framework đa nền tảng có thể được sử dụng để xây dựng ứng dụng từ các đồng hồ thông minh đến các TV lớn. Trong những trường hợp này, không thể sử dụng cùng một thiết kế và bố cục. Các bố cục và thiết kế phải được thay đổi để phù hợp với kích thước màn hình. Flutter cung cấp một số widget thông qua đó, tính đáng tin cậy có thể được đạt được.
Dưới đây là các bước để đạt được tính đáng tin cậy trong một ứng dụng Flutter:
Bước 1: Để ứng dụng Flutter hỗ trợ tất cả các hướng, thêm mã được cung cấp trong đoạn mã bên dưới vào phần build của MyApp()
từ main.dart
.
import 'package:flutter/services.dart';
SystemChrome.setPreferredOrientations([
DeviceOrientation.landscapeLeft,
DeviceOrientation.landscapeRight,
DeviceOrientation.portraitDown,
DeviceOrientation.portraitUp,
]);
Bước 2: Sử dụng setPreferredOrientations
, chỉ định danh sách các hướng mà thiết bị nên hỗ trợ. Ví dụ, nếu ứng dụng chỉ hoạt động trong chế độ ngang, chỉ định hai tùy chọn này:
DeviceOrientation.landscapeLeft
DeviceOrientation.landscapeRight
Tương tự, sử dụng các tùy chọn dọc nếu ứng dụng cần hỗ trợ chế độ dọc.
Xây dựng Lớp: Flutter
Sử dụng LayoutBuilder
, có thể xác định maxHeight
và maxWidth
của Widget.
Trong Đoạn mã bên dưới, hai LayoutBuilder
được xác định bên trong một Row
. Widget Expanded
có thể được sử dụng để phân bổ không gian tối đa. Bên trong các layout builders
này, một widget Text
được triển khai để hiển thị chiều rộng tối đa mà widget đó chiếm.
Hình ảnh bên dưới cho thấy việc sử dụng LayoutBuilder
.
Scaffold(
appBar: AppBar(),
body: Row(
children: [
// Widget 1
Expanded(
child: LayoutBuilder(
builder: (context, constraints) => Container(
color: Colors.yellow.shade200,
child: Center(
child: Text('Widget width\n' + constraints.maxWidth.toStringAsFixed(2)),
),
),
),
),
// Widget 2
Expanded(
flex: 2,
child: LayoutBuilder(
builder: (context, constraints) => Container(
color: Colors.teal,
child: Center(
child: Text('Widget width\n' + constraints.maxWidth.toStringAsFixed(2)),
),
),
),
),
],
),
);
MediaQuery.of()
Phương thức hàm xây dựng
Trong Flutter, MediaQuery
có thể được sử dụng để lấy kích thước (chiều rộng và chiều cao) và hướng (dọc/ngang) của thiết bị. Lưu ý: MediaQuery
có thể được sử dụng ở những nơi cần kích thước tổng của màn hình. Trong khi đó, LayoutBuilder
có thể được sử dụng ở những nơi cần chiều cao/chiều rộng của một widget cụ thể.
Trong các đoạn mã bên dưới, có thể thấy rằng một container được sử dụng trong một Scaffold
và được bọc trong một widget Center. Chiều cao và chiều rộng của Container được xác định bằng MediaQuery
, nó sẽ trả về một giá trị double. 0.25 xác định chiều cao của container là 25% của tổng chiều cao. Kết quả trong 2 hình bên dưới cho thấy sự khác biệt khi sử dụng MediaQuery
để chỉ định kích thước động cho các hướng.
// Lấy hướng của thiết bị
Orientation orientation = MediaQuery.of(context).orientation;
// Lấy chiều cao của thiết bị
double deviceHeight = MediaQuery.of(context).size.height;
// Lấy chiều rộng của thiết bị
double deviceWidth = MediaQuery.of(context).size.width;
Container(
height: deviceHeight * 0.25,
width: deviceWidth * 0.5,
color: Colors.red,
),
Lưu ý: MediaQuery.of(context)
chỉ có thể truy cập từ bên trong phương thức build.
Các Tiện ích để Xây Dựng Giao Diện Đáp ứng trong Flutter
Flutter cung cấp một số tiện ích khác ngoài LayoutBuilder
thông qua đó ứng dụng có thể trở nên đáp ứng.
Aspect Ratio
Tỷ lệ khung hình có thể được sử dụng để gán kích thước cụ thể cho một tiện ích con. Tiện ích này trước tiên thử chiều rộng lớn nhất cho phép bởi các ràng buộc bố trí và sau đó quyết định chiều cao bằng cách áp dụng tỷ lệ khung hình đã cho vào chiều rộng.
Tỷ lệ khung hình nên được chỉ định dưới dạng một giá trị double. Tham khảo Đoạn mã bên dưới.
AspectRatio(
aspectRatio: 16 / 9,
child: Container(
color: Colors.amber,
),
)
CustomSingleChildLayout
Các tiện ích SingleChildLayout
là những tiện ích chỉ cho phép một tiện ích làm tiện ích con của chúng như Container
, Align
, Center
, SizedBox
, Positioned
, và nhiều tiện ích khác. Một tiện ích con đơn có thể được tạo ra bằng cách sử dụng tiện ích CustomSingleChildLayout
. Quan trọng là nhà phát triển phải chỉ định delegate và tiện ích con. Delegate có hai phương thức ghi đè performLayout
và shouldRelayout
mà cũng có thể được tùy chỉnh theo nhu cầu.
Đoạn mã bên dưới hiển thị cách ghi đè performLayout
và shouldLayout
mặc định để tùy chỉnh SingleChildLayout
.
class MySingleChildDelegate extends SingleChildLayoutDelegate {
@override
void performLayout(Size size) {
// TODO: implement performLayout
}
@override
bool shouldRelayout(covariant SingleChildLayoutDelegate oldDelegate) {
// TODO: implement shouldRelayout
throw UnimplementedError();
}
}
CustomMultiChildLayout
Các tiện ích MultiChildLayout cho phép nhiều tiện ích con. Row, Column, Stack, ListView, và nhiều tiện ích khác có thể được nhấn mạnh như các ví dụ. Sử dụng CustomMultiChildLayourDelegate, có thể tùy chỉnh một tiện ích MultiChild.
Dưới đây là cú pháp cho CustomMultiChildLayout:
CustomMultiChildLayout({
Key? key,
required MultiChildLayoutDelegate delegate,
List<Widget> children = const <Widget>[],
})
Lưu ý rằng các widget con trong widget này cần được đặt bên trong một widget LayoutId
, với mỗi con có một ID duy nhất.
FittedBox
FittedBox
hạn chế tiện ích con của nó không phát triển vượt quá một giới hạn nhất định. Nó thay đổi tỷ lệ chúng tùy thuộc vào kích thước có sẵn. Đoạn mã bên dưới hiển thị cách thức hoạt động của nó.
Code Snippet 19:
FittedBox(
child: Container(
color: Colors.amber,
child: Text("Fitted Box Container"),
),
)
MediaQueryData
MediaQuery.of(context)
bản thân nó là một MediaQueryData
sẽ trả về các thông tin về padding, ViewInsets và ViewPadding của màn hình hiện tại. Thông tin này sẽ giúp chúng ta tính toán padding cho các hướng, kích thước màn hình và khi bàn phím xuất hiện. Hình bên dưới phân biệt giữa padding, viewInsets và viewPadding.
Insets và Padding
- Padding: Khoảng cách bên trong giữa nội dung và viền của một phần tử giao diện.
- ViewInsets: Khoảng cách bị chèn bởi các yếu tố như bàn phím.
- ViewPadding: Khoảng cách được thêm vào bởi các phần của hệ thống như thanh trạng thái và thanh điều hướng.
Hình bên dưới: Phân biệt giữa Padding, ViewInsets và ViewPadding
OrientationBuilder
OrientationBuilder
có thể được sử dụng để bố trí các widget theo hướng của thiết bị (chân dung hoặc phong cảnh). Đoạn mã bên dưới minh họa việc sử dụng OrientationBuilder
. GridView
đã được sử dụng, trong đó crossAxisCount
và childAspectRatio
được điều chỉnh theo hướng. Sẽ có hai widget khi ở chế độ chân dung và ba widget khi ở chế độ phong cảnh như trong hình bên dưới.
Scaffold(
body: OrientationBuilder(
builder: (context, orientation) => GridView.builder(
// gridDelegate là nơi chúng ta chỉ định khoảng cách,
// số lượng và tỷ lệ khung hình...
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
// thay đổi số lượng theo hướng
crossAxisCount: orientation == Orientation.portrait ? 2 : 3,
// thay đổi tỷ lệ khung hình theo hướng
childAspectRatio: orientation == Orientation.portrait ? 1 : 3,
mainAxisSpacing: 10,
crossAxisSpacing: 10,
),
itemBuilder: (context, index) => Container(
color: Colors.blue,
),
),
),
)