Di artikel ini, kita akan membedah bagaimana logika Flutter bekerja untuk mengubah warna tema dan latar belakang secara real-time.
Aplikasi ini bukan sekadar pencatat tugas. Saya menyematkan beberapa fitur kustomisasi tingkat lanjut:
Inti dari kustomisasi gambar ada pada penggunaan URL dinamis. Dengan memanfaatkan Image.network(), aplikasi akan menarik gambar dari web. Namun, agar teks tetap terbaca, saya menambahkan Color Filter:
Teknik ini berfungsi seperti "kaca film" yang menggelapkan gambar sehingga konten di atasnya tetap menonjol.
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: TodoPage(),
);
}
}
class TodoItem {
String text;
bool isDone;
TodoItem(this.text, {this.isDone = false});
}
class TodoPage extends StatefulWidget {
@override
_TodoPageState createState() => _TodoPageState();
}
class _TodoPageState extends State<TodoPage> {
TextEditingController todoController = TextEditingController();
TextEditingController urlController = TextEditingController();
List<TodoItem> todoList = [];
Color appBarColor = Colors.blue;
String? backgroundImageUrl;
void tambahTodo() {
if (todoController.text.isNotEmpty) {
setState(() {
todoList.add(TodoItem(todoController.text));
todoController.clear();
});
}
}
void hapusTodo(int index) {
setState(() {
todoList.removeAt(index);
});
}
void toggleSelesai(int index) {
setState(() {
todoList[index].isDone = !todoList[index].isDone;
});
}
void gantiWarna(Color warnaBaru) {
setState(() {
appBarColor = warnaBaru;
});
}
void showImageDialog() {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text("Masukkan URL Foto"),
content: TextField(
controller: urlController,
decoration: InputDecoration(
hintText: "https://example.com/gambar.jpg",
border: OutlineInputBorder(),
),
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: Text("Batal"),
),
ElevatedButton(
onPressed: () {
setState(() {
backgroundImageUrl = urlController.text;
});
urlController.clear();
Navigator.pop(context);
},
child: Text("Simpan"),
),
],
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
elevation: 0,
centerTitle: true,
title: Text(
"To Do List - Fito",
style: TextStyle(fontWeight: FontWeight.bold),
),
flexibleSpace: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [appBarColor, appBarColor.withOpacity(0.7)],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
),
),
actions: [
PopupMenuButton<dynamic>(
icon: Icon(Icons.more_vert),
onSelected: (value) {
if (value is Color) {
gantiWarna(value);
} else if (value == "ganti_foto") {
showImageDialog();
} else if (value == "reset_foto") {
setState(() => backgroundImageUrl = null);
}
},
itemBuilder: (context) => <PopupMenuEntry<dynamic>>[
PopupMenuItem(value: Colors.blue, child: Text("Tema Biru")),
PopupMenuItem(value: Colors.green, child: Text("Tema Hijau")),
PopupMenuItem(value: Colors.purple, child: Text("Tema Ungu")),
PopupMenuItem(value: Colors.red, child: Text("Tema Merah")),
PopupMenuItem(value: Colors.yellow, child: Text("Tema Kuning")),
PopupMenuItem(value: Colors.white, child: Text("Tema Kuning")),
PopupMenuItem(value: Colors.purple, child: Text("Tema Kuning")),
const PopupMenuDivider(),
PopupMenuItem(
value: "ganti_foto",
child: Row(
children: [
Icon(Icons.image, size: 20, color: Colors.grey),
SizedBox(width: 8),
Text("Ganti Background"),
],
),
),
PopupMenuItem(
value: "reset_foto",
child: Row(
children: [
Icon(Icons.delete_forever, size: 20, color: Colors.red),
SizedBox(width: 8),
Text("Hapus Background"),
],
),
),
],
),
],
),
body: Stack(
children: [
// Background Image Layer
if (backgroundImageUrl != null && backgroundImageUrl!.isNotEmpty)
Positioned.fill(
child: Image.network(
backgroundImageUrl!,
fit: BoxFit.cover,
errorBuilder: (context, error, stackTrace) {
return Container(color: Colors.white); // Jika URL salah
},
color: Colors.black.withOpacity(0.2),
colorBlendMode: BlendMode.darken,
),
),
// Content Layer
Column(
children: [
Container(
padding: EdgeInsets.all(20),
width: double.infinity,
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.8),
borderRadius: BorderRadius.vertical(bottom: Radius.circular(25)),
),
child: Text(
"Hari ini mau ngapain? 🚀",
textAlign: TextAlign.center,
style: TextStyle(fontSize: 18, fontWeight: FontWeight.w600),
),
),
Padding(
padding: EdgeInsets.all(16),
child: Row(
children: [
Expanded(
child: TextField(
controller: todoController,
decoration: InputDecoration(
hintText: "Tambahkan tugas baru...",
filled: true,
fillColor: Colors.white.withOpacity(0.9),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(15),
borderSide: BorderSide.none,
),
),
),
),
SizedBox(width: 10),
ElevatedButton(
onPressed: tambahTodo,
style: ElevatedButton.styleFrom(
backgroundColor: appBarColor,
padding: EdgeInsets.all(18),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(15),
),
),
child: Icon(Icons.add, color: Colors.white),
),
],
),
),
Expanded(
child: todoList.isEmpty
? Center(
child: Text(
"Belum ada tugas ✨",
style: TextStyle(
color: backgroundImageUrl != null ? Colors.white : Colors.grey,
fontWeight: FontWeight.bold,
backgroundColor: Colors.black12,
),
),
)
: ListView.builder(
itemCount: todoList.length,
itemBuilder: (context, index) {
final item = todoList[index];
return AnimatedContainer(
duration: Duration(milliseconds: 300),
margin: EdgeInsets.symmetric(horizontal: 16, vertical: 6),
decoration: BoxDecoration(
color: item.isDone
? Colors.green.withOpacity(0.7)
: Colors.white.withOpacity(0.85),
borderRadius: BorderRadius.circular(15),
boxShadow: [
BoxShadow(
color: Colors.black12,
blurRadius: 6,
offset: Offset(0, 3),
)
],
),
child: ListTile(
leading: Checkbox(
value: item.isDone,
activeColor: appBarColor,
onChanged: (_) => toggleSelesai(index),
),
title: Text(
item.text,
style: TextStyle(
fontSize: 16,
decoration: item.isDone
? TextDecoration.lineThrough
: TextDecoration.none,
),
),
trailing: IconButton(
icon: Icon(Icons.delete, color: Colors.red),
onPressed: () => hapusTodo(index),
),
),
);
},
),
),
],
),
],
),
);
}
Komentar
Posting Komentar