Mini Project: Calculator 🧮
Xây dựng máy tính với giao diện đẹp. ⏱️ 30 phút
🎯 Mục tiêu
Tạo ứng dụng Calculator với:
- Các phép tính cơ bản (+, -, ×, ÷)
- Giao diện grid buttons
- Hiển thị expression và kết quả
- Clear và delete chức năng
🚀 Bắt đầu
Bước 1: Cấu trúc cơ bản
Tạo file calculator_app.py:
import flet as ft
@ft.observable
class CalcState:
expression: str = ""
result: str = "0"
def input(self, value: str):
self.expression += value
def clear(self):
self.expression = ""
self.result = "0"
def delete(self):
self.expression = self.expression[:-1]
def calculate(self):
try:
# Thay thế ký hiệu để eval
expr = self.expression.replace("×", "*").replace("÷", "/")
self.result = str(eval(expr))
except:
self.result = "Error"
state = CalcState()
def main(page: ft.Page):
page.title = "Calculator"
page.add(ft.Text("Calculator"))
ft.run(main)Bước 2: Tạo Display
@ft.component
def Display():
return ft.Container(
content=ft.Column([
ft.Text(
state.expression or "0",
size=24,
color=ft.Colors.GREY_500,
text_align=ft.TextAlign.RIGHT,
),
ft.Text(
state.result,
size=48,
weight=ft.FontWeight.BOLD,
text_align=ft.TextAlign.RIGHT,
),
], horizontal_alignment=ft.CrossAxisAlignment.END, spacing=5),
padding=20,
bgcolor=ft.Colors.GREY_100,
border_radius=ft.border_radius.only(top_left=20, top_right=20),
)Bước 3: Tạo Button Component
@ft.component
def CalcButton(text: str, expand: int = 1, bg_color=None, text_color=None):
def on_click(_):
if text == "C":
state.clear()
elif text == "⌫":
state.delete()
elif text == "=":
state.calculate()
else:
state.input(text)
return ft.Container(
content=ft.Text(text, size=24, weight=ft.FontWeight.W_500, color=text_color),
bgcolor=bg_color or ft.Colors.WHITE,
border_radius=10,
alignment=ft.alignment.center,
expand=expand,
height=70,
on_click=on_click,
)Bước 4: Tạo Button Grid
@ft.component
def ButtonGrid():
# Định nghĩa các nút
buttons = [
["C", "⌫", "÷"],
["7", "8", "9", "×"],
["4", "5", "6", "-"],
["1", "2", "3", "+"],
["0", ".", "="],
]
return ft.Column([
ft.Row([
CalcButton(
btn,
expand=2 if btn == "0" else 1,
bg_color=ft.Colors.ORANGE if btn in ["÷", "×", "-", "+", "="] else
ft.Colors.GREY_300 if btn in ["C", "⌫"] else None,
text_color=ft.Colors.WHITE if btn in ["÷", "×", "-", "+", "="] else None,
)
for btn in row
], spacing=10)
for row in buttons
], spacing=10)Bước 5: Code hoàn chỉnh
import flet as ft
@ft.observable
class CalcState:
expression: str = ""
result: str = "0"
def input(self, value: str):
self.expression += value
def clear(self):
self.expression = ""
self.result = "0"
def delete(self):
self.expression = self.expression[:-1]
if not self.expression:
self.result = "0"
def calculate(self):
try:
expr = self.expression.replace("×", "*").replace("÷", "/")
if expr:
result = eval(expr)
# Format số đẹp
if isinstance(result, float) and result.is_integer():
self.result = str(int(result))
else:
self.result = str(round(result, 10))
except:
self.result = "Error"
state = CalcState()
@ft.component
def Display():
return ft.Container(
content=ft.Column([
ft.Text(
state.expression or " ",
size=22,
color=ft.Colors.GREY_600,
text_align=ft.TextAlign.RIGHT,
),
ft.Text(
state.result,
size=52,
weight=ft.FontWeight.BOLD,
text_align=ft.TextAlign.RIGHT,
),
], horizontal_alignment=ft.CrossAxisAlignment.END, spacing=5),
padding=ft.padding.only(left=20, right=20, top=30, bottom=20),
bgcolor=ft.Colors.GREY_100,
border_radius=ft.border_radius.only(top_left=20, top_right=20),
width=320,
)
@ft.component
def CalcButton(text: str, expand: int = 1, bg_color=None, text_color=None):
def on_click(_):
if text == "C":
state.clear()
elif text == "⌫":
state.delete()
elif text == "=":
state.calculate()
else:
state.input(text)
return ft.Container(
content=ft.Text(
text,
size=26,
weight=ft.FontWeight.W_500,
color=text_color or ft.Colors.BLACK87
),
bgcolor=bg_color or ft.Colors.WHITE,
border_radius=15,
alignment=ft.alignment.center,
expand=expand,
height=65,
shadow=ft.BoxShadow(blur_radius=3, color=ft.Colors.BLACK12),
on_click=on_click,
)
@ft.component
def ButtonGrid():
return ft.Container(
content=ft.Column([
ft.Row([
CalcButton("C", bg_color=ft.Colors.RED_100, text_color=ft.Colors.RED),
CalcButton("⌫", bg_color=ft.Colors.GREY_200),
CalcButton("÷", bg_color=ft.Colors.ORANGE, text_color=ft.Colors.WHITE),
], spacing=10),
ft.Row([
CalcButton("7"),
CalcButton("8"),
CalcButton("9"),
CalcButton("×", bg_color=ft.Colors.ORANGE, text_color=ft.Colors.WHITE),
], spacing=10),
ft.Row([
CalcButton("4"),
CalcButton("5"),
CalcButton("6"),
CalcButton("-", bg_color=ft.Colors.ORANGE, text_color=ft.Colors.WHITE),
], spacing=10),
ft.Row([
CalcButton("1"),
CalcButton("2"),
CalcButton("3"),
CalcButton("+", bg_color=ft.Colors.ORANGE, text_color=ft.Colors.WHITE),
], spacing=10),
ft.Row([
CalcButton("0", expand=2),
CalcButton("."),
CalcButton("=", bg_color=ft.Colors.ORANGE, text_color=ft.Colors.WHITE),
], spacing=10),
], spacing=10),
padding=15,
width=320,
)
@ft.component
def Calculator():
return ft.Container(
content=ft.Column([
Display(),
ButtonGrid(),
], spacing=0),
bgcolor=ft.Colors.GREY_200,
border_radius=20,
shadow=ft.BoxShadow(blur_radius=20, color=ft.Colors.BLACK26),
)
def main(page: ft.Page):
page.title = "Calculator"
page.vertical_alignment = ft.MainAxisAlignment.CENTER
page.horizontal_alignment = ft.CrossAxisAlignment.CENTER
page.bgcolor = ft.Colors.BLUE_GREY_100
page.add(Calculator())
ft.run(main)✅ Kết quả
Calculator hoàn chỉnh với:
- ✅ Các phép tính +, -, ×, ÷
- ✅ Clear (C) và Delete (⌫)
- ✅ Hiển thị expression và result
- ✅ Giao diện đẹp với shadows
🎯 Thử thách mở rộng
- Percentage (%) - Thêm phép tính phần trăm
- Parentheses - Thêm dấu ngoặc ()
- History - Lưu lịch sử tính toán
- Keyboard support - Nhập từ bàn phím
- Scientific mode - Thêm sin, cos, sqrt…
📝 Kiến thức ôn tập
- Grid layout với Row/Column
- Conditional styling - màu sắc theo loại nút
- State management - expression và result
- Shadow và border radius
⏭️ Tiếp theo
Tuyệt! Tiếp tục với Mini Project: Theme Switcher.
Last updated on