Skip to main content

2 posts tagged with "Material Design"

View All Tags

Flutter: Dự án xây dựng UI cho ứng dụng

· 6 min read

Thiết kế giao diện người dùng (UI) là một phần quan trọng trong phát triển ứng dụng Flutter. Bài viết này sẽ hướng dẫn bạn cách xây dựng UI đẹp và hiệu quả cho ứng dụng Flutter.

1. Nguyên tắc thiết kế UI​

1.1. Material Design​

Material Design là hệ thống thiết kế của Google, cung cấp các nguyên tắc và thành phần để xây dựng giao diện người dùng đẹp và nhất quán.

MaterialApp(
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
// Customize theme
textTheme: TextTheme(
headline1: TextStyle(fontSize: 32, fontWeight: FontWeight.bold),
bodyText1: TextStyle(fontSize: 16),
),
),
home: HomeScreen(),
)

1.2. Responsive Design​

Thiết kế responsive giúp ứng dụng hoạt động tốt trên nhiều kích thước màn hình khác nhau.

class ResponsiveLayout extends StatelessWidget {
@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (context, constraints) {
if (constraints.maxWidth > 600) {
return DesktopLayout();
} else {
return MobileLayout();
}
},
);
}
}

2. Custom Widgets​

2.1. Custom Button​

class CustomButton extends StatelessWidget {
final String text;
final VoidCallback onPressed;
final Color? color;

const CustomButton({
Key? key,
required this.text,
required this.onPressed,
this.color,
}) : super(key: key);

@override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: onPressed,
style: ElevatedButton.styleFrom(
primary: color ?? Theme.of(context).primaryColor,
padding: EdgeInsets.symmetric(horizontal: 32, vertical: 16),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
child: Text(
text,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
);
}
}

2.2. Custom Card​

class CustomCard extends StatelessWidget {
final String title;
final String subtitle;
final Widget? trailing;
final VoidCallback? onTap;

const CustomCard({
Key? key,
required this.title,
required this.subtitle,
this.trailing,
this.onTap,
}) : super(key: key);

@override
Widget build(BuildContext context) {
return Card(
elevation: 4,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
child: InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(12),
child: Padding(
padding: EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: Theme.of(context).textTheme.headline6,
),
SizedBox(height: 8),
Text(
subtitle,
style: Theme.of(context).textTheme.bodyText2,
),
if (trailing != null) ...[
SizedBox(height: 16),
trailing!,
],
],
),
),
),
);
}
}

3. Layout và Navigation​

3.1. Bottom Navigation​

class MainScreen extends StatefulWidget {
@override
_MainScreenState createState() => _MainScreenState();
}

class _MainScreenState extends State<MainScreen> {
int _selectedIndex = 0;

final List<Widget> _screens = [
HomeScreen(),
SearchScreen(),
ProfileScreen(),
];

@override
Widget build(BuildContext context) {
return Scaffold(
body: _screens[_selectedIndex],
bottomNavigationBar: BottomNavigationBar(
currentIndex: _selectedIndex,
onTap: (index) {
setState(() {
_selectedIndex = index;
});
},
items: [
BottomNavigationBarItem(
icon: Icon(Icons.home),
label: 'Home',
),
BottomNavigationBarItem(
icon: Icon(Icons.search),
label: 'Search',
),
BottomNavigationBarItem(
icon: Icon(Icons.person),
label: 'Profile',
),
],
),
);
}
}

3.2. Drawer Navigation​

class DrawerScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Drawer Example'),
),
drawer: Drawer(
child: ListView(
padding: EdgeInsets.zero,
children: [
DrawerHeader(
decoration: BoxDecoration(
color: Theme.of(context).primaryColor,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
CircleAvatar(
radius: 30,
backgroundImage: NetworkImage('https://example.com/avatar.jpg'),
),
SizedBox(height: 16),
Text(
'John Doe',
style: TextStyle(
color: Colors.white,
fontSize: 18,
),
),
Text(
'john.doe@example.com',
style: TextStyle(
color: Colors.white70,
),
),
],
),
),
ListTile(
leading: Icon(Icons.home),
title: Text('Home'),
onTap: () {
Navigator.pop(context);
// Navigate to home
},
),
ListTile(
leading: Icon(Icons.settings),
title: Text('Settings'),
onTap: () {
Navigator.pop(context);
// Navigate to settings
},
),
],
),
),
body: Center(
child: Text('Main Content'),
),
);
}
}

4. Animations​

4.1. Hero Animation​

class HeroAnimation extends StatelessWidget {
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(builder: (_) => DetailScreen()),
);
},
child: Hero(
tag: 'imageHero',
child: Image.network(
'https://example.com/image.jpg',
width: 200,
height: 200,
),
),
);
}
}

4.2. Custom Animation​

