257 lines
8.3 KiB
QML
257 lines
8.3 KiB
QML
|
|
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
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|