Qt实现仿Cupertino的输入控件CupertinoFloatingField(PyQt5)

效果预览

PixPin_2026-05-07_10-43-10

代码

import sys
from PyQt5.QtWidgets import QApplication, QLineEdit, QVBoxLayout, QWidget, QLabel
from PyQt5.QtCore import Qt, QPropertyAnimation, QPoint, QEasingCurve


class CupertinoFloatingField(QWidget):
    def __init__(self, placeholder="手机号 / 邮箱", parent=None):
        super().__init__(parent)
        self.setFixedHeight(50)  # 紧凑的高度

        # 1. 样式定义
        self.STYLE_NORMAL = "border: 1px solid #D1D1D6; background-color: #F2F2F7;"
        self.STYLE_FOCUS = "border: 2px solid #007AFF; background-color: #FFFFFF;"

        # 2. 创建输入框
        self.input_field = QLineEdit(self)
        # 输入框占满容器,但顶部留出 10px 的空间给标签中位线
        self.input_field.setGeometry(0, 10, 320, 40)
        # self.input_field.setMinimumSize(0, 40)
        self.input_field.setStyleSheet(
            f"QLineEdit {{ {self.STYLE_NORMAL} border-radius: 8px; padding: 0 12px; font-size: 14px; }}")

        # 3. 创建悬浮标签
        self.label = QLabel(placeholder, self)
        self.label.setAttribute(Qt.WA_TransparentForMouseEvents)
        # 核心:背景色与窗口背景一致,形成“遮断”效果
        self.label.setStyleSheet("color: #8E8E93; font-size: 14px; background-color: transparent; padding: 0 4px;")

        # 坐标计算 (x, y)
        # Down: 居中在输入框内
        self.label_pos_down = QPoint(12, 22)
        # Up: 骑在边框线上 (y 坐标正好是输入框顶边 10px 附近)
        self.label_pos_up = QPoint(10, 2)

        self.label.move(self.label_pos_down)

        # 4. 动画
        self.anim = QPropertyAnimation(self.label, b"pos")
        self.anim.setDuration(150)
        self.anim.setEasingCurve(QEasingCurve.OutQuad)

        # 5. 重写事件
        self.input_field.focusInEvent = self._on_focus_in
        self.input_field.focusOutEvent = self._on_focus_out

    def _on_focus_in(self, event):
        self.input_field.setStyleSheet(
            f"QLineEdit {{ {self.STYLE_FOCUS} border-radius: 8px; padding: 0 12px; font-size: 14px; }}")
        self.label.setStyleSheet("color: #007AFF; font-size: 11px; font-weight: bold; background-color: white;")
        self.anim.setEndValue(self.label_pos_up)
        self.anim.start()
        QLineEdit.focusInEvent(self.input_field, event)

    def _on_focus_out(self, event):
        if not self.input_field.text():
            self.input_field.setStyleSheet(
                f"QLineEdit {{ {self.STYLE_NORMAL} border-radius: 8px; padding: 0 12px; font-size: 14px; }}")
            self.label.setStyleSheet("color: #8E8E93; font-size: 14px; background-color: transparent;")
            self.anim.setEndValue(self.label_pos_down)
            self.anim.start()
        else:
            # 有文字但失去焦点,保持上方,但颜色变灰,背景保持白色以遮挡线条
            self.input_field.setStyleSheet(
                f"QLineEdit {{ {self.STYLE_NORMAL} border-radius: 8px; padding: 0 12px; font-size: 14px; }}")
            self.label.setStyleSheet("color: #8E8E93; font-size: 11px; background-color: white;")
        QLineEdit.focusOutEvent(self.input_field, event)


class Demo(QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Cupertino Notch Label")
        self.setFixedSize(400, 250)
        self.setStyleSheet("background-color: white;")  # 必须是白色,以配合标签背景

        layout = QVBoxLayout(self)
        layout.setSpacing(25)
        layout.addWidget(CupertinoFloatingField("User Name"))
        layout.addWidget(CupertinoFloatingField("Password"))
        layout.setContentsMargins(40, 40, 40, 40)


if __name__ == "__main__":
    app = QApplication(sys.argv)
    demo = Demo()
    demo.show()
    sys.exit(app.exec_())
posted @ 2026-05-07 10:47  乌合之众  阅读(18)  评论(0)    收藏  举报
clear