#!/usr/bin/env python # -*- coding: utf-8 -*- """ Created on 2018年11月24日 author: Irony site: https://pyqt.site , https://github.com/PyQt5 email: 892768447@qq.com file: description: 参考 http://qt.shoutwiki.com/wiki/Extending_QStackedWidget_for_sliding_page_animations_in_Qt """ try: from PyQt5.QtCore import Qt, pyqtProperty, QEasingCurve, QPoint, QPropertyAnimation, \ QParallelAnimationGroup, QTimer from PyQt5.QtWidgets import QStackedWidget except ImportError: from PySide2.QtCore import Qt, Property as pyqtProperty, QEasingCurve, QPoint, QPropertyAnimation, \ QParallelAnimationGroup, QTimer from PySide2.QtWidgets import QStackedWidget class SlidingStackedWidget(QStackedWidget): LEFT2RIGHT, RIGHT2LEFT, TOP2BOTTOM, BOTTOM2TOP, AUTOMATIC = range(5) def __init__(self, *args, **kwargs): super(SlidingStackedWidget, self).__init__(*args, **kwargs) self._pnow = QPoint(0, 0) # 动画速度 self._speed = 500 # 当前索引 self._now = 0 # 自动模式的当前索引 self._current = 0 # 下一个索引 self._next = 0 # 是否激活 self._active = 0 # 动画方向(默认是横向) self._orientation = Qt.Horizontal # 动画曲线类型 self._easing = QEasingCurve.Linear # 初始化动画 self._initAnimation() def setSpeed(self, speed=500): """设置动画速度 :param speed: 速度值,默认值为500 :type speed: int """ self._speed = speed @pyqtProperty(int, fset=setSpeed) def speed(self): return self._speed def setOrientation(self, orientation=Qt.Horizontal): """设置动画的方向(横向和纵向) :param orientation: 方向(Qt.Horizontal或Qt.Vertical) :type orientation: http://doc.qt.io/qt-5/qt.html#Orientation-enum """ self._orientation = orientation @pyqtProperty(int, fset=setOrientation) def orientation(self): return self._orientation def setEasing(self, easing=QEasingCurve.OutBack): """设置动画的曲线类型 :param easing: 默认为QEasingCurve.OutBack :type easing: http://doc.qt.io/qt-5/qeasingcurve.html#Type-enum """ self._easing = easing @pyqtProperty(int, fset=setEasing) def easing(self): return self._easing def slideInNext(self): """滑动到下一页""" now = self.currentIndex() if now < self.count() - 1: self.slideInIdx(now + 1) self._current = now + 1 def slideInPrev(self): """滑动到上一页""" now = self.currentIndex() if now > 0: self.slideInIdx(now - 1) self._current = now - 1 def slideInIdx(self, idx, direction=4): """滑动到指定序号 :param idx: 序号 :type idx: int :param direction: 方向,默认是自动AUTOMATIC=4 :type direction: int """ if idx > self.count() - 1: direction = self.TOP2BOTTOM if self._orientation == Qt.Vertical else self.RIGHT2LEFT idx = idx % self.count() elif idx < 0: direction = self.BOTTOM2TOP if self._orientation == Qt.Vertical else self.LEFT2RIGHT idx = (idx + self.count()) % self.count() self.slideInWgt(self.widget(idx), direction) def slideInWgt(self, widget, direction): """滑动到指定的widget :param widget: QWidget, QLabel, etc... :type widget: QWidget Base Class :param direction: 方向 :type direction: int """ if self._active: return self._active = 1 _now = self.currentIndex() _next = self.indexOf(widget) if _now == _next: self._active = 0 return w_now = self.widget(_now) w_next = self.widget(_next) # 自动判断方向 if _now < _next: directionhint = self.TOP2BOTTOM if self._orientation == Qt.Vertical else self.RIGHT2LEFT else: directionhint = self.BOTTOM2TOP if self._orientation == Qt.Vertical else self.LEFT2RIGHT if direction == self.AUTOMATIC: direction = directionhint # 计算偏移量 offsetX = self.frameRect().width() offsetY = self.frameRect().height() w_next.setGeometry(0, 0, offsetX, offsetY) if direction == self.BOTTOM2TOP: offsetX = 0 offsetY = -offsetY elif direction == self.TOP2BOTTOM: offsetX = 0 elif direction == self.RIGHT2LEFT: offsetX = -offsetX offsetY = 0 elif direction == self.LEFT2RIGHT: offsetY = 0 # 重新定位显示区域外部/旁边的下一个窗口小部件 pnext = w_next.pos() pnow = w_now.pos() self._pnow = pnow # 移动到指定位置并显示 w_next.move(pnext.x() - offsetX, pnext.y() - offsetY) w_next.show() w_next.raise_() self._animnow.setTargetObject(w_now) self._animnow.setDuration(self._speed) self._animnow.setEasingCurve(self._easing) self._animnow.setStartValue(QPoint(pnow.x(), pnow.y())) self._animnow.setEndValue( QPoint(offsetX + pnow.x(), offsetY + pnow.y())) self._animnext.setTargetObject(w_next) self._animnext.setDuration(self._speed) self._animnext.setEasingCurve(self._easing) self._animnext.setStartValue( QPoint(-offsetX + pnext.x(), offsetY + pnext.y())) self._animnext.setEndValue(QPoint(pnext.x(), pnext.y())) self._next = _next self._now = _now self._active = 1 self._animgroup.start() def _initAnimation(self): """初始化当前页和下一页的动画变量""" # 当前页的动画 self._animnow = QPropertyAnimation( self, propertyName=b'pos', duration=self._speed, easingCurve=self._easing) # 下一页的动画 self._animnext = QPropertyAnimation( self, propertyName=b'pos', duration=self._speed, easingCurve=self._easing) # 并行动画组 self._animgroup = QParallelAnimationGroup( self, finished=self.animationDoneSlot) self._animgroup.addAnimation(self._animnow) self._animgroup.addAnimation(self._animnext) def setCurrentIndex(self, index): # 覆盖该方法实现的动画切换 # super(SlidingStackedWidget, self).setCurrentIndex(index) # 坚决不能调用上面的函数,否则动画失效 self.slideInIdx(index) def setCurrentWidget(self, widget): # 覆盖该方法实现的动画切换 super(SlidingStackedWidget, self).setCurrentWidget(widget) # 坚决不能调用上面的函数,否则动画失效 self.setCurrentIndex(self.indexOf(widget)) def animationDoneSlot(self): """动画结束处理函数""" # 由于重写了setCurrentIndex方法所以这里要用父类本身的方法 # self.setCurrentIndex(self._next) QStackedWidget.setCurrentIndex(self, self._next) w = self.widget(self._now) w.hide() w.move(self._pnow) self._active = 0 def autoStop(self): """停止自动播放""" if hasattr(self, '_autoTimer'): self._autoTimer.stop() def autoStart(self, msec=3000): """自动轮播 :param time: 时间, 默认3000, 3秒 """ if not hasattr(self, '_autoTimer'): self._autoTimer = QTimer(self, timeout=self._autoStart) self._autoTimer.stop() self._autoTimer.start(msec) def _autoStart(self): if self._current == self.count(): self._current = 0 self._current += 1 self.setCurrentIndex(self._current)