作者:CGRnDStudio 由于这次的代码量在200多行,笔者把所有的代码放在了文末,如果给您造成了阅读上的不便十分抱歉~~ 笔者的微信是:sd181914,欢迎大家来骚扰~~ 今天带大家制作的就是上次我展示的UI 那么我们进入正题,逐个函数来解析 这次一共有17个函数我们依此从上至下说(以下略过的函数,在前几期有说过一些函数的使用或者在官方均可查询到使用方法故不再重复说明): __init__: 我们创建了许多初始化变量用于下面的函数使用 __init__ui: 这个函数需要介绍这么几句话: self.setWindowFlags(Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint) 这个主要是用于设定无边框和置顶的功能(置顶在某些情况可能会失效,主要是与windows机制有关) self.setAttribute(Qt.WA_TranslucentBackground) 用于设定UI的背景可被透明,这个是关键的关键我们分别看下2个图,左图是拥有背景透明,右图是不具有背景透明属性 self._window_title.mouseDoubleClickEvent = lambda *args: self._set_window_size() 把_window_title的鼠标双击事件强行赋予了一个新的函数用于达到我们双击的时候能最大化和正常化窗体的目的 self.setMouseTracking(True) 这句话也是很重要的一句话,获取鼠标的实时状态,如果我们不开启这个属性那么我们很多功能都将失效 self.main_widget = QWidget() self.main_widget.setMouseTracking(True) self._main_widget_layout.addWidget(self.main_widget) 我们有了基础的窗体还需要一个承载体用于之后我们可以用这个窗体来做为基础的框架来使用,那么这个main_widget对象,就是一个用于,承载之后我们需要加入的,一些其他窗体的父物体 set_window_title(略) _user_color_ctrl(略) _color_dialog_call(略) change_color: self.update() 这个方法主要是为了强制更新窗体其中包含颜色,说白了就是怕颜色信息没有及时更新,所以在这步强制刷新下窗体 _set_window_ctrl(略) _set_window_size(略) _face_in: 我们迎来了第一个重头戏: self._face_ani_in = QPropertyAnimation(self, 'windowOpacity') 这个是属性动画,我们设定了顶层窗体的窗体透明度属性,我们如何知道有哪些属性可以被我们设定呢—官方文档是最好的答案,或者有个偏门的方法--大部分可以用setxxx这种,xxx可能就是属性 self._face_ani_in.setStartValue(0) 我们在这里给了一个开始值 self._face_ani_in.setEndValue(self._face_num) 在我们的初始化函数里面我们创建了个变量,这个地方我们给了一个最后值 self._face_ani_in.setDuration(self._face_time) 这里就是我们设定这个动画的时长,单位:ms self._face_ani_in.setEasingCurve(QEasingCurve.Linear) 既然是动画就有动画曲线,这步就是设定动画曲线:线性动画。当然官方还提供了各式各样的动画曲线可选,甚至可以自己创建。 self._face_ani_in.start() 这步就是动画的开始 到这里为止我们的淡入功能完成了,但是我们现在还显示不出窗体,因为我们并没有调用这个函数和show函数 _face_out: 既然有淡入功能就需要淡出功能,重复部门不再介绍 self._face_ani_out.finished.connect(self.close) 这里我们做了信号的连接让动画完成是执行我们需要的函数 Show: 想了下还是说明下这个函数吧 super(Class5, self).show() 我们继承了原本show的所有功能 self._face_in() 这里我们调用了淡入功能 self._close_button.clicked.connect(self._face_out_close) 这里我们连接了关闭按钮的信号 _face_out_close: 关闭按钮链接的函数 self._face_out() 我们调用了淡出功能 self._close_button.clicked.disconnect() 这步是为了打断关闭按钮的连接,为什么要这样做呢,因为笔者之前在测试这个窗体的时候连续按关闭按钮的时候窗体不会被关闭动画并没有执行完成然后再次点击关闭按钮就会再次触发动画……所以笔者为了避免重复的触发动画干脆就直接断开信号了 mousePressEvent: 鼠标的按下事件,这里不过多介绍,说一下概念,鼠标按下的时候我们需要一个记录,用于记录是什么按键,其次为了获取鼠标的位置,然后为了一个状态锁 mouseMoveEvent: 好了第二个重头戏来了: x_move = event.pos().x() > self.geometry().width() - self._move_num y_move = event.pos().y() > self.geometry().height() - self._move_num 这里都是用于判断鼠标的坐标 if x_move and y_move: self.setCursor(Qt.SizeFDiagCursor) elif x_move: self.setCursor(Qt.SizeHorCursor) elif y_move: self.setCursor(Qt.SizeVerCursor) else: self.setCursor(Qt.ArrowCursor) 根据鼠标的坐标来判断鼠标的图标,是横向缩放还是纵向缩放还是对角缩放 if self._x_move_enbale and self._y_move_enbale: self.setGeometry(self._widget_geometry.x(), self._widget_geometry.y(), max(self._widget_geometry.width() + event.pos().x() - self._m_point.x(), 0), max(self._widget_geometry.height() + event.pos().y() - self._m_point.y(), 0)) elif self._x_move_enbale: self.setGeometry(self._widget_geometry.x(), self._widget_geometry.y(), max(self._widget_geometry.width() + event.pos().x() - self._m_point.x(), 0), self._widget_geometry.height()) elif self._y_move_enbale: self.setGeometry(self._widget_geometry.x(), self._widget_geometry.y(), self._widget_geometry.width(), max(self._widget_geometry.height() + event.pos().y() - self._m_point.y(), 0)) else: if self._move_enable: if self._left_button: self.move(event.globalPos() - self._m_point) 这里我们判断之前按下的信息,如果正好在我们需要的判断范围内那么我们将改变窗体的大小(setGeometry),如果不在这个范围内我们将直接根据鼠标移动窗体 到这里我们淡入、淡出、移动、无边框功能都有了,换色功能暂时还用不上还有阴影我们还没有,别着急在这个paintEvent函数会介绍 mouseReleaseEvent: 鼠标的释放事件,我们把之前赋予的变量都重新赋予了,为了避免错误的触发mouseMoveEvent事件并释放掉状态锁 paintEvent: 绘图事件,我们所有的窗体均来源于这个绘画事件,如果我们注释了这个函数那么我们将看不到窗体,只能看到3个按钮,甚至所有的鼠标事件都将失效: path = QPainterPath() 这里我们创建了一个绘画路径,用于记录窗体需要画什么地方 path.setFillRule(Qt.WindingFill) 这里我们设定了填充方法:全部填充。就像PS一样,我们勾勒了轮廓使用了油漆桶功能 if self._window_size == 1: path.addRoundedRect(self.margin_num, self.margin_num, self.width() - self.margin_num * 2, self.height() - self.margin_num * 2, self.round_num, self.round_num) elif self._window_size == 2: path.addRoundedRect(0, 0, self.width(), self.height(), self.round_num, self.round_num) 这里我们正式给绘画路径指定了绘画的范围以及圆角范围,addRoundedRect是一个带有圆角的矩形,这里有一个if语句是为了设置最大化时没有阴影勾边,不然会有一段空白的地方 painter = QPainter(self) 创建一个绘画 painter.setRenderHint(QPainter.Antialiasing, True) 设定绘画的渲染方式:抗锯齿边缘 painter.fillPath(path, QBrush(QColor(50, 50, 50, 255))) 在这里我们填充路径内的区域,填充的颜色采用的是RGBA的模式不用担心255的A会不会不透明,我们的透明是用属性动画做的,和这个地方不冲突 if self._window_size == 1: _range = range(self.margin_num) attenuation = 80 max_a = math.sqrt(max(_range)) * attenuation 这里我们设定了阴影的宽度和衰减以及衰减的方式,当然读者有更好的衰减方式可以替换为自己的 for i in _range: s_path = QPainterPath() s_path.setFillRule(Qt.WindingFill) 这里和上面一样但是需要创建一个新的变量 rect = QRectF(self.margin_num - i, self.margin_num - i, self.width() - (self.margin_num - i) * 2, self.height() - (self.margin_num - i) * 2) 创建一个RectF,这个是用浮点类型的方法定义一个矩形 s_path.addRoundedRect(rect, self.round_num, self.round_num) 需要绘画的路径就是上面定义的矩形(阴影边框) self._color.setAlpha(max_a - math.sqrt(i) * attenuation) 这里我们设定了颜色的透明度也就是我们阴影的透明 painter.setPen(self._color) 我们把绘画对象设定为颜色(阴影) painter.drawPath(s_path) 这里我们不在需要填充全部区域,我们只需要填充一个矩形的1个像素点的框就可以了 由于这里是for循环所以会不停的勾勒阴影边框 main(略) 至此我们所有的功能全部写完了,我们可以显示出我们的窗体,如果需要更改最终窗体透明和动画时间可以在实例化Class5的时候传入我们需要的数值,达到我们需要的效果 现在我们所有关于QT的一个新手向教程至此全部结束了,一共5期的课程我们得到了一个不算好看,但是也不算丑陋的基础UI,当然阴影有很多做法,网上也有好几种,也有用QT的特效模块制作的阴影,但是我们更改不了颜色,还记得当初制作这个东西的时候就是为了好玩想要学习下paintEvent的一些使用方法,不知不觉就做出了这么个鬼玩意。 我也在想接着该开什么坑来填,如果大家有好的意见和建议欢迎加我微信:sd181914,或者留言告知,笔者会时不时看下,如果近期大家都没有比较好奇的主题,笔者可能考虑下个文章说一些关于MayaAPI部分或者是流程的一些心得啊吐槽话题啊之类的与大家分享,最后感谢大家耐心的看完了5篇推文,如果大家都有疑惑和意见欢迎来骚扰我~~ ```python # coding=utf-8 import imp try: imp.find_module('PySide2') from PySide2.QtCore import * from PySide2.QtGui import * from PySide2.QtWidgets import * except: from PySide.QtGui import * from PySide.QtCore import * import sys import math class Class5(QWidget): def __init__(self, parent=None, face_num=0.9, face_time=500, title=u'', color_enable=False, move_enable=True): super(Class5, self).__init__(parent) self.margin_num = 6 self.round_num = 5 self._move_enable = move_enable self._face_time = face_time self._face_num = face_num self._left_button = False self._x_move_enbale = False self._y_move_enbale = False self._move_num = 10 self._window_size = 1 self._color = QColor(100, 130, 250, 255) self.__init_ui() self.set_window_title(title) self._set_window_ctrl() if color_enable: self._user_color_ctrl() def __init_ui(self): self.setWindowFlags(Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint) self.setAttribute(Qt.WA_TranslucentBackground) self._widget_layout = QVBoxLayout(self) self._widget_layout.setSpacing(3) self._widget_layout.setContentsMargins(10, 10, 10, 10) self._title_widget_layout = QHBoxLayout() self._title_widget_layout.setSpacing(3) self._title_widget_layout.setContentsMargins(3, 3, 3, 3) self._main_widget_layout = QHBoxLayout() self._main_widget_layout.setSpacing(0) self._main_widget_layout.setContentsMargins(0, 0, 0, 0) self._widget_layout.addLayout(self._title_widget_layout) self._widget_layout.addLayout(self._main_widget_layout) self._window_title = QLabel() self._window_title.setMinimumWidth(80) self._window_title.mouseDoubleClickEvent = lambda *args: self._set_window_size() self._title_widget_layout.addWidget(self._window_title) self._ctrl_layout = QHBoxLayout() self._title_widget_layout.addLayout(self._ctrl_layout) self.setMouseTracking(True) self.main_widget = QWidget() self.main_widget.setMouseTracking(True) self._main_widget_layout.addWidget(self.main_widget) def set_window_title(self, title): self._window_title.setText(' ' * 2 + title) def _user_color_ctrl(self): self._color_pb = QPushButton(u'换色') self._color_pb.setMaximumSize(32, 30) self._color_pb.setMinimumSize(32, 30) self._ctrl_layout.addWidget(self._color_pb) self._color_pb.clicked.connect(self._color_dialog_call) def _color_dialog_call(self): self._color_dialog = QColorDialog() self._color_dialog.setOption(QColorDialog.ShowAlphaChannel) self._color_dialog.currentColorChanged.connect(self.change_color) self._color_dialog.show() def change_color(self, color): self._color = color self.update() def _set_window_ctrl(self): self._lower_button = QPushButton('-') self._lower_button.setMaximumSize(32, 30) self._lower_button.setMinimumSize(32, 30) self._lower_button.clicked.connect(self.showMinimized) self._maximized_button = QPushButton(u'□') self._maximized_button.setMaximumSize(32, 30) self._maximized_button.setMinimumSize(32, 30) self._maximized_button.clicked.connect(self._set_window_size) self._close_button = QPushButton('X') self._close_button.setMaximumSize(32, 30) self._close_button.setMinimumSize(32, 30) self._ctrl_layout.addWidget(self._lower_button) self._ctrl_layout.addWidget(self._maximized_button) self._ctrl_layout.addWidget(self._close_button) def _set_window_size(self): if self._window_size == 1: self.showMaximized() self._window_size = 2 elif self._window_size == 2: self.showNormal() self._window_size = 1 self.update() def _face_in(self): u"""淡入功能函数""" self._face_ani_in = QPropertyAnimation(self, 'windowOpacity') self._face_ani_in.setStartValue(0) self._face_ani_in.setEndValue(self._face_num) self._face_ani_in.setDuration(self._face_time) self._face_ani_in.setEasingCurve(QEasingCurve.Linear) self._face_ani_in.start() def _face_out(self): u"""淡出功能函数""" self._face_ani_out = QPropertyAnimation(self, 'windowOpacity') self._face_ani_out.setStartValue(self._face_num) self._face_ani_out.setEndValue(0) self._face_ani_out.setDuration(self._face_time) self._face_ani_out.setEasingCurve(QEasingCurve.Linear) self._face_ani_out.finished.connect(self.close) self._face_ani_out.start() def show(self, *args, **kwargs): u"""重写show函数加入淡入功能""" super(Class5, self).show() self._face_in() self._close_button.clicked.connect(self._face_out_close) def _face_out_close(self): u"""关闭功能函数加入淡出功能""" self._face_out() self._close_button.clicked.disconnect() def mousePressEvent(self, event): u"""重写鼠标点击事件""" if event.button() == Qt.LeftButton: self._left_button = True self._m_point = event.pos() self._widget_geometry = self.geometry() self._x_move_enbale = self._m_point.x() > self._widget_geometry.width() - self._move_num self._y_move_enbale = self._m_point.y() > self._widget_geometry.height() - self._move_num def mouseMoveEvent(self, event): u"""重写鼠标移动事件""" x_move = event.pos().x() > self.geometry().width() - self._move_num y_move = event.pos().y() > self.geometry().height() - self._move_num if x_move and y_move: self.setCursor(Qt.SizeFDiagCursor) elif x_move: self.setCursor(Qt.SizeHorCursor) elif y_move: self.setCursor(Qt.SizeVerCursor) else: self.setCursor(Qt.ArrowCursor) if self._x_move_enbale and self._y_move_enbale: self.setGeometry(self._widget_geometry.x(), self._widget_geometry.y(), max(self._widget_geometry.width() + event.pos().x() - self._m_point.x(), 0), max(self._widget_geometry.height() + event.pos().y() - self._m_point.y(), 0)) elif self._x_move_enbale: self.setGeometry(self._widget_geometry.x(), self._widget_geometry.y(), max(self._widget_geometry.width() + event.pos().x() - self._m_point.x(), 0), self._widget_geometry.height()) elif self._y_move_enbale: self.setGeometry(self._widget_geometry.x(), self._widget_geometry.y(), self._widget_geometry.width(), max(self._widget_geometry.height() + event.pos().y() - self._m_point.y(), 0)) else: if self._move_enable: if self._left_button: self.move(event.globalPos() - self._m_point) def mouseReleaseEvent(self, event): u"""重写鼠标放开事件""" if event.button() == Qt.LeftButton: self._x_move_enbale = False self._y_move_enbale = False self._left_button = False def paintEvent(self, event): u"""重新绘图事件""" path = QPainterPath() path.setFillRule(Qt.WindingFill) if self._window_size == 1: path.addRoundedRect(self.margin_num, self.margin_num, self.width() - self.margin_num * 2, self.height() - self.margin_num * 2, self.round_num, self.round_num) elif self._window_size == 2: path.addRoundedRect(0, 0, self.width(), self.height(), self.round_num, self.round_num) painter = QPainter(self) painter.setRenderHint(QPainter.Antialiasing, True) painter.fillPath(path, QBrush(QColor(50, 50, 50, 255))) if self._window_size == 1: _range = range(self.margin_num) attenuation = 80 max_a = math.sqrt(max(_range)) * attenuation for i in _range: s_path = QPainterPath() s_path.setFillRule(Qt.WindingFill) rect = QRectF(self.margin_num - i, self.margin_num - i, self.width() - (self.margin_num - i) * 2, self.height() - (self.margin_num - i) * 2) s_path.addRoundedRect(rect, self.round_num, self.round_num) self._color.setAlpha(max_a - math.sqrt(i) * attenuation) painter.setPen(self._color) painter.drawPath(s_path) def main(self): self.show() if __name__ == '__main__': app = QApplication(sys.argv) class_5 = Class5() class_5.main() sys.exit(app.exec_()) ``` ------------------------------------------------------------------------- 我们尊重原创,也注重分享,如若侵权请联系qter@qter.org。 ------------------------------------------------------------------------- |