Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |
Tags
- 안드로이드
- Behavior
- recyclerview
- hilt
- Algorithm
- Coroutine
- View
- onLayout
- CustomView
- 알고리즘
- DataBinding
- AppBarLayout
- HTTP
- activity
- 코틀린
- BOJ
- LiveData
- 알림
- kotlin
- CoordinatorLayout
- Android
- sqlite
- 백준
- ViewModel
- CollapsingToolbarLayout
- onMeasure
- notification
- Navigation
- lifecycle
- room
Archives
- Today
- Total
개발일지
Android in A..Z - View (CustomView) 본문
CustomView
Android에서 기본으로 제공하는 View로 UI를 구축할 수 없을 때 사용자가 직접 View를 상속받아서 CustomView를 만들 수 있다.
ProgressView 전체코드
class ProgressView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0, defStyleRes: Int = 0) : View(context, attrs, defStyleAttr, defStyleRes) {
private val progressBarBackgroundPaint = Paint().apply {
isAntiAlias = true
style = Paint.Style.STROKE
color = Color.parseColor("#646F88")
strokeWidth = 20 * resources.displayMetrics.density
}
private val progressBarPaint = Paint().apply {
isAntiAlias = true
style = Paint.Style.STROKE
color = Color.parseColor("#61AFEF")
strokeWidth = 20 * resources.displayMetrics.density
}
private val percentPaint = Paint().apply {
isAntiAlias = true
style = Paint.Style.FILL_AND_STROKE
textAlign = Paint.Align.CENTER
textSize = 20 * resources.displayMetrics.density
color = Color.parseColor("#000000")
}
var maxValue = 100
set(value) {
field = value
invalidate()
}
var value = 0
set(value) {
field = value
invalidate()
}
var textSize: Float
get() {
return percentPaint.textSize
}
set(value) {
percentPaint.textSize = value
invalidate()
}
var textColor: Int
get() {
return percentPaint.color
}
set(value) {
percentPaint.color = value
invalidate()
}
var progressBarBackgroundColor: Int
get() {
return progressBarBackgroundPaint.color
}
set(value) {
progressBarBackgroundPaint.color = value
invalidate()
}
var progressBarColor: Int
get() {
return progressBarPaint.color
}
set(value) {
progressBarPaint.color = value
invalidate()
}
var progressBarWidth: Float
get() {
return progressBarPaint.strokeWidth
}
set(value) {
progressBarPaint.strokeWidth = value
progressBarBackgroundPaint.strokeWidth = value
}
init {
context.theme.obtainStyledAttributes(attrs, R.styleable.ProgressView, defStyleAttr, defStyleRes).apply {
maxValue = getInt(R.styleable.ProgressView_maxValue, maxValue)
value = getInt(R.styleable.ProgressView_value, value)
textSize = getDimension(R.styleable.ProgressView_textSize, textSize)
textColor = getColor(R.styleable.ProgressView_textColor, textColor)
progressBarBackgroundColor = getColor(R.styleable.ProgressView_progressBarBackgroundColor, progressBarBackgroundColor)
progressBarColor = getColor(R.styleable.ProgressView_progressBarColor, progressBarColor)
progressBarWidth = getDimension(R.styleable.ProgressView_progressBarWidth, progressBarWidth)
}
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
var w = MeasureSpec.getSize(widthMeasureSpec)
var h = MeasureSpec.getSize(heightMeasureSpec)
val widthMode = MeasureSpec.getMode(widthMeasureSpec)
val heightMode = MeasureSpec.getMode(heightMeasureSpec)
when {
(widthMode == MeasureSpec.AT_MOST || widthMode == MeasureSpec.UNSPECIFIED) && (heightMode == MeasureSpec.AT_MOST || heightMode == MeasureSpec.UNSPECIFIED) -> {
w = (100 * resources.displayMetrics.density + 0.5).toInt()
h = (100 * resources.displayMetrics.density + 0.5).toInt()
}
widthMode == MeasureSpec.EXACTLY && (heightMode == MeasureSpec.AT_MOST || heightMode == MeasureSpec.UNSPECIFIED) -> {
h = w
}
(widthMode == MeasureSpec.AT_MOST || widthMode == MeasureSpec.UNSPECIFIED) && heightMode == MeasureSpec.EXACTLY -> {
w = h
}
}
setMeasuredDimension(w, h)
}
override fun onDraw(canvas: Canvas) {
val left = (paddingLeft + progressBarWidth/2)
val top = (paddingTop + progressBarWidth/2)
val right = (width - paddingLeft - paddingRight - progressBarWidth/2)
val bottom = (height - paddingTop - paddingBottom - progressBarWidth/2)
val x = (left + right)/2
val y = (top + bottom)/2 + textSize/2
drawProgressBarBackground(canvas, left, top, right, bottom)
drawProgress(canvas, left, top, right, bottom, 360F * value / maxValue)
drawPercent(canvas, x, y)
}
private fun drawProgressBarBackground(canvas: Canvas, left: Float, top: Float, right: Float, bottom: Float) {
canvas.drawArc(left, top, right, bottom, -90F, 360F, false, progressBarBackgroundPaint)
}
private fun drawProgress(canvas: Canvas, left: Float, top: Float, right: Float, bottom: Float, angle: Float) {
canvas.drawArc(left, top, right, bottom, -90F, angle, false, progressBarPaint)
}
private fun drawPercent(canvas: Canvas, x: Float, y: Float) {
canvas.drawText("${value * 100 / maxValue}%", x, y, percentPaint)
}
}
onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int)
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
var w = MeasureSpec.getSize(widthMeasureSpec)
var h = MeasureSpec.getSize(heightMeasureSpec)
val widthMode = MeasureSpec.getMode(widthMeasureSpec)
val heightMode = MeasureSpec.getMode(heightMeasureSpec)
when {
(widthMode == MeasureSpec.AT_MOST || widthMode == MeasureSpec.UNSPECIFIED) && (heightMode == MeasureSpec.AT_MOST || heightMode == MeasureSpec.UNSPECIFIED) -> {
w = (100 * resources.displayMetrics.density + 0.5).toInt()
h = (100 * resources.displayMetrics.density + 0.5).toInt()
}
widthMode == MeasureSpec.EXACTLY && (heightMode == MeasureSpec.AT_MOST || heightMode == MeasureSpec.UNSPECIFIED) -> {
h = w
}
(widthMode == MeasureSpec.AT_MOST || widthMode == MeasureSpec.UNSPECIFIED) && heightMode == MeasureSpec.EXACTLY -> {
w = h
}
}
setMeasuredDimension(w, h)
}
View.onMeasure(int widthMeasureSpec, int heightMeasureSpec)
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
onMeasure는 View의 크기를 측정하는 함수이다. onMeasure 단계에서 setMeasuredDimension을 호출하지 않으면 Exception이 발생하기 때문에 setMeasuredDimension을 호출해야 한다. CustomView에 맞춰서 크기를 측정할 경우 onMeasure을 재정의하여 super.onMeasure을 호출하지 않아도 된다.
* setMeasuredDimension은 Pixel단위기 때문에 DP로 변환하는 과정이 필요하다.
onDraw(canvas: Canvas)
override fun onDraw(canvas: Canvas) {
val left = (paddingLeft + progressBarWidth/2)
val top = (paddingTop + progressBarWidth/2)
val right = (width - paddingLeft - paddingRight - progressBarWidth/2)
val bottom = (height - paddingTop - paddingBottom - progressBarWidth/2)
val x = (left + right)/2
val y = (top + bottom)/2 + textSize/2
drawProgressBarBackground(canvas, left, top, right, bottom)
drawProgress(canvas, left, top, right, bottom, 360F * value / maxValue)
drawPercent(canvas, x, y)
}
private fun drawProgressBarBackground(canvas: Canvas, left: Float, top: Float, right: Float, bottom: Float) {
canvas.drawArc(left, top, right, bottom, -90F, 360F, false, progressBarBackgroundPaint)
}
private fun drawProgress(canvas: Canvas, left: Float, top: Float, right: Float, bottom: Float, angle: Float) {
canvas.drawArc(left, top, right, bottom, -90F, angle, false, progressBarPaint)
}
private fun drawPercent(canvas: Canvas, x: Float, y: Float) {
canvas.drawText("${value * 100 / maxValue}%", x, y, percentPaint)
}
onDraw는 View를 그리는 함수이다. onDraw에서 Canvas를 통해 그래픽을 그릴 수 있다. 주의할 점은 onDraw는 자주 호출되기 때문에 onDraw에서 객체를 생성하는 행위는 하지 않는 것이 좋다. ( onDraw에서 객체를 생성하고 소멸하는 행위가 자주 호출되고 이는 GC를 자주 호출하기 때문에 성능에 좋지 않다. )
Git (예제코드)
github.com/KangTaeJong98/Example/tree/main/Android/CustomView
KangTaeJong98/Example
My Example Code. Contribute to KangTaeJong98/Example development by creating an account on GitHub.
github.com
'Android (안드로이드) > View' 카테고리의 다른 글
Android in A..Z - View (CustomView-Extend) (0) | 2021.04.30 |
---|---|
Android in A..Z - View (CustomViewGroup) (0) | 2021.04.30 |
Android in A..Z - View (MeasureSpec) (0) | 2021.04.29 |
Android in A..Z - View (AttributeSet) (0) | 2021.04.29 |
Android in A..Z - View (개념) (0) | 2021.04.29 |
Comments