tkinter 开关按钮(Switch)控件

 

 

import tkinter as tk
from typing import Callable
 
 
class EllipseSwitch(tk.Canvas):
    animation_time = 1
    def __init__(
            self, 
            master: tk.Widget, 
            size: int = 40,
            state: bool = False,
            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 = size
        self.padding = size // 5
        self.bg_off = bg_off
        self.bg_on = bg_on
        self.button_off = btn_off
        self.button_on = btn_on
        self.callback = state_changed_callback
        self.__speed = max(int(self.animation_time * 500 / self.size), 1)
        self.__enter: bool = False
        self.__moving: bool = False
        self.__state: bool = state
        self.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>", lambda _: 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:
            return
        self.coords("switch_button", *self.__switch_btn_coords(stretch=True))
 
    def __switch_btn_coords(self, stretch: bool=False) -> tuple[int, int, int, int]:
        y1 = self.padding
        y2 = self.size - self.padding
        if self.__state:
            x1 = self.size if stretch else self.size + self.padding
            x2 = self.size * 2 - self.padding
        else:
            x1 = self.padding
            x2 = self.size if stretch else self.size - self.padding
        return x1, y1, x2, y2
 
    def __update_switch_btn(self, active: bool):
        if self.__moving:
            return
        self.__enter = active
        self.padding = self.size // 6 if active else self.size // 5
        self.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):
        bg = self.bg_on if self.__state else self.bg_off
        r = self.size // 2
        self.create_arc(
            0, 0, 2 * r, self.size, start=90, extent=180, fill=bg, 
            outline="", tags="bg"
        )
        self.create_arc(
            self.size * 2 - 2 * r, 0, self.size * 2, self.size, start=270, extent=180,
            fill=bg, outline="", tags="bg"
        )
        self.create_rectangle(
            r, 0, self.size * 2 - r, self.size, fill=bg,
            outline="", tags="bg"
        )
 
    def toggle(self, state: bool = None, animation=True):
        if state is None:
            if self.__moving or not self.__enter:
                return
            self.__state = not self.__state
        else:
            self.__state = state
        bg_color = self.bg_on if self.__state else self.bg_off
        btn_color = self.button_on if self.__state else self.button_off
        if animation:
            self.__moving = True
            self.__move_switch_button(
                self.padding if self.__state else self.size + self.padding,
                self.size + self.padding if self.__state else self.padding
            )
        else:
            self.coords("switch_button", *self.__switch_btn_coords())
        self.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", 5, 0)
            self.after(self.__speed, lambda: self.__move_switch_button(current_x + 5, target_x))
        elif not self.__state and current_x >= target_x:
            self.move("switch_button", -5, 0)
            self.after(self.__speed, lambda: self.__move_switch_button(current_x - 5, 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
 
    @property
    def state(self) -> bool:
        return self.__state
 
 
 
 
if __name__ == "__main__":
    import ctypes
    ctypes.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():
        # 在外界切换开关,请明确使用state参数;
        global switch
        switch.toggle(state=not switch.state)
 
    switch = EllipseSwitch(root, size=200, state_changed_callback=state_changed)
    switch.pack(pady=30)
 
    btn = tk.Button(root, text="切换状态", command=switch_state)
    btn.pack()
 
    root.mainloop()

只是简单实现了一下动画

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值