Bỏ qua để đến nội dung

🧮 Calculator

Xây dựng máy tính với giao diện đẹp. ⏱️ 30 phút

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

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)
@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),
)
@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,
)
@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)
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)

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
  1. Percentage (%) - Thêm phép tính phần trăm
  2. Parentheses - Thêm dấu ngoặc ()
  3. History - Lưu lịch sử tính toán
  4. Keyboard support - Nhập từ bàn phím
  5. Scientific mode - Thêm sin, cos, sqrt…
  • 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

Tuyệt! Tiếp tục với Mini Project: Theme Switcher.