import QtQuick 2.15 import QtQuick.Controls 2.15 import QtGraphicalEffects 1.15 Item { id: root width: 800 height: 400 property var originalModel: [] property alias model: listView.model property int currentIndex: listView.currentIndex property int cardSpacing : -100 property int switchInterval: 999999999 signal clickedFunction(data:var) // 初始化循环模型 onOriginalModelChanged: { if(originalModel.length <= 0)return var loopedModel = []; loopedModel = loopedModel.concat(originalModel.slice(-1)); loopedModel = loopedModel.concat(originalModel); loopedModel = loopedModel.concat(originalModel.slice(0, 2)); listView.model = loopedModel; } Timer { id: autoSwitch interval: switchInterval repeat: true running: true onTriggered: { listView.currentIndex = listView.currentIndex + 1; } } ListView { id: listView anchors.fill: parent orientation: ListView.Horizontal snapMode: ListView.SnapOneItem highlightRangeMode: ListView.StrictlyEnforceRange preferredHighlightBegin: (width - delegateWidth) / 2 preferredHighlightEnd: preferredHighlightBegin + delegateWidth spacing: -100 leftMargin: delegateWidth * 0.2 rightMargin: delegateWidth * 0.2 boundsBehavior: Flickable.StopAtBounds flickDeceleration: 1500 readonly property real delegateWidth: width * 0.85 onCurrentIndexChanged: { //位移到最后跳回第一 if (currentIndex === (originalModel.length + 1)) { currentIndex = 1; } //位移到最前跳回最后 else if (currentIndex === 0) { currentIndex = originalModel.length; } } onModelChanged: { if(model && model.length > 0){ currentIndex = 1 listView.positionViewAtIndex(1, ListView.Center) } } Component.onCompleted: { } delegate: Item { id: delegateItem width: listView.delegateWidth height: listView.height // 被动式偏移计算 property real _rawCenterOffset: 0 property real _stableCenterOffset: 0 property real centerOffset: _stableCenterOffset property real itemScale: 0.8 + (1 - Math.abs(centerOffset)) * 0.2 property real sideMargin: Math.abs(centerOffset) * 40 Timer { id: offsetTracker interval: 16 repeat: true running: true onTriggered: { const itemCenter = x + width / 2 const viewCenter = listView.contentX + listView.width / 2 _rawCenterOffset = (itemCenter - viewCenter) / (listView.width / 2) if (Math.abs(_rawCenterOffset - _stableCenterOffset) > 0.001) { _stableCenterOffset = _rawCenterOffset } } } // // 延迟定位修正 // Timer { // id: positionCorrector // interval: 50 // onTriggered: { // if (listView.currentIndex === index && Math.abs(centerOffset) > 0.1) { // listView.positionViewAtIndex(index, ListView.Center) // } // } // } // onCenterOffsetChanged: { // if (index === listView.currentIndex) { // positionCorrector.restart() // } // } Rectangle { id: imageContainer anchors { fill: parent leftMargin: delegateItem.sideMargin * 0.8 rightMargin: delegateItem.sideMargin * 0.8 topMargin: 20 * (1 - Math.abs(delegateItem.centerOffset)) bottomMargin: 20 * (1 - Math.abs(delegateItem.centerOffset)) } radius: 10 border.color: "#e0e0e0" border.width: 2 clip: true Image { id: contentImage anchors.fill: parent source: modelData.img fillMode: Image.PreserveAspectCrop layer.enabled: true layer.effect: OpacityMask { maskSource: Rectangle { width: imageContainer.width height: imageContainer.height radius: imageContainer.radius } } } layer.enabled: true layer.effect: DropShadow { radius: 8 samples: 16 color: "#40000000" } } MouseArea { id: mouse_effect anchors.fill: parent hoverEnabled: true onClicked: { listView.currentIndex = index clickEffect.start(mouseX, mouseY) var realIndex = index - 1; if (realIndex < 0) { realIndex = originalModel.length - 1; } else if (realIndex >= originalModel.length) { realIndex = 0; } root.clickedFunction(originalModel[realIndex]); } onWheel: { var buf = 1; if (wheel.angleDelta.y > 0) buf = -1 var newIndex = listView.currentIndex + buf; listView.currentIndex = newIndex; } Rectangle { id: clickEffect width: 0 height: width radius: width / 2 color: "#40ffffff" visible: false x: 0 y: 0 function start(x, y) { clickEffect.x = 0 - delegateItem.width clickEffect.y = 0 - delegateItem.height visible = true anim.start() } SequentialAnimation { id: anim ParallelAnimation { NumberAnimation { target: clickEffect property: "width" from: 0 to: Math.max(delegateItem.width, delegateItem.height) * 4 duration: 800 easing.type: Easing.OutQuad } NumberAnimation { target: clickEffect property: "opacity" from: 0.8 to: 0 duration: 800 easing.type: Easing.OutQuad } } ScriptAction { script: clickEffect.visible = false } } } } transform: Scale { origin.x: delegateItem.width / 2 origin.y: delegateItem.height / 2 xScale: itemScale yScale: itemScale } Behavior on scale { NumberAnimation { duration: 300; easing.type: Easing.OutQuad } } } layoutDirection: Qt.LeftToRight highlightMoveDuration: 300 } PageIndicator { anchors { bottom: parent.bottom horizontalCenter: parent.horizontalCenter margins: 1 } count: originalModel.length currentIndex: { var index = listView.currentIndex - 1 >= 0? listView.currentIndex - 1 : originalModel.length - 1 if(index >= originalModel.length)index = 0 return index } } }