0Pricing
Android Academy · 강의

사용자 지정 뷰

UI 구성 요소를 처음부터 직접 그립니다. View를 확장하고 Canvas와 Paint를 사용해 onMeasure와 onDraw를 구현하며 사용자 지정 XML 속성을 정의하고 복합 뷰를 만듭니다.

사용자 지정 뷰은(는) CoddyKit의 무료 Android Academy 강의입니다. 이것은 5개 중 3번째 강의입니다. 아래에서 전체 강의를 무료로 읽을 수 있으며, 내장 코드 에디터와 24/7 AI 튜터와 함께 브라우저에서 직접 실습할 수 있습니다. 이 강의는 Android Academy 학습 경로의 일부이며, 진행 상황이 웹과 CoddyKit 앱에 동기화됩니다. Android Academy 강의에는 총 5개의 강의가 포함되어 있습니다.

사용자 지정 뷰를 사용하는 이유

내장 Android 뷰가 필요한 기능을 정확히 제공하지 못하는 경우가 있습니다. 사용자 지정 뷰를 사용하면 다음을 할 수 있습니다.

  • SDK에 없는 도형, 그래프, 진행률 링 또는 차트 그리기
  • 앱 전체에서 일관되게 사용하는 브랜드 UI 구성 요소 만들기
  • 여러 뷰를 하나의 재사용 가능한 구성 요소로 결합하기(복합 뷰)
  • 중첩된 레이아웃 계층을 제거해 성능 향상

View 확장

View(또는 ImageView와 같은 하위 클래스)를 확장해 사용자 지정 뷰를 만듭니다. 필요한 생성자를 재정의합니다.

class CircleProgressView @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {

    var progress: Float = 0f
        set(value) {
            field = value.coerceIn(0f, 100f)
            invalidate()   // trigger redraw
        }

    // onMeasure and onDraw go here
}

onMeasure()

onMeasure()는 이 뷰가 원하는 크기를 부모에게 알립니다. 마지막에는 항상 setMeasuredDimension()을 호출합니다.

override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
    val desiredSize = 200  // dp → pixels
    val px = (desiredSize * resources.displayMetrics.density).toInt()

    val width  = resolveSize(px, widthMeasureSpec)
    val height = resolveSize(px, heightMeasureSpec)

    // Square: same width and height
    val size = minOf(width, height)
    setMeasuredDimension(size, size)
}

Paint — 그리기 도구

Paint 객체는 그리기에 필요한 스타일 정보(색상, 선 너비, 안티앨리어싱)를 담습니다. 한 번만 만들고 onDraw() 안에서는 만들지 마세요.

class CircleProgressView ... : View(...) {

    // Create Paint in init, not in onDraw!
    private val backgroundPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
        color = Color.LTGRAY
        style = Paint.Style.STROKE
        strokeWidth = 20f
        strokeCap = Paint.Cap.ROUND
    }

    private val progressPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
        color = Color.parseColor("#6750A4")
        style = Paint.Style.STROKE
        strokeWidth = 20f
        strokeCap = Paint.Cap.ROUND
    }
}

onDraw() — 뷰 그리기

뷰를 다시 그려야 할 때마다 onDraw(canvas)가 호출됩니다. Canvas를 사용해 도형, 텍스트 및 비트맵을 그립니다.

override fun onDraw(canvas: Canvas) {
    super.onDraw(canvas)

    val cx = width / 2f
    val cy = height / 2f
    val radius = (minOf(width, height) / 2f) - 30f

    // Draw background circle
    canvas.drawCircle(cx, cy, radius, backgroundPaint)

    // Draw progress arc
    val oval = RectF(cx - radius, cy - radius, cx + radius, cy + radius)
    val sweepAngle = 360f * (progress / 100f)
    canvas.drawArc(oval, -90f, sweepAngle, false, progressPaint)
}

invalidate()와 requestLayout() 비교

업데이트를 실행하는 두 가지 메서드:

  • invalidate() — 뷰를 다시 그립니다(onDraw 호출). 콘텐츠는 변경되지만 크기는 그대로일 때 사용합니다.
  • requestLayout() — 다시 측정한 후 다시 그립니다(onMeasure 다음 onDraw 호출). 뷰의 크기가 변경될 때 사용합니다.

사용자 지정 속성(attrs.xml)

레이아웃 파일에서 뷰를 구성할 수 있도록 사용자 지정 XML 속성을 선언합니다.

<!-- res/values/attrs.xml -->
<resources>
    <declare-styleable name="CircleProgressView">
        <attr name="progressColor" format="color" />
        <attr name="trackColor"    format="color" />
        <attr name="strokeWidth"   format="dimension" />
        <attr name="progress"      format="float" />
    </declare-styleable>
</resources>

사용자 지정 속성 읽기

obtainStyledAttributes를 사용해 생성자에서 attrs를 읽습니다.

init {
    context.obtainStyledAttributes(attrs, R.styleable.CircleProgressView).use { ta ->
        progressPaint.color = ta.getColor(
            R.styleable.CircleProgressView_progressColor,
            Color.parseColor("#6750A4")
        )
        backgroundPaint.color = ta.getColor(
            R.styleable.CircleProgressView_trackColor,
            Color.LTGRAY
        )
        val sw = ta.getDimension(R.styleable.CircleProgressView_strokeWidth, 20f)
        progressPaint.strokeWidth = sw
        backgroundPaint.strokeWidth = sw
        progress = ta.getFloat(R.styleable.CircleProgressView_progress, 0f)
    }
}