class AnimatedContainer extends StatefulWidget {
@override
_AnimatedContainerState createState() => _AnimatedContainerState();
}

class _AnimatedContainerState extends State<AnimatedContainer>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _animation;

@override
void initState() {
super.initState();
_controller = AnimationController(
duration: Duration(seconds: 2),
vsync: this,
);

_animation = CurvedAnimation(
parent: _controller,
curve: Curves.easeInOut,
);

_controller.repeat(reverse: true);
}

@override
void dispose() {
_controller.dispose();
super.dispose();
}

@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _animation,
builder: (context, child) {
return Transform.scale(
scale: 1.0 + (_animation.value * 0.2),
child: Container(
width: 200,
height: 200,
decoration: BoxDecoration(
color: Colors.blue,
borderRadius: BorderRadius.circular(20),
),
),
);
},
);
}
}

5. Best Practices​

5.1. Tổ chức code​

lib/
├── widgets/
│ ├── custom_button.dart
│ ├── custom_card.dart
│ └── animated_container.dart
├── screens/
│ ├── home_screen.dart
│ └── detail_screen.dart
├── theme/
│ └── app_theme.dart
└── main.dart

5.2. Tối ưu hiệu suất​

  • Sử dụng const constructor khi có thể
  • Tránh rebuild không cần thiết
  • Sử dụng ListView.builder thay vì ListView
  • Tối ưu hình ảnh và assets
// Good
const CustomWidget({
required this.title,
required this.onTap,
});

// Bad
CustomWidget({
required this.title,
required this.onTap,
});

6. Ví dụ thực tế​

6.1. Profile Screen​

class ProfileScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: CustomScrollView(
slivers: [
SliverAppBar(
expandedHeight: 200,
pinned: true,
flexibleSpace: FlexibleSpaceBar(
title: Text('Profile'),
background: Image.network(
'https://example.com/cover.jpg',
fit: BoxFit.cover,
),
),
),
SliverToBoxAdapter(
child: Padding(
padding: EdgeInsets.all(16),
child: Column(
children: [
CircleAvatar(
radius: 50,
backgroundImage: NetworkImage('https://example.com/avatar.jpg'),
),
SizedBox(height: 16),
Text(
'John Doe',
style: Theme.of(context).textTheme.headline5,
),
Text(
'Software Developer',
style: Theme.of(context).textTheme.subtitle1,
),
SizedBox(height: 24),
CustomButton(
text: 'Edit Profile',
onPressed: () {
// Navigate to edit profile
},
),
],
),
),
),
],
),
);
}
}

6.2. Settings Screen​

class SettingsScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Settings'),
),
body: ListView(
children: [
ListTile(
leading: Icon(Icons.notifications),
title: Text('Notifications'),
trailing: Switch(
value: true,
onChanged: (value) {
// Handle notification settings
},
),
),
ListTile(
leading: Icon(Icons.dark_mode),
title: Text('Dark Mode'),
trailing: Switch(
value: false,
onChanged: (value) {
// Handle theme settings
},
),
),
ListTile(
leading: Icon(Icons.language),
title: Text('Language'),
trailing: Text('English'),
onTap: () {
// Show language selection
},
),
],
),
);
}
}

Kết luận​

Xây dựng UI trong Flutter là một quá trình sáng tạo và thú vị. Bằng cách tuân thủ các nguyên tắc thiết kế, sử dụng các widget có sẵn và tạo custom widget, bạn có thể xây dựng giao diện người dùng đẹp và hiệu quả.

Một số điểm cần lưu ý:

  • Tuân thủ Material Design guidelines
  • Tối ưu hiệu suất
  • Tổ chức code rõ ràng
  • Sử dụng animation một cách hợp lý
  • Thiết kế responsive

Tài liệu tham khảo:

Material Design và Cupertino Widgets trong Flutter

· 4 min read

Flutter cung cấp hai bộ widget chính để tạo giao diện người dùng: Material Design cho Android và Cupertino cho iOS. Bài viết này sẽ giúp bạn hiểu rõ về hai phong cách thiết kế này và cách sử dụng chúng.

1. Material Design​

Material Design là ngôn ngữ thiết kế của Google, được sử dụng chủ yếu cho các ứng dụng Android.

1.1. Các widget Material cơ bản​

AppBar​

AppBar(
title: const Text('Material App'),
actions: [
IconButton(
icon: const Icon(Icons.search),
onPressed: () {},
),
IconButton(
icon: const Icon(Icons.more_vert),
onPressed: () {},
),
],
)

FloatingActionButton​

FloatingActionButton(
onPressed: () {},
child: const Icon(Icons.add),
)

Card​

Card(
child: ListTile(
leading: const Icon(Icons.person),
title: const Text('John Doe'),
subtitle: const Text('john.doe@example.com'),
trailing: IconButton(
icon: const Icon(Icons.edit),
onPressed: () {},
),
),
)

