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