XML에서 사용자 지정 뷰 사용

전체 클래스 경로와 사용자 지정 속성을 사용해 레이아웃 XML에서 사용자 지정 뷰를 사용합니다.

<!-- In any layout XML -->
<com.example.myapp.CircleProgressView
    android:id="@+id/progressView"
    android:layout_width="120dp"
    android:layout_height="120dp"
    app:progress="65"
    app:progressColor="@color/primary"
    app:trackColor="@color/surface_variant"
    app:strokeWidth="12dp" />

<!-- Control in code: -->
binding.progressView.progress = 75f

복합 뷰

복합 뷰는 기존 뷰를 하나의 재사용 가능한 구성 요소로 결합합니다. LinearLayout과 같은 레이아웃 클래스를 확장합니다.

class SearchBarView @JvmOverloads constructor(
    context: Context, attrs: AttributeSet? = null
) : LinearLayout(context, attrs) {

    private val binding = ViewSearchBarBinding.inflate(LayoutInflater.from(context), this)

    init {
        orientation = HORIZONTAL
        binding.ivSearch.setOnClickListener {
            onSearchClick?.invoke(binding.etSearch.text.toString())
        }
    }

    var onSearchClick: ((String) -> Unit)? = null

    fun setHint(hint: String) { binding.etSearch.hint = hint }
}

텍스트 그리기

drawText()를 사용해 Canvas에 텍스트를 그립니다. Paint.getFontMetrics()를 사용하면 텍스트를 정확히 가운데에 배치할 수 있습니다.

private val textPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
    color = Color.BLACK
    textSize = 48f
    textAlign = Paint.Align.CENTER
}

override fun onDraw(canvas: Canvas) {
    super.onDraw(canvas)
    val cx = width / 2f
    val cy = height / 2f

    // Vertically center the text:
    val fm = textPaint.fontMetrics
    val textY = cy - (fm.ascent + fm.descent) / 2f
    canvas.drawText("${progress.toInt()}%", cx, textY, textPaint)
}

빠른 확인

데이터는 변경되었지만 크기가 그대로일 때 사용자 지정 뷰를 다시 그리려면 어떤 메서드를 호출해야 할까요?

복습: 사용자 지정 뷰

이제 화면에 무엇이든 그릴 수 있습니다.

  • @JvmOverloads constructor를 사용해 View 확장
  • onMeasure() — setMeasuredDimension()으로 원하는 크기 전달
  • Paint — 스타일 정의; 한 번만 만들고 onDraw() 안에서는 만들지 않기
  • onDraw(canvas) — 원, 호, 텍스트, 비트맵 그리기
  • 다시 그리려면 invalidate(), 다시 측정하려면 requestLayout() 사용
  • XML 구성을 위한 사용자 지정 속성 — attrs.xml
  • 복합 뷰 — 레이아웃을 확장해 기존 뷰 결합

다음: 애니메이션과 전환으로 UI에 생동감 더하기

자주 묻는 질문

“사용자 지정 뷰” 강의는 무료인가요?

네 — “사용자 지정 뷰” 전체 내용을 이 웹사이트에서 무료로 읽을 수 있습니다. 인터랙티브하게 실습하려면(내장 코드 에디터와 24/7 AI 튜터), CoddyKit PRO로 업그레이드하면 Android Academy 강의 전체를 잠금 해제할 수 있습니다. Android Academy 강의에는 총 5개의 강의가 포함되어 있습니다.

“사용자 지정 뷰”에서 뭘 배우나요?

UI 구성 요소를 처음부터 직접 그립니다. View를 확장하고 Canvas와 Paint를 사용해 onMeasure와 onDraw를 구현하며 사용자 지정 XML 속성을 정의하고 복합 뷰를 만듭니다. 브라우저에서 직접 실행하는 실습 코드로 Android Academy을(를) 배우며, 24/7 AI 튜터가 강의를 진행하면서 질문에 답변해줍니다.

Android Academy을(를) 시작하는 데 경험이 필요한가요?

사전 경험은 필요하지 않습니다. CoddyKit의 Android Academy은(는) 초급자부터 고급 학습자까지를 위해 구성되어 있으므로, 여기서 시작하거나 처음부터 시작할 수 있으며 자신의 속도대로 진행할 수 있습니다. 이것은 5개 중 3번째 강의입니다.

“사용자 지정 뷰” 강의는 얼마나 걸리나요?

대부분의 CoddyKit 강의는 약 5~10분이 소요됩니다. 각 강의는 간결하고 인터랙티브하여 꾸준한 진행이 가능하며, 웹과 앱에서 중단한 부분부터 바로 시작할 수 있습니다.

이 Android Academy 강의에서 코드를 작성하고 실행할 수 있나요?

네. 모든 Android Academy 강의에는 내장 코드 에디터가 포함되어 있으므로, 브라우저에서 바로 실제 코드를 작성하고 실행한 후 즉시 AI 피드백을 받을 수 있습니다 — 로컬 설정이 필요 없습니다.

이 강의의 모든 강의

  1. Material Design 기초
  2. 테마와 스타일
  3. 사용자 지정 뷰
  4. 애니메이션과 전환
  5. 하단 탐색과 탭
← Android Academy(으)로 돌아가기