QML 自带的 BusyIndicator 自定义效果不大理响,所以自己做了一些 Loading 样式。
(新增了两种样式:https://gongjianbo1992.blog.csdn.net/article/details/122824084)
本文效果如下:
代码链接(CustomLoading1 / 2):https://github.com/gongjianbo/QmlComponentStyle
实现代码:
import QtQuick 2.12
import QtQuick.Controls 2.12
//自定义 loading 效果
//龚建波 2021-1-17
Item {
id: control
//item圆圈个数
property int itemCount: 10
//item圆圈直径大小
property int itemSize: 16
//item变大范围
property int itemExpand: 10
//item圆圈颜色
property color itemColor: "green"
//当前item,配合动画
property int itemIndex: 0
//轮转一次的时长
property int duration: 1500
//
property bool running: visible
implicitHeight: 160
implicitWidth: 160
//index从0到count轮转,转到对应item就变大且不透明
//animation也可以换成timer
NumberAnimation{
target: control
property: "itemIndex"
from: 0
to: control.itemCount-1
loops: Animation.Infinite
duration: control.duration
running: control.running
}
//加一层item.margin来容纳item大小变化
Item{
id: content
anchors.fill: parent
anchors.margins: control.itemExpand/2+1
Repeater{
id: repeater
model: control.itemCount
Rectangle{
id: item
height: control.itemSize
width: height
x: content.width/2 - width/2
y: content.height/2 - height/2
radius: height/2
color: control.itemColor
//环绕在中心
transform: [
Translate {
y: content.height/2-height/2
},
Rotation {
angle: index / repeater.count * 360
origin.x: width/2
origin.y: height/2
}
]
//轮转到当前item时就切换状态
state: control.itemIndex===index?"current":"normal"
states: [
State {
name: "current"
PropertyChanges {
target: item
opacity: 1
height: control.itemSize+control.itemExpand
}
},
State {
name: "normal"
PropertyChanges {
target: item
opacity: 0.1
height: control.itemSize
}
}
]
//大小和透明度的状态过渡
transitions: [
Transition {
from: "current"
to: "normal"
NumberAnimation{
properties: "opacity,height"
duration: control.duration
}
},
Transition {
from: "normal"
to: "current"
NumberAnimation{
properties: "opacity,height"
duration: 0
}
}
]
}
}
}
}
import QtQuick 2.12
import QtQuick.Controls 2.12
//自定义 loading 效果
//目前角度是设定死的,可以根据直径算
//龚建波 2021-1-17
Item {
id: control
//item圆圈个数
property int itemCount: 4
//item圆圈直径
property int itemSize: 20
//item圆圈颜色
property color itemColor: "green"
//转一次时长
property int duration: 3000
//
property bool running: visible
implicitHeight: 160
implicitWidth: 160
Item{
id: content
anchors.fill: parent
anchors.margins: 5
Repeater{
id: repeater
model: control.itemCount
//长方形wrapper用来旋转
Item{
id: wrapper
width: control.itemSize
height: content.height
x: content.width/2-width/2
//todo 目前角度是设定死的,可以根据直径算
rotation: -index*20
//动画序列,根据顺序做了间隔
SequentialAnimation {
running: control.running
loops: Animation.Infinite
NumberAnimation{
duration: index*100
}
ParallelAnimation{
RotationAnimation{
target: wrapper
from: -index*20
to: 360-index*20
duration: control.duration
easing.type: Easing.OutCubic
}
SequentialAnimation{
NumberAnimation{
target: item
property: "opacity"
from: 0
to: 1
duration: control.duration*1/8
}
NumberAnimation{
duration: control.duration*3/4
}
}
}
NumberAnimation{
duration: (control.itemCount-index)*100
}
}
//小圆圈
Rectangle{
id: item
height: control.itemSize
width: height
radius: height/2
color: control.itemColor
}
}
}
}
}
2021-2-11 优化了第二个 loading:
import QtQuick 2.12
import QtQuick.Controls 2.12
//自定义 loading 效果
//目前角度是设定死的,可以根据直径算
//龚建波 2021-1-17
//2021-2-11代码优化
Item {
id: control
//item圆圈个数
property int itemCount: 4
//item圆圈直径
property int itemSize: 20
//item圆圈颜色
property color itemColor: "green"
//转一次时长
property int duration: 3000
//
property bool running: visible
implicitHeight: 160
implicitWidth: 160
Item{
id: content
anchors.fill: parent
anchors.margins: 5 + control.itemSize/2
Repeater{
id: repeater
model: control.itemCount
//旋转的小球
Rectangle{
id: item
width: control.itemSize
height: control.itemSize
color: control.itemColor
radius: width/2
//[1].圆边上点坐标计算公式(角度从右端逆时针增长)
//x1 = x0 + r * cos(angle * PI / 180)
//y1 = y0 + r * sin(angle * PI /180)
//[2].js Math用的弧度
//1弧度bai=180/pai 度
//1度=pai/180 弧度
//[3].Qt是屏幕坐标系,所以y值取反一下
//y1 = y0 - r * sin(angle * PI /180)
//再把相位调到90度从顶上开始转,这样三角函数可以简化下
//sin(π/2+α)=cosα
//cos(π/2+α)=-sinα
//于是有了
//x1 = x0 - r * sin(angle * PI / 180)
//y1 = y0 - r * cos(angle * PI /180)
//[4].此时逐渐增大角度就是逆时针转的,想要顺时针转又得再取反一次坐标
//或者改为逐渐减小
//我选择取反x
//x1 = x0 + r * sin(angle * PI / 180)
//[5].最后把小球的半径偏移去掉,就是围绕圆心转的效果了
//不然默认起点为左上角,会往右下角偏离一点
//(我把wrapper的margin加了size/2,这样就不用减掉半径放值越界了)
x: content.width/2 - control.itemSize/2 + content.width/2 * Math.sin(rotate/360*6.283185307179)
y: content.height/2 - control.itemSize/2 - content.height/2 * Math.cos(rotate/360*6.283185307179)
//rotate表示角度,范围[0,360],初始值目前为固定的
property real rotate: -index*20
//动画序列,根据顺序做了间隔
SequentialAnimation {
running: control.running
loops: Animation.Infinite
NumberAnimation{
duration: index*100
}
ParallelAnimation{
NumberAnimation{
target: item
property: "rotate"
from: -index*20
to: 360-index*20
duration: control.duration
easing.type: Easing.OutCubic
}
SequentialAnimation{
NumberAnimation{
target: item
property: "opacity"
from: 0
to: 1
duration: control.duration*1/8
}
NumberAnimation{
duration: control.duration*3/4
}
}
}
NumberAnimation{
duration: (control.itemCount-index)*100
}
}
}
}
}
}