import tkinter as tk
from typing import Callableclass Switch(tk.Canvas):MOVE_STEP = 6def __init__(self, master: tk.Widget, size: int = 60, bg_off="#cccccc", bg_on="#0066cc", btn_off="#333333", btn_on="#ffffff",state_changed_callback: Callable = None, *args, **kwargs):super().__init__(master, *args, **kwargs)self.size = sizeself.padding = size // 5self.bg_off = bg_offself.bg_on = bg_onself.button_off = btn_offself.button_on = btn_onself.callback = state_changed_callbackself.__enter: bool = Falseself.__moving: bool = Falseself.__state: bool = Falseself.configure(width=self.size * 2, height=self.size, highlightthickness=0,bd=0, bg=self.master.cget("background"))self._draw_background()self.__create_switch_btn()self.__bind_event()def __bind_event(self):self.bind("<ButtonPress-1>", self.__on_click)self.bind("<ButtonRelease-1>", self.toggle)self.bind("<Enter>", lambda _: self.__update_switch_btn(True))self.bind("<Leave>", lambda _: self.__update_switch_btn(False))def __on_click(self, event):if self.__moving:returnself.coords("switch_button", *self.__switch_btn_coords(stretch=True))def __switch_btn_coords(self, stretch: bool=False) -> tuple[int, int, int, int]:y1 = self.paddingy2 = self.size - self.paddingif self.__state:x1 = self.size if stretch else self.size + self.paddingx2 = self.size * 2 - self.paddingelse:x1 = self.paddingx2 = self.size if stretch else self.size - self.paddingreturn x1, y1, x2, y2def __update_switch_btn(self, active: bool):if self.__moving:returnself.__enter = activeself.padding = self.size // 6 if active else self.size // 5self.coords("switch_button", *self.__switch_btn_coords())def __create_switch_btn(self, stretch: bool=False) -> str:return self.create_oval(*self.__switch_btn_coords(stretch),fill=self.button_on if self.__state else self.button_off, outline="", tag="switch_button")def _draw_background(self):r = self.size // 2self.create_arc(0, 0, 2 * r, self.size, start=90, extent=180, fill=self.bg_off, outline="", tags="bg")self.create_arc(self.size * 2 - 2 * r, 0, self.size * 2, self.size, start=270, extent=180,fill=self.bg_off, outline="", tags="bg")self.create_rectangle(r, 0, self.size * 2 - r, self.size, fill=self.bg_off,outline="", tags="bg")def toggle(self, event=None, force=False):if self.__moving or not self.__enter and not force:returnself.__state = not self.__statebg_color = self.bg_on if self.__state else self.bg_offbtn_color = self.button_on if self.__state else self.button_offself.__move_switch_button(self.padding if self.__state else self.size + self.padding,self.size + self.padding if self.__state else self.padding)self.__moving = Trueself.itemconfig("switch_button", fill=btn_color)self.itemconfig("bg", fill=bg_color)if self.callback:self.callback(self.__state)self.configure(bg=self.master.cget("background"))def __move_switch_button(self, current_x: int, target_x: int):if self.__state and current_x <= target_x:self.move("switch_button", self.MOVE_STEP, 0)self.after(10, lambda: self.__move_switch_button(current_x + self.MOVE_STEP, target_x))elif not self.__state and current_x >= target_x:self.move("switch_button", -self.MOVE_STEP, 0)self.after(10, lambda: self.__move_switch_button(current_x - self.MOVE_STEP, target_x))else:self.coords("switch_button", target_x, self.padding, target_x + self.size - 2 * self.padding, self.padding + self.size - 2 * self.padding)self.__moving = False@propertydef state(self) -> bool:return self.__stateif __name__ == "__main__":import ctypesctypes.windll.shcore.SetProcessDpiAwareness(1)root = tk.Tk()root.title("Switch Demo")root.geometry("200x200")root.configure(background="#969696")def state_changed(state):print(f"当前状态: {'开启' if state else '关闭'}")root.configure(background="#99D9EA" if state else "#969696")def switch_state():global switchswitch.toggle(force=True)switch = Switch(root, size=45, state_changed_callback=state_changed)switch.pack(pady=30)btn = tk.Button(root, text="切换状态", command=switch_state)btn.pack()root.mainloop()
只是简单实现了一下动画