BottomNavigationBar​

BottomNavigationBar(
items: const [
BottomNavigationBarItem(
icon: Icon(Icons.home),
label: 'Home',
),
BottomNavigationBarItem(
icon: Icon(Icons.business),
label: 'Business',
),
BottomNavigationBarItem(
icon: Icon(Icons.school),
label: 'School',
),
],
)

1.2. Material Design Themes​

MaterialApp(
theme: ThemeData(
primarySwatch: Colors.blue,
brightness: Brightness.light,
textTheme: const TextTheme(
headline1: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
bodyText1: TextStyle(fontSize: 16),
),
),
home: const MyHomePage(),
)

2. Cupertino Widgets​

Cupertino widgets là bộ widget theo phong cách iOS, cung cấp giao diện native cho người dùng iOS.

2.1. Các widget Cupertino cơ bản​

CupertinoNavigationBar​

CupertinoNavigationBar(
middle: const Text('Cupertino App'),
trailing: CupertinoButton(
child: const Icon(CupertinoIcons.add),
onPressed: () {},
),
)

CupertinoButton​

CupertinoButton(
color: CupertinoColors.activeBlue,
child: const Text('Cupertino Button'),
onPressed: () {},
)

CupertinoListTile​

CupertinoListTile(
leading: const Icon(CupertinoIcons.person),
title: const Text('John Doe'),
subtitle: const Text('john.doe@example.com'),
trailing: const Icon(CupertinoIcons.chevron_right),
)

CupertinoTabBar​

CupertinoTabBar(
items: const [
BottomNavigationBarItem(
icon: Icon(CupertinoIcons.home),
label: 'Home',
),
BottomNavigationBarItem(
icon: Icon(CupertinoIcons.settings),
label: 'Settings',
),
],
)

2.2. Cupertino Themes​

CupertinoApp(
theme: const CupertinoThemeData(
primaryColor: CupertinoColors.systemBlue,
brightness: Brightness.light,
textTheme: CupertinoTextThemeData(
primaryColor: CupertinoColors.systemBlue,
),
),
home: const MyHomePage(),
)

3. So sánh Material và Cupertino​

3.1. Điểm khác biệt chính​

Đặc điểmMaterial DesignCupertino
Phong cáchGoogle MaterialApple iOS
Màu sắcĐa dạng, tươi sángTối giản, trung tính
AnimationPhức tạp, mượt màĐơn giản, nhanh
TypographyRobotoSan Francisco
IconographyMaterial IconsSF Symbols

3.2. Khi nào sử dụng cái nào?​

Sử dụng Material Design khi:​

  • Ứng dụng chủ yếu cho Android
  • Cần giao diện phong phú, nhiều animation
  • Muốn tùy chỉnh nhiều về giao diện
  • Cần các widget phức tạp

Sử dụng Cupertino khi:​

  • Ứng dụng chủ yếu cho iOS
  • Cần giao diện đơn giản, tối giản
  • Muốn trải nghiệm native iOS
  • Cần hiệu năng tốt

4. Kết hợp Material và Cupertino​

4.1. Platform-aware widgets​

Widget build(BuildContext context) {
return Platform.isIOS
? CupertinoButton(
child: const Text('iOS Button'),
onPressed: () {},
)
: ElevatedButton(
onPressed: () {},
child: const Text('Android Button'),
);
}

4.2. Adaptive widgets​

Scaffold(
appBar: AppBar(
title: const Text('Adaptive App'),
actions: [
IconButton(
icon: Icon(
Platform.isIOS
? CupertinoIcons.add
: Icons.add,
),
onPressed: () {},
),
],
),
body: Center(
child: Platform.isIOS
? const CupertinoActivityIndicator()
: const CircularProgressIndicator(),
),
)

5. Best Practices​

5.1. Thiết kế nhất quán​

  • Chọn một phong cách chính cho ứng dụng
  • Sử dụng các widget phù hợp với platform
  • Duy trì tính nhất quán trong toàn bộ ứng dụng

5.2. Performance​

  • Sử dụng const constructor khi có thể
  • Tránh rebuild không cần thiết
  • Tối ưu hóa animation

5.3. Accessibility​

  • Sử dụng semantic labels
  • Đảm bảo contrast ratio phù hợp
  • Hỗ trợ screen readers

Kết luận​

Material Design và Cupertino widgets là hai công cụ mạnh mẽ trong Flutter để tạo giao diện người dùng. Việc hiểu rõ sự khác biệt và biết khi nào sử dụng cái nào sẽ giúp bạn tạo ra ứng dụng có trải nghiệm người dùng tốt nhất trên cả Android và iOS.


Tài liệu tham khảo: