236 lines
7.9 KiB
Python
236 lines
7.9 KiB
Python
#!/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)
|