Skip to main content

One post tagged with "UI 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: