오늘도 삽질중

Android MulitGradient Circle ProgressBar programmatically 본문

안드로이드

Android MulitGradient Circle ProgressBar programmatically

Choi3950 2022. 6. 26. 21:21
반응형

멀티 그라데이션 원형 프로그래스바를 프로그래밍으로 구현해보았습니다.

 

3개 이상의 색으로 그라데이션을 표현해 줘야 되는데, 구글링을 해보니 XML 방식으로 세팅하는건 최대 2가지 색 조합만 가능한 것으로 확인했습니다.

 

 

페인트과 애니메이션을 사용해서 구현을 했고, 그라데이션은 LinearGradient 를 사용했습니다.

프로그래밍으로 구현을 했으니 컬러 변경이 필요한 경우 컬러 코드값만 변경해주므로 변경에도 용이하다 판단됩니다.

 

또한 애니메이션을 적용해서 stop and restart 개념도 작업을 했습니다.

import android.animation.Animator
import android.animation.ObjectAnimator
import android.content.Context
import android.graphics.*
import android.util.AttributeSet
import android.util.TypedValue
import android.view.View
import android.view.animation.LinearInterpolator
import androidx.annotation.Keep

/**
 * Created by genesislab_Jeong-Hyun. 2022/04/06
 */
class MultiGradientCircleProgressBar constructor(
    context: Context,
    attr: AttributeSet
) : View(context,attr) {

    private val stroke = context.dpToPx(6).toFloat()

    private val backgroundPaint: Paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
        color = Color.parseColor("#80000000")
    }
    private val gradientBoardPaint: Paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
        color = Color.parseColor("#7b7b7b")

        style = Paint.Style.STROKE
        strokeWidth = stroke
        isAntiAlias = true
    }

    private val progressBoardPaint: Paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
        color = Color.parseColor("#7b7b7b")

        style = Paint.Style.STROKE
        strokeWidth = stroke
        isAntiAlias = true
    }

    private var boardRectF = RectF()
    private var animator: ObjectAnimator? = null

    @Keep
    private var sweep: Float = 0f
        set(value) {
            field = value
            invalidate()
        }


    override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
        super.onSizeChanged(w, h, oldw, oldh)
        boardRectF.set(
            stroke/2f,
            stroke/2f,
            width.toFloat() - stroke/2f,
            height.toFloat() - stroke/2f
        )

        LinearGradient(
            0f, 0f, width.toFloat(), 0f,
            intArrayOf(
                Color.parseColor("#7859de"),
                Color.parseColor("#e62983"),
                Color.parseColor("#ee7625")
            ),
            floatArrayOf(0f,0.23f, 0.81f),
            Shader.TileMode.CLAMP
        ).also {
            gradientBoardPaint.shader = it

//             멀티 그라데이션 프로그래스바가 보여지고 싶다면 아래 코드로 변경
//            progressBoardPaint.shader = it
        }
    }

    override fun onDraw(canvas: Canvas?) {
        super.onDraw(canvas)
        canvas?.drawCircle(width/2f , height/2f , width/2f , backgroundPaint)

        canvas?.drawArc(boardRectF, 0f,360f,false , gradientBoardPaint)
        canvas?.drawArc(boardRectF , 270f,sweep , false , progressBoardPaint)
    }


    fun setProgress(p: Int) {
        sweep = p.toFloat() / 100f * 360f
    }
    
    fun reset(){
        sweep = 0f
    }

    @Keep
    fun startAnimation(duration: Float){
        ObjectAnimator.ofFloat(this , "sweep", 0f, 360f+OFF_SET).apply {
            this.duration = (duration * 1000).toLong()
            interpolator = LinearInterpolator()
            addListener(object : Animator.AnimatorListener{
                override fun onAnimationStart(animation: Animator?) {}
                override fun onAnimationEnd(animation: Animator?) { animator = null }
                override fun onAnimationCancel(animation: Animator?) { animator = null }
                override fun onAnimationRepeat(animation: Animator?) {}
            })
        }.also {
            animator = it
        }.start()
    }

    fun isRunning() = animator?.isRunning?: false
    fun isPaused() = animator?.isPaused?: false

    fun pauseAnimation(){
        if (animator?.isRunning == true) animator?.pause()
    }

    fun resumeAnimation(){
        if (animator?.isPaused == true) animator?.resume()
    }


    private fun Context.dpToPx(dp: Int): Int {
        return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP , dp.toFloat() , this.resources.displayMetrics).toInt()
    }

    companion object {
        const val OFF_SET = 8
    }

}

 

해당코드를 적용후 XML 에서 기본 Ui 쓰는것처럼 사용하시면 됩니다.

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <ai.genesislab.mulitgradientcircleprogressbar.MultiGradientCircleProgressBar
        android:id="@+id/progressbar"
        android:layout_width="84dp"
        android:layout_height="84dp"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"/>

</androidx.constraintlayout.widget.ConstraintLayout>

 

액티비티에서 해당 뷰에 접근하여 아래처럼 실행합니다. (샘플 프로젝트라 findViewById 로 접근했으나, 원래는 데이터바인딩 또는 뷰 바인딩을 사용합니다.)

class MainActivity : AppCompatActivity() {

    private val progressBar: MultiGradientCircleProgressBar by lazy {
        findViewById(R.id.progressbar)
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        //1f -> 1초
        progressBar.startAnimation(10f)
        
        //stop animation
//        progressBar.pauseAnimation()
        
        //resume animation
//        progressBar.resumeAnimation()
    }
}

 

결과 영상입니다.

 

 

위 영상과 반대로 그라데이션 색깔이 서서히 나타나게 하고 싶다면 onSizeChanged 를 아래처럼 변경 후 실행하면 됩니다.

    override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
        super.onSizeChanged(w, h, oldw, oldh)
        boardRectF.set(
            stroke/2f,
            stroke/2f,
            width.toFloat() - stroke/2f,
            height.toFloat() - stroke/2f
        )

        LinearGradient(
            0f, 0f, width.toFloat(), 0f,
            intArrayOf(
                Color.parseColor("#7859de"),
                Color.parseColor("#e62983"),
                Color.parseColor("#ee7625")
            ),
            floatArrayOf(0f,0.23f, 0.81f),
            Shader.TileMode.CLAMP
        ).also {
//            gradientBoardPaint.shader = it

//             멀티 그라데이션 프로그래스바가 보여지고 싶다면 아래 코드로 변경
            progressBoardPaint.shader = it
        }
    }

 

결과 영상입니다.

 

 

반응형
Comments