회사에서 개발할 때 위 이미지처럼 빛나는 UI를 요청받았는데, 이런 효과를 네온, 글로 효과라고 한다.
다만 안드로이드에는 글로 효과가 없기 때문에 그라이데이션, 블러 등 을 사용해 만들어야 한다.
xml과 compose에서 글로 효과를 만드는 방법에 대해 설명하려 한다.
xml을 사용한 기존 UI에서는 Paint 속성에 있는 BlurMaskFilter를 사용해야 한다.
하지만 속성을 적용하고 그리면 흐린 효과만 나타나기 때문에 흐려지지 않는 부분을 추가로 그려줘야 한다.
아래 코드에서 bodyPaint는 흐려지지 않는 부분을, glowPaint는 흐려지는 부분이다.
class GlowView(context: Context, attrs: AttributeSet) : View(context, attrs) {
private val bodyPaint = Paint().apply {
color = Color.CYAN
style = Paint.Style.STROKE
strokeWidth = 10f
}
private val glowPaint = Paint(bodyPaint).apply {
maskFilter = BlurMaskFilter(20f, BlurMaskFilter.Blur.NORMAL)
}
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
val centerX = width / 2f
val centerY = height / 2f
canvas?.drawCircle(centerX, centerY, centerX, bodyPaint)
canvas?.drawCircle(centerX, centerY, centerX, glowPaint)
}
}
사용할 때는 다음 코드처럼 사용한다.
<com.panax.glowtest1.GlowView
android:layout_width="200dp"
android:layout_height="200dp" />
근데 코드를 실행해보면 글로 효과가 잘려있을 것이다.
안드로이드는 기본적으로 canvas에 그려지는 것이 뷰의 크기를 벗어나지 못하기 때문으로 보인다.
이를 해결하려면 다음 방법들이 있다.
먼저 GlowView의 부모 클래스에 다음 속성을 추가하면 된다.
android:clipChildren="false"
만약 코드를 사용해 해결하고 싶으면 다음 코드를 GlowView에 추가하면 된다.
init {
doOnAttach {
disableClipOnParents(this)
}
}
private fun disableClipOnParents(view: View) {
if (view.parent == null) return
if (view.parent is ViewGroup) {
(view.parent as ViewGroup).clipChildren = false
}
if (view.parent is View) {
disableClipOnParents(view.parent as View)
}
}
앱을 실행하면 다음 그림처럼 나온다.
compose에서는 다음 코드를 사용하면 된다.
xml과 같은 이유로 첫번째 border는 흐려지는 않는 부분이고 두번째 border는 흐려지는 부분이다.
굳이 Box를 2개 사용한 이유는 blur가 적용된 Box 안에 content를 넣으면 content도 같이 흐려지기 때문이다.
@Composable
private fun GlowCircle(
modifier: Modifier = Modifier,
content: @Composable BoxScope.() -> Unit = {}
) {
Box(
modifier = modifier,
contentAlignment = Alignment.Center
) {
Box(
modifier = Modifier
.fillMaxSize()
.border(2.dp, Color.Cyan, shape = CircleShape)
.blur(10.dp, edgeTreatment = BlurredEdgeTreatment.Unbounded)
.border(2.dp, Color.Cyan, shape = CircleShape)
)
content()
}
}
compose의 blur 효과는 뷰의 크기에 상관없이 잘리지 않기 때문에 별도의 처리가 필요없다.
다음 코드처럼 사용하면 된다.
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
Box(
modifier = Modifier.fillMaxSize().background(Color.Black),
contentAlignment = Alignment.Center
) {
GlowCircle(Modifier.size(200.dp))
}
}
}
}
결과는 다음과 같다.