/*
* Copyright (c) Huawei Technologies Co., Ltd. 2024-2024. All rights reserved.
*/
package smartrefresh.toprefresh
internal import ohos.resource_manager.*
import ohos.state_macro_manage.*
import std.math.*
import std.time.*
import std.sync.*
import smartrefresh.utils.*
internal import ohos.hilog.*
import ohos.prompt_action.PromptAction
/**
* Created on 2024/8/27
*/
@Component
public class Classics {
@Link
var model: SmartRefreshModel
@State
var classicsSimple: ClassicsSimpleModel = ClassicsSimpleModel().initData()
@State
var refresh: Bool = false
@State
var refreshState: Bool = false
var information: String = ""
var arrowDown: String = "M 0 0 L0 1"
var arrowUp: String = "M 25 100 L25 -20 L0 15 L25 -20 L50 15 L25 -20"
var pullRefreshImage: AppResource = @r(app.media.classicsRefreshing)
@State
var pullRefreshImageAngle: Float32 = 0.0
var screenWidth: Int32 = getDefaultDisplaySync().width
// 动画刷新速率,30ms刷新一次
var timerRefreshRate: Duration = 30 * Duration.millisecond
// 记录结束动画持续时长
var endAnimationTime: Duration = 0 * Duration.millisecond
// 是否可以刷新,已经在刷新中为false,可以刷新为true
var refreshCanStart: Bool = true
protected override func aboutToAppear() {
this.model.setRefreshHeaderCallback({
=> draw()
})
if (model.initRefreshing) {
model.refreshHeaderCallback()
model.initRefreshing = false
}
}
protected override func aboutToDisappear(): Unit {
if (model.headerRefreshId.isSome()) {
model.headerRefreshId.getOrThrow().cancel()
model.headerRefreshId = Option<Timer>.None
}
}
func calculationArrowSize(): Unit {
let arrowDownHeight = 20 + model.drawableArrowSize
let arrowDownBottomPointCoordinate = "L25 ${arrowDownHeight.toString()}"
let arrowDownLeftPointCoordinate = "L0 ${(arrowDownHeight - 35).toString()}"
let arrowDownRightPointCoordinate = "L50 ${(arrowDownHeight - 35).toString()}"
arrowDown = "M 25 20 ${arrowDownBottomPointCoordinate} ${arrowDownLeftPointCoordinate} ${arrowDownBottomPointCoordinate} ${arrowDownRightPointCoordinate} ${arrowDownBottomPointCoordinate}"
let arrowUpHeight = 20 + model.drawableArrowSize
let arrowUpBottomPointCoordinate = "M25 ${arrowUpHeight.toString()}"
arrowUp = "${arrowUpBottomPointCoordinate} L25 20 L0 55 L25 20 L50 55 L25 20"
}
func draw(): Unit { //刷新样式
calculateTime()
if (model.getDrawableArrow()) { // 计算箭头的长度
calculationArrowSize()
}
endAnimationTime = 0 * Duration.millisecond
if (model.getDrawableProgress()) {
refreshingImage()
} else {
// model.headerRefreshId = Timer.repeatDuring(model.refreshDuration, 0 * Duration.millisecond, 20 * Duration.millisecond, {=> onDraw()
// }, style: CatchupStyle.Burst)
refreshingPath()
}
}
func refreshingImage(): Unit {
// 触发主界面数据刷新,控制只触发一次
if (model.refreshState == 2 && refreshCanStart) {
refreshCanStart = false
model.refreshHeaderStart()
}
model.headerRefreshId = Timer.once(timerRefreshRate) {
=>
if (model.refreshState == 3) { // 刷新完成后
refreshCanStart = true
// 更新持续时间
endAnimationTime += timerRefreshRate
// 判断持续时间是否超过设置
if (endAnimationTime >= model.getRefreshFinishStopDuration()) {
// 超过就通知主界面结束刷新
endAnimationTime = 0 * Duration.millisecond
model.refreshHeaderComplete()
} else {
refreshingImage() // 继续调用自己,保持刷新
}
} else { // 刷新中,调用自己
if (model.refreshState == 2) {
if (pullRefreshImageAngle > 360.0) {
pullRefreshImageAngle = pullRefreshImageAngle - 360.0
}
pullRefreshImageAngle = pullRefreshImageAngle + 10.0
}
refreshingImage()
}
}
}
func refreshingPath(): Unit {
// 触发主界面数据刷新,控制只触发一次
if (model.refreshState == 2 && refreshCanStart) {
refreshCanStart = false
model.refreshHeaderStart()
}
model.headerRefreshId = Timer.once(timerRefreshRate) {
=>
if (model.refreshState == 3) { // 刷新完成后
refreshCanStart = true
// 更新持续时间
endAnimationTime += timerRefreshRate
// 判断持续时间是否超过设置
if (endAnimationTime >= model.getRefreshFinishStopDuration()) {
// 超过就通知主界面结束刷新
endAnimationTime = 0 * Duration.millisecond
model.refreshHeaderComplete()
} else {
onDraw() // 继续调用自己,保持刷新
}
} else { // 刷新中,调用自己
onDraw()
}
}
}
func calculateTime(): Unit {
let index = 0
information = ""
let month = model.lastRefreshTime.month
let day: Int64 = model.lastRefreshTime.dayOfMonth
let hour: Int64 = model.lastRefreshTime.hour
let minute: Int64 = model.lastRefreshTime.minute
information = "${month}月${day}日 ${hour}:${minute}"
}
func onDraw(): Unit {
if (classicsSimple.isSpinning) {
if ((classicsSimple.barExtraLength + classicsSimple.minAngle) < 180.0) {
classicsSimple.angleFlag = 0.0
} else {
classicsSimple.angleFlag = 1.0
}
// 增量时间
let deltaTime = DateTime.now().toUnixTimeStamp().toNanoseconds() - classicsSimple.lastTimeAnimated
let deltaNormalized = Float64(deltaTime) * classicsSimple.spinSpeed / 1000000000.0
classicsSimple.mInitprogress += deltaNormalized
if (classicsSimple.mInitprogress > 360.0) {
classicsSimple.mInitprogress -= 360.0
}
let radian = classicsSimple.mInitprogress * Float64.PI / 180.0
classicsSimple.startX = classicsSimple.circleRadius + (classicsSimple.radius * sin(radian))
classicsSimple.startY = classicsSimple.circleRadius - (classicsSimple.radius * cos(radian))
// 计算需要额外绘制的角度
updateBarLength(deltaTime)
classicsSimple.lastTimeAnimated = DateTime.now().toUnixTimeStamp().toNanoseconds()
// 计算坐标
calculation()
classicsSimple.targetAngle = classicsSimple.mInitprogress + classicsSimple.barExtraLength + classicsSimple.
minAngle
if (classicsSimple.targetAngle > 360.0) {
classicsSimple.targetAngle -= 360.0
}
classicsSimple.svgPath = "M ${classicsSimple.startX} ${classicsSimple.startY} A ${classicsSimple.radius} ${classicsSimple.radius}, 0, ${classicsSimple.angleFlag}, ${classicsSimple.directionFlag}, ${classicsSimple.endX} ${classicsSimple.endY}"
classicsSimple.mProgress = "Progress:"
refreshingPath()
}
}
func updateBarLength(deltaTimeInMilliSeconds: Int64): Unit {
// 到达最大小最小角的保持时间
if (classicsSimple.pausedTimeWithoutGrowing >= classicsSimple.pauseGrowingTime) {
classicsSimple.timeStartGrowing += deltaTimeInMilliSeconds
// 计算递增递减
if (classicsSimple.timeStartGrowing > classicsSimple.barSpinCycleTime) {
classicsSimple.timeStartGrowing -= classicsSimple.barSpinCycleTime
classicsSimple.pausedTimeWithoutGrowing = 0
// 递增递减切换
classicsSimple.barGrowingFromFront = !classicsSimple.barGrowingFromFront
}
let distance = cos(Float64(classicsSimple.timeStartGrowing / classicsSimple.barSpinCycleTime + 1) * Float64.PI) /
2.0 + 0.5
// 目标角度
let destLength = (classicsSimple.maxAngle - classicsSimple.minAngle)
if (classicsSimple.barGrowingFromFront) {
// 递增
classicsSimple.barExtraLength = distance * destLength
} else {
//递减
let newLength = destLength * (1.0 - distance)
classicsSimple.mInitprogress += (classicsSimple.barExtraLength - newLength)
classicsSimple.barExtraLength = newLength
}
} else {
classicsSimple.pausedTimeWithoutGrowing += deltaTimeInMilliSeconds
}
}
func calculation(): Unit {
// 计算弧度
let radian = classicsSimple.targetAngle * Float64.PI / 180.0
classicsSimple.endX = classicsSimple.circleRadius + (classicsSimple.radius * sin(radian))
classicsSimple.endY = classicsSimple.circleRadius - (classicsSimple.radius * cos(radian))
// 控制起点终点无限接近时正常画圆
if ((classicsSimple.circleRadius - 1.0) < classicsSimple.endX && classicsSimple.endX < classicsSimple.
circleRadius && (classicsSimple.circleRadius - classicsSimple.endX) < 0.01) {
classicsSimple.endX = classicsSimple.circleRadius - 0.01
}
}
func calculateTopPosition(): Float64 {
if (model.classicsSpinnerStyle == SpinnerStyleEnum.TRANSLATE) {
return model.headerHeight - model.textTimeMarginTop
}
if (this.model.classicsSpinnerStyle == SpinnerStyleEnum.SCALE) {
return (model.headerHeight - 150.0) / 2.0
}
if (this.model.classicsSpinnerStyle == SpinnerStyleEnum.FIXED_BEHIND) {
return model.textTimeMarginTop
}
return (model.headerHeight - 150.0) / 2.0
}
func build() {
Flex() {
if (model.refreshState == 2) { //松开过后的刷新样式
Stack(Alignment.Start) {
if (model.getDrawableProgress()) {
Row() {
Image(pullRefreshImage).width(testLog(model.drawableProgressSize, model)).height(
model.drawableProgressSize).rotate(
x: 0.0,
y: 0.0,
z: 1.0,
centerX: 50.percent,
centerY: 50.percent,
angle: pullRefreshImageAngle
)
}.margin(left: 100)
} else {
Path() //圆的轨道
.commands(classicsSimple.svgPath).strokeWidth(classicsSimple.barWidth).fillOpacity(0).
width(100).height(200).stroke(model.getAccentColor()).margin(left: 50.0,
top: 50.0 + calculateTopPosition())
}
Column() {
Row() {
Text(model.refreshHeaderRefreshing).fontSize(30).height(50).margin(
top: calculateTopPosition(), left: model.drawableMarginRight).fontColor(
model.getAccentColor()).maxLines(2).textOverflow(TextOverflow.Ellipsis)
}
if (model.getTimeShowState()) {
Row() {
Text("更新于${information}").fontSize(model.textSizeTime).align(Alignment.Center).height(
50).margin(top: calculateTopPosition(), left: model.drawableMarginRight).fontColor(
model.getAccentColor()).maxLines(2).textOverflow(TextOverflow.Ellipsis)
}.width(100.percent)
}
}.width(70.percent).alignItems(HorizontalAlign.Center).margin(left: 80.0,
top: (this.model.headerHeight - 150.0) / 2.0)
}.width(100.percent).height(100.percent)
} else if (model.refreshState == 1) { //拖住过程中的样式
if (model.headerHeight > (model.initHeaderHeight / 2.0)) {
Stack(Alignment.Start) {
Path().commands(if (model.headerHeight < model.initHeaderHeight) {
arrowDown
} else {
arrowUp
}).strokeWidth(classicsSimple.barWidth).fillOpacity(0).stroke(model.getAccentColor()).margin(
left: 80.0, top: calculateTopPosition()).visibility(
if (model.getDrawableArrow()) {
Visibility.Visible
} else {
Visibility.None
})
Column() {
Row() {
Text(
if (model.headerHeight < model.initHeaderHeight) {
model.refreshHeaderPullDown
} else {
model.refreshHeaderRelease
}).fontSize(model.textSizeTitle).height(50).fontColor(model.getAccentColor()).margin(
top: calculateTopPosition(), left: model.drawableMarginRight).maxLines(2).
textOverflow(TextOverflow.Ellipsis)
}
if (model.getTimeShowState()) {
Row() {
Text("更新于${information}").fontSize(model.textSizeTime).fontColor(
model.getAccentColor()).height(50).margin(top: calculateTopPosition(),
left: model.drawableMarginRight).maxLines(2).textOverflow(TextOverflow.Ellipsis)
}
}
}.width(70.percent).alignItems(HorizontalAlign.Center).margin(left: 80.0,
top: (model.headerHeight - 150.0) / 2.0)
}.width(100.percent).height(100.percent)
}
} else if (model.refreshState == 3) {
Row() {
Text(model.refreshHeaderRefreshFinish).fontSize(40).fontColor(model.getAccentColor()).margin(
top: 60.0, left: px2vp(Int64(screenWidth / 3).px).getOrThrow().value).maxLines(2).textOverflow(
TextOverflow.Ellipsis)
}.alignItems(VerticalAlign.Center)
}
}.backgroundColor(model.getBackgroundShadowColor()).width(100.percent)
}
}
func testLog(msg: String): String {
Hilog.info(0x0000, "classicsTag", "msg:${msg}")
return msg
}
func testLog(msg: Int64, mode: SmartRefreshModel): Int64 {
Hilog.info(0x0000, "classicsTag", "msg:${msg}")
//Hilog.info(0x0000, "classicsTag", "SmartRefreshModel:${mode.refreshState}")
return msg
}
@Observed
public class ClassicsSimpleModel {
//画笔宽度
@Publish
var barWidth: Float64 = 5.0
//轮圈宽度
@Publish
var rimWidth: Float64 = 5.0
//画笔颜色
@Publish
var barColor: Color = Color(0X5588FF)
// 大/小角度弧标志 0为小角度
@Publish
var angleFlag: Float64 = 0.0
// 圆弧最小角度
@Publish
var minAngle: Float64 = 16.0
// 圆弧最大角度
@Publish
var maxAngle: Float64 = 270.0
//半径
@Publish
var circleRadius: Float64 = 60.0
//真实半径
@Publish
var radius: Float64 = 55.0
// 在 minAngle 基础上,额外需要绘制的角度
@Publish
var barExtraLength: Float64 = 0.0
// 需要绘制到的角度
@Publish
var targetAngle: Float64 = 0.0
// 圆弧递增或递减的标记 true表示递增
@Publish
var barGrowingFromFront: Bool = true
// 逆时针顺时针标志 1表示顺时针
@Publish
var directionFlag: UInt8 = 1
// svg路径
@Publish
var svgPath: String = ""
// 绘制终点的X轴坐标
@Publish
var endX: Float64 = 0.0
//绘制终点的Y轴坐标
@Publish
var endY: Float64 = 0.0
//起始位置
@Publish
var startX: Float64 = 60.0
@Publish
var startY: Float64 = 5.0
// 目标进度值
@Publish
var mTargetProgress: Float64 = 1.0
// 进度值
@Publish
var mProgress: String = "0"
// 起始角度
@Publish
var mInitprogress: Float64 = 0.0
//轮圈颜色
@Publish
var rimColor: Color = Color.WHITE
// 近似闭合的坐标
@Publish
var closureY: Float64 = 0.0
// 每1ms的旋转角度,例:230 每1s旋转 230 度 (270-16)/0.46 = 230
@Publish
var spinSpeed: Float64 = 230.0
//轮圈的svg路径
@Publish
var rimSvgPath: String = ""
// 是否是动态进度轮
@Publish
var isSpinning: Bool = true
// 是否是线性进度轮
@Publish
var linearProgress: Bool = false
//圆弧处在最大或者最小角度时,保持当时状态的时间,简而言之,当圆弧为的角度为 barLength
//圆弧处在最大或者最小角度时,保持当时状态的时间,简而言之,当圆弧为的角度为 barLength
// 或者 barMaxLength 时,都会在 pauseGrowingTime 这个时间后才会继续变化,最大向最小变化,最小向最大变化,可以理解为 delayTime
@Publish
var pauseGrowingTime: Int64 = 200
// 圆弧 递增/递减 已经经历时间
@Publish
var timeStartGrowing: Int64 = 0
//圆弧 从最小到最大/从最大到最小 的总时间
@Publish
var barSpinCycleTime: Int64 = 460
// 圆弧在 最大/最小 角度保持的时间
@Publish
var pausedTimeWithoutGrowing: Int64 = 0
//上一次绘制的时间
@Publish
var lastTimeAnimated: Int64 = DateTime.now().toUnixTimeStamp().toNanoseconds()
// 定时器
var interval: ?Timer = Option<Timer>.None
func initData(): ClassicsSimpleModel {
radius = circleRadius - barWidth
startX = circleRadius
startY = barWidth
// 近似一个完整的回环(轮圈)
let closureRadian = 359.99 * Float64.PI / 180.0
// 近似闭合的坐标
let closureX = circleRadius + (radius * sin(closureRadian))
closureY = circleRadius - (radius * cos(closureRadian))
rimSvgPath = "M${startX} ${startY} A ${radius} ${radius}, 0, 1, ${directionFlag}, ${closureX} ${closureY}Z"
// 计算弧度
if (isSpinning) {
let radian = minAngle * Float64.PI / 180.0
endX = circleRadius + (radius * sin(radian))
endY = circleRadius - (radius * cos(radian))
svgPath = "M${startX} ${startY} A ${radius} ${radius}, 0, ${angleFlag}, ${directionFlag}, ${endX} ${endY}"
} else {
if (mTargetProgress == 1.0) {
maxAngle = 359.99
} else {
maxAngle = mTargetProgress * 360.0
}
}
if (!interval.isNone()) {
interval.getOrThrow().cancel()
}
return this
}
func setCircleRadius(circleRadius: Float64): ClassicsSimpleModel {
this.circleRadius = circleRadius
return this
}
func getCircleRadius(): Float64 {
return circleRadius
}
func setBarColor(barColor: Color): ClassicsSimpleModel {
this.barColor = barColor
return this
}
func getBarColor(): Color {
return barColor
}
func setRimColor(rimColor: Color): ClassicsSimpleModel {
this.rimColor = rimColor
return this
}
func getRimColor(): Color {
return rimColor
}
func setRimWidth(rimWidth: Float64): ClassicsSimpleModel {
this.rimWidth = rimWidth
return this
}
func getRimWidth(): Float64 {
return this.rimWidth
}
func setBarWidth(barWidth: Float64): ClassicsSimpleModel {
this.barWidth = barWidth
return this
}
func getBarWidth(): Float64 {
return barWidth
}
func setProgress(targetProgress: Float64): ClassicsSimpleModel {
if (isSpinning) {
mInitprogress = 0.0
isSpinning = false
}
if (targetProgress > 1.0) {
mTargetProgress = 1.0
} else if (targetProgress < 0.0) {
mTargetProgress = 0.0
}
this.mTargetProgress = targetProgress
return this
}
// 获取当前的进度值
func getProgress(): String {
return mProgress
}
func setSpinning(isSpinning: Bool): ClassicsSimpleModel {
this.isSpinning = isSpinning
return this
}
func setLinearProgress(linearProgress: Bool): ClassicsSimpleModel {
this.linearProgress = linearProgress
return this
}
func setSpinSpeed(spinSpeed: Float64): ClassicsSimpleModel {
this.spinSpeed = spinSpeed
return this
}
func getSpinSpeed(): Float64 {
return spinSpeed
}
// Reset the count (in increment mode)
func resetCount(): Unit {
mInitprogress = 0.0
mTargetProgress = 0.0
}
func stopSpinning(): Unit {
isSpinning = false
mInitprogress = 0.0
mTargetProgress = 0.0
}
// Puts the view on spin mode
func spin(): Unit {
lastTimeAnimated = DateTime.now().toUnixTimeStamp().toNanoseconds()
isSpinning = true
}
// 将进度设置为特定值,栏将立即设置为该值 @param progress 0 和 1 之间的进度
func setInstantProgress(progress: Float64): Unit {
if (isSpinning) {
mInitprogress = 0.0
isSpinning = false
}
var temp = progress
if (progress > 1.0) {
temp = progress - 1.0
} else if (progress < 0.0) {
temp = 0.0
}
if (temp == mTargetProgress) {
return
}
mTargetProgress = min(temp * 360.0, 360.0)
mInitprogress = mTargetProgress
lastTimeAnimated = DateTime.now().toUnixTimeStamp().toNanoseconds()
}
func spinning(): Bool {
return isSpinning
}
}