Skip to content

Instantly share code, notes, and snippets.

@mcassiano
Created July 5, 2018 17:13
Show Gist options
  • Save mcassiano/b322aad7eb0e5eb05f154093597cdd96 to your computer and use it in GitHub Desktop.
Save mcassiano/b322aad7eb0e5eb05f154093597cdd96 to your computer and use it in GitHub Desktop.
<resources>
<declare-styleable name="ProfitabilityIndexView">
<attr name="pi_circle_radius" format="dimension" />
<attr name="pi_circle_color" format="color" />
<attr name="pi_percentage" format="integer" />
<attr name="pi_circle_spacing" format="dimension" />
<attr name="pi_number_of_circles" format="integer" />
</declare-styleable>
</resources>
private const val DEFAULT_NUMBER_OF_CIRCLES = 10
class ProfitabilityIndexView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
private val rect = RectF()
private var circleRadius: Float
@ColorInt
private val circleColor: Int
private var circleSpacing: Float
private val numberOfCircles: Int
private val circlePaint: Paint
private var percentage: Int = 0
init {
val styledAttributes = context.obtainStyledAttributes(
attrs,
R.styleable.ProfitabilityIndexView,
0,
R.style.ProfitabilityIndexViewStyle
)
circleRadius = styledAttributes.getDimension(
R.styleable.ProfitabilityIndexView_pi_circle_radius,
0f
)
circleColor = styledAttributes.getColor(
R.styleable.ProfitabilityIndexView_pi_circle_color,
0
)
circleSpacing = styledAttributes.getDimension(
R.styleable.ProfitabilityIndexView_pi_circle_spacing,
0f
)
numberOfCircles = styledAttributes.getInt(
R.styleable.ProfitabilityIndexView_pi_number_of_circles,
DEFAULT_NUMBER_OF_CIRCLES)
val percentage = styledAttributes.getInt(
R.styleable.ProfitabilityIndexView_pi_percentage,
0
)
checkPercentageBounds(percentage)
this.percentage = percentage
circlePaint = Paint(ANTI_ALIAS_FLAG).apply {
color = circleColor
}
styledAttributes.recycle()
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
val width = paddingStart + getDrawnRegionSize() + paddingEnd
val height = paddingBottom + paddingTop + 2 * circleRadius
setMeasuredDimension(Math.round(width), Math.round(height))
}
override fun onDraw(canvas: Canvas) {
clipPercentageBounds(canvas)
drawCircles(canvas)
}
fun setPercentage(value: Int) {
checkPercentageBounds(value)
percentage = value
invalidate()
}
fun setSpacing(value: Int) {
circleSpacing = convertToPx(value).toFloat()
requestLayout()
}
fun setRadius(value: Int) {
circleRadius = convertToPx(value).toFloat()
requestLayout()
}
fun getPercentage() = percentage
@Suppress("DEPRECATION")
private fun clipPercentageBounds(canvas: Canvas) {
with(rect) {
left = 0f
top = paddingTop.toFloat()
right = paddingLeft + getDrawnRegionSize() * percentage / 100
bottom = paddingTop + circleRadius * 2
}
canvas.clipRect(rect)
}
private fun drawCircles(canvas: Canvas) {
var dx = paddingStart.toFloat()
for (i in 1..numberOfCircles) {
canvas.drawOval(dx,
paddingTop.toFloat(),
dx + circleRadius * 2,
paddingTop + circleRadius * 2,
circlePaint
)
dx += circleRadius * 2 + circleSpacing
}
}
private fun checkPercentageBounds(value: Int) {
if (value < 0 || value > 100)
throw IllegalArgumentException("Profitability view only accepts values in range 0..100")
}
private fun convertToPx(dp: Int): Int {
val scale = resources.displayMetrics.density
return (dp * scale + 0.5f).toInt()
}
private fun getDrawnRegionSize() =
numberOfCircles * 2 * circleRadius + (numberOfCircles - 1) * circleSpacing
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment