Neumorphism in Android

안드로이드에서 neumorphism을 구현하는 방법을 소개합니다. 🌈

Sungyong An
8 min readMay 16, 2020

Go to English version

작년 말부터 Neumorphism 디자인에 대한 article을 접하신 분들이 있을텐데요. 개인적으로 새로운 디자인에 관심이 동해서, Android에 적용해보고 싶었습니다. 하지만 플랫폼 제약사항 때문인지? 관련 라이브러리를 찾을 수 없어서 직접 간단하게 만들어 봤습니다. (Github Repository)

혼자만 사용하는 것보다는, 공유하고 다양한 의견을 받아보면 재미있을 것 같아서 글을 적어봅니다. 🤣

시작하기 앞서, Neumorphism이라는 용어가 생소하신 분들도 있을 것 같은데요. Material Design 같은 UI 컨셉으로, 그림자를 그리는 부분에서 차이가 있습니다. (자세한 내용은 아래의 영문 글을 참고하세요.)

Material Design에서는 물체 표면이 광원을 가리면 그림자가 생깁니다. Neumorphism에서는 물체가 배경에서 돌출된 것처럼 표시됩니다.

How to implement it?

어떤 방법으로 구현할 수 있는지 살펴봅시다.

Look and feel

구현하는 방법은 간단합니다. 2장의 그림자를 좌상단, 우하단에 그려줍니다. 각 그림자는 부드럽게 퍼져나가게 RenderScript로 Blur 처리를 했습니다.

하지만 렌더링에 문제가 있었습니다. View의 영역을 벗어난 부분에 그림자를 그리기 때문에, 실제로는 그림자가 잘려나갑니다. 🤔

이를 해결하는 방법으로, 처음에는 root에 android:clipChilderen="false" 를 사용하는 방법을 시도했었는데요. OS 버전에 따라 혹은 RelativeLayout 등의 ViewGroup에서는 속성이 제대로 동작하지 않아, 문제가 온전히 해결되지 않았습니다.

그래서 최종적으로는 View의 Padding 속성을 이용하여, View 영역 내에 그림을 그려주도록 변경하여 해결했습니다. 🎉

Interaction: Press Effect

모바일 UI는 심미적으로 아름답게 보여지는 것도 물론 중요하지만,
버튼을 누르는 것과 같은 인터랙션도 매우 중요합니다.

Neumorphism에서 인터랙션 효과를 어떻게 구현하면 좋을까요?

일단 먼저 떠오르는 것은 MotionEvent에 따라 Shape를 다르게 표시하는 방법입니다. 아쉽게도 이 방법은 인터랙션이 자연스럽게 느껴지지 않습니다.

override fun onTouchEvent(event: MotionEvent): Boolean {
when (event.action) {
MotionEvent.ACTION_DOWN ->
setShapeType(ShapeType.PRESSED)
MotionEvent.ACTION_UP ->
setShapeType(ShapeType.FLAT)
}
return super.onTouchEvent(event)
}

그렇다면 Material Button처럼 StateListAnimator를 이용하면 어떨까요?

TranslationZ에 따라 그림자가 이동하게끔 구현하면,
버튼이 눌려지는 효과를 좀 더 자연스럽게 보여줄 수 있습니다. 🥳

Getting started

지금까지 얘기한 부분들을 구현하여 라이브러리를 만들었습니다.

CardView, Button, ImageView와 같은 컴포넌트들을 제공하는데요.

라이브러리를 사용하는 방법은 다음과 같습니다:

<!-- ViewGroup에 적용할 때는 Child View를 감싸줍니다. 기본값이 적용될 수 있도록 style을 함께 선언해주세요. --><soup.neumorphism.NeumorphCardView
style="@style/Widget.Neumorph.CardView">
<ConstraintLayout>
...
</ConstraintLayout>
</soup.neumorphism.NeumorphCardView><!-- View 컴포넌트는 Android 기본 View와 동일하게 사용합니다. 기본값이 적용될 수 있도록 style을 함께 선언해주세요. --><soup.neumorphism.NeumorphButton
style="@style/Widget.Neumorph.Button"
android:text="Button" />
<soup.neumorphism.NeumorphImageView
style="@style/Widget.Neumorph.ImageView"
android:src="@drawable/icon" />

다음으로 Neumorphism Widget이 어떤 속성들을 제공하는지 알아봅니다.

Feature: Shadow

그림자의 색상과 높이를 조정할 수 있습니다.

<soup.neumorphism.NeumorphCardView
app:neumorph_shadowElevation="6dp"
app:neumorph_shadowColorLight="@color/light_color"
app:neumorph_shadowColorDark="@color/dark_color" />

Feature: Shape

View 표면, 모서리 모양을 변경할 수 있습니다.

<soup.neumorphism.NeumorphCardView
app:neumorph_shapeType="{flat|pressed|basin}"
app:neumorph_shapeAppearance="@style/ShapeAppearance" />
<style name="ShapeAppearance">
<item name="neumorph_cornerFamily">{rounded|oval}</item>
<item name="neumorph_cornerSize">32dp</item>
</style>

Feature: Color

View의 배경, 테두리 색상을 지정할 수 있습니다.

<soup.neumorphism.NeumorphCardView
app:neumorph_backgroundColor="@color/background_color"
app:neumorph_strokeColor="@color/stroke_color"
app:neumorph_strokeWidth="@dimen/stroke_width" />

Feature: Padding

마지막으로 View의 그림자가 잘리지 않도록 Padding을 지원합니다.
사전에 정의된 Style에는 기본값이 12dp로 설정되어 있습니다.

<soup.neumorphism.NeumorphCardView
android:padding="12dp" />

위에 언급된 모든 속성들은 동적으로도 변경할 수 있고, 동일한 기능을 View에 적용하기 쉽게 Drawable 형태로 구현되어 있습니다. 따라서 CustomView에도 손쉽게 적용할 수 있습니다.

지금까지 Neumorphism을 Android 플랫폼에서 구현하는 방법과 만들어진 라이브러리 사용법에 대해 살펴봤습니다.

새로운 느낌의 디자인은 개발자에게도 흥미로운 영감을 준다고 생각하는데요. Neumorphism은 어느 정도 새로운 느낌이 들어서 구현하는 동안 재미있었습니다. 다만 Material Design에 비해, Neumorphism은 원칙과 가이드가 명확하지 않은 부분이 많습니다.

즉, 얼추 구현을 하긴 했지만 프로덕션에 적용하기에는 아직 무리라고 생각합니다. 프로덕션에 적용할 만한 수준이 되려면, 다양한 의견이 필요한데요. Neumorphism에 관심이 가는 분들은 시험삼아 라이브러리를 사용해보시고,
궁금한 점이나 요청사항을 Github 저장소이슈로 생성해주세요. 🧑‍💻

--

--