0

我有一个自定义小部件,它采用其他小部件并以滑动方式为它们设置动画。它的目的是像 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_() )
4

0 回答 0