1 /* 2 * Copyright 2021 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * https://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.google.accompanist.swiperefresh 18 19 import androidx.compose.runtime.getValue 20 import androidx.compose.runtime.mutableStateOf 21 import androidx.compose.runtime.setValue 22 import androidx.compose.ui.geometry.Offset 23 import androidx.compose.ui.geometry.Rect 24 import androidx.compose.ui.geometry.Size 25 import androidx.compose.ui.geometry.center 26 import androidx.compose.ui.graphics.Color 27 import androidx.compose.ui.graphics.Path 28 import androidx.compose.ui.graphics.PathFillType 29 import androidx.compose.ui.graphics.StrokeCap 30 import androidx.compose.ui.graphics.drawscope.DrawScope 31 import androidx.compose.ui.graphics.drawscope.Stroke 32 import androidx.compose.ui.graphics.drawscope.rotate 33 import androidx.compose.ui.graphics.painter.Painter 34 import androidx.compose.ui.unit.dp 35 import kotlin.math.min 36 37 /** 38 * A private class to do all the drawing of SwipeRefreshIndicator, which includes progress spinner 39 * and the arrow. This class is to separate drawing from animation. 40 * Adapted from CircularProgressDrawable. 41 */ 42 internal class CircularProgressPainter : Painter() { 43 var color by mutableStateOf(Color.Unspecified) 44 var alpha by mutableStateOf(1f) 45 var arcRadius by mutableStateOf(0.dp) 46 var strokeWidth by mutableStateOf(5.dp) 47 var arrowEnabled by mutableStateOf(false) 48 var arrowWidth by mutableStateOf(0.dp) 49 var arrowHeight by mutableStateOf(0.dp) 50 var arrowScale by mutableStateOf(1f) 51 <lambda>null52 private val arrow: Path by lazy { 53 Path().apply { fillType = PathFillType.EvenOdd } 54 } 55 56 var startTrim by mutableStateOf(0f) 57 var endTrim by mutableStateOf(0f) 58 var rotation by mutableStateOf(0f) 59 60 override val intrinsicSize: Size 61 get() = Size.Unspecified 62 applyAlphanull63 override fun applyAlpha(alpha: Float): Boolean { 64 this.alpha = alpha 65 return true 66 } 67 onDrawnull68 override fun DrawScope.onDraw() { 69 rotate(degrees = rotation) { 70 val arcRadius = arcRadius.toPx() + strokeWidth.toPx() / 2f 71 val arcBounds = Rect( 72 size.center.x - arcRadius, 73 size.center.y - arcRadius, 74 size.center.x + arcRadius, 75 size.center.y + arcRadius 76 ) 77 val startAngle = (startTrim + rotation) * 360 78 val endAngle = (endTrim + rotation) * 360 79 val sweepAngle = endAngle - startAngle 80 drawArc( 81 color = color, 82 alpha = alpha, 83 startAngle = startAngle, 84 sweepAngle = sweepAngle, 85 useCenter = false, 86 topLeft = arcBounds.topLeft, 87 size = arcBounds.size, 88 style = Stroke( 89 width = strokeWidth.toPx(), 90 cap = StrokeCap.Square 91 ) 92 ) 93 if (arrowEnabled) { 94 drawArrow(startAngle, sweepAngle, arcBounds) 95 } 96 } 97 } 98 DrawScopenull99 private fun DrawScope.drawArrow(startAngle: Float, sweepAngle: Float, bounds: Rect) { 100 arrow.reset() 101 arrow.moveTo(0f, 0f) 102 arrow.lineTo( 103 x = arrowWidth.toPx() * arrowScale, 104 y = 0f 105 ) 106 arrow.lineTo( 107 x = arrowWidth.toPx() * arrowScale / 2, 108 y = arrowHeight.toPx() * arrowScale 109 ) 110 val radius = min(bounds.width, bounds.height) / 2f 111 val inset = arrowWidth.toPx() * arrowScale / 2f 112 arrow.translate( 113 Offset( 114 x = radius + bounds.center.x - inset, 115 y = bounds.center.y + strokeWidth.toPx() / 2f 116 ) 117 ) 118 arrow.close() 119 rotate(degrees = startAngle + sweepAngle) { 120 drawPath( 121 path = arrow, 122 color = color, 123 alpha = alpha 124 ) 125 } 126 } 127 } 128