我有一个自定义小部件,它采用其他小部件并以滑动方式为它们设置动画。它的目的是像 QStackedWidget 一样工作,但请求的小部件从顶部下拉,并将以前可见的小部件“推”出视线。这或多或少有效,但我看到了奇怪的行为(例如包含的小部件的偏移以及取决于添加的小部件的行为差异),这让我在我的主代码中感到悲伤。
喝了几杯咖啡后,我意识到这个“PageAnimator”实际上应该是一个布局管理器而不是一个小部件,因为它只负责放置(和动画)其他小部件。
下面是我的(仍然有点狡猾的)PageAnimator 小部件的代码,我想把它变成一个合适的布局管理器,因为这对我来说似乎更合乎逻辑。
小部件下方是迄今为止我设法提出的新布局管理器代码。这实际上似乎工作正常,除了小部件是在布局的几何图形之外绘制的,我想确保它们只在其中可见。换句话说,布局的几何图形需要成为小部件的“视口”。
有谁知道如何做到这一点?
干杯,坦率
import sys
from PySide.QtGui import *
from PySide.QtCore import *
class PageAnimator( QWidget ):
'''A widget that takes other widgets and animates them with a sliding motion'''
def __init__( self, parent=None ):
super( PageAnimator, self ).__init__( parent )
self.pages = []
self.visibleWidget = None
self.end = 0
def sizeHint( self ):
return QSize( 400, 400 )
def minimumSizeHint( self ):
return QSize( 400, 400 )
def addPage( self, widget, startPage=False ):
'''Add this widget to the animator. If startPage is notTrue hide the widget'''
widget.setParent( self )
if not startPage:
# POSITION TOOL PAGES OFF SCREEN
widget.setGeometry(0,
-widget.sizeHint().height(),
widget.sizeHint().width(),
widget.sizeHint().height())
self.pages.append( widget )
widget.hide()
else:
widget.move( (self.sizeHint().width()-widget.sizeHint().width())/2, (self.sizeHint().height()-widget.sizeHint().height())/2 )
self.visibleWidget = widget
def resizeEvent( self, event ):
'''keep visible widget centred when resizing parent'''
widget = self.visibleWidget
widget.move( (event.size().width()-widget.sizeHint().width())/2, (event.size().height()-widget.sizeHint().height())/5 )
def change( self, newWidget ):
'''Slide in the new widget and slide out the old one'''
if newWidget == self.visibleWidget:
return
# Slide in
newSize = QSize( newWidget.sizeHint().width(), self.height() )
self.resize( newSize ) # SET VIEWPORT
newWidget.show()
self.animGroup = QParallelAnimationGroup()
slideInAnimation = self.getMoveAnimation( newWidget, -newWidget.sizeHint().height(), 0 )
self.animGroup.addAnimation( slideInAnimation )
# Slide out
oldWidget = self.visibleWidget
if oldWidget:
#slideOutAnimation = self.getMoveAnimation( oldWidget, 0, newWidget.sizeHint().height() )
slideOutAnimation = self.getMoveAnimation( oldWidget, oldWidget.pos().y(), newWidget.sizeHint().height() )
self.animGroup.addAnimation( slideOutAnimation )
slideOutAnimation.finished.connect( oldWidget.hide )
self.animGroup.start()
self.visibleWidget = newWidget
def getMoveAnimation(self, widget, start, end):
'''
Horizontal animation, moves the widget
from "start" y-position to "end" y-position.
'''
moveAnimation = QPropertyAnimation(widget, 'pos')
moveAnimation.setDuration( 700 )
frameWidth = self.style().pixelMetric( QStyle.PM_DefaultFrameWidth )
xpos = widget.pos().x()+5 # NEED TO NOT HARDCODE THE OFFSET HERE BUT CREATE IT DYNAMICALLY
moveAnimation.setStartValue( QPoint( xpos, start ) )
moveAnimation.setEndValue( QPoint( xpos, end ) )
moveAnimation.setEasingCurve( QEasingCurve.OutCubic )
return moveAnimation
这是我模仿 QStackedLayout 的第一段代码,我想将其转换为动画版本以替换上面的小部件代码。它有动画位,但小部件由于某种原因没有移动。
class AnimStackLayout( QLayout ):
'''Like QStackLayout but with sliding animation'''
def __init__( self, parent=None, duration=700 ):
super( AnimStackLayout, self).__init__( parent )
self.duration = duration
self.itemList = []
self.__currentIndex = 0
def addItem( self, item ):
'''add item to layout'''
if self.count() > 0:
item.widget().hide()
self.itemList.append( item )
def count( self ):
'''return the amount of items currently held by the layout'''
return len( self.itemList )
def currentIndex( self ):
'''return the current item index'''
return self.__currentIndex
def currentWidget( self ):
'''return the current widget'''
return self.itemList[ self.__currentIndex ].widget()
def doLayout( self, rect ):
'''do the layout work'''
print 'doing layout work'
x = rect.x()
y = rect.y()
width = rect.width()
height = rect.height()
for item in self.itemList:
itemRect = QRect( x, y, width, height )
item.setGeometry( itemRect )
widget = item.widget()
#widget.setVisible( widget is self.currentWidget() )
def getMoveAnimation( self, widget, direction ):
'''Animation, moves the widget from "start" position to "end" position.'''
assert direction in ( 'enter', 'leave' )
if direction == 'enter':
start = self.geometry().y() - self.geometry().height()
end = self.geometry().y()
elif direction == 'leave':
start = self.geometry().y()
end = self.geometry().y() + self.geometry().height()
moveAnimation = QPropertyAnimation( widget, 'pos' )
moveAnimation.setDuration( self.duration )
xpos = self.geometry().x()
moveAnimation.setStartValue( QPoint( xpos, start ) )
moveAnimation.setEndValue( QPoint( xpos, end ) )
moveAnimation.setEasingCurve( QEasingCurve.OutCubic )
return moveAnimation
def itemAt( self, index ):
'''return the item for the given index'''
if -1 < index < self.count():
return self.itemList[index]
def minimumSize( self ):
return QSize(115,18)
def takeAt( self, index ):
'''remove the item at the given index'''
if -1 < index < self.count():
return self.itemList.pop(index)
return None
def setCurrentIndex( self, index ):
'''
Set the current index to index.
Slides the requested item's widget into view and pushes the previous one out of sight
'''
oldWidget = self.currentWidget()
newWidget = self.itemList[index].widget()
self.__currentIndex = index
if oldWidget is newWidget:
return
self.animGroup = QParallelAnimationGroup()
slideInAnimation = self.getMoveAnimation( newWidget, direction='enter' )
slideOutAnimation = self.getMoveAnimation( oldWidget, direction='leave' )
slideOutAnimation.finished.connect( oldWidget.hide )
self.animGroup.addAnimation( slideInAnimation )
self.animGroup.addAnimation( slideOutAnimation )
newWidget.show()
self.animGroup.start()
def setCurrentWidget( self, widget ):
'''set the current widget to widget'''
assert widget in [ item.widget() for item in self.itemList ]
self.setCurrentIndex( self.itemList.index(widget) )
def setGeometry( self, rect ):
super( AnimStackLayout, self ).setGeometry( rect )
self.doLayout( rect )
def sizeHint( self ):
return self.minimumSize()
if __name__ == '__main__':
from copy import copy
app = QApplication( sys.argv )
# WIDGETS
mainWidget = QWidget()
btn1 = QPushButton( 'page A' )
btn2 = QPushButton( 'page B' )
frame1 = QLabel( 'A (custom layout)' )
frame1.setFrameStyle( QFrame.Box )
frame2 = QLabel( 'B (custom layout)' )
frame2.setFrameStyle( QFrame.Box )
frame3 = QLabel( 'A (default layout)' )
frame3.setFrameStyle( QFrame.Box )
frame4 = QLabel( 'B (default layout)' )
frame4.setFrameStyle( QFrame.Box )
# LAYOUTS
mainLayout = QVBoxLayout()
stackLayout = AnimStackLayout( duration=1000 ) # MAKE THIS WORK LIKE QStackedLayout
stackLayoutDef = QStackedLayout() # FOR COMPARISON
mainWidget.setLayout( mainLayout )
# PLACE WIDGETS
stackLayout.addWidget( frame1 )
stackLayout.addWidget( frame2 )
stackLayoutDef.addWidget( frame3 )
stackLayoutDef.addWidget( frame4 )
mainLayout.addWidget( btn1 )
mainLayout.addWidget( btn2 )
mainLayout.addLayout( stackLayoutDef )
mainLayout.addLayout( stackLayout )
# CONNECT
def changeToPage( index ):
stackLayout.setCurrentIndex( index )
stackLayoutDef.setCurrentIndex( index )
btn1.clicked.connect( lambda: changeToPage(0) )
btn2.clicked.connect( lambda: changeToPage(1) )
# GO
mainWidget.show()
sys.exit( app.exec_() )