안드로이드 그림자(Shadow) 효과 넣기
안드로이드에서 그림자 효과를 넣는 방법으로는 UI의 elevation 속성 값을 조정하는 것과 직접 그림자용 리소스 파일을 만드는 방법이 있다. 이번 포스트에서는 이 두가지의 사용 방법과 각각의 장단점을 소개해보려고 한다.
1. elevation 값 조정하기
UI에 가장 쉽게 섀도우 효과를 입힐 수 있는 방법이다. 안드로이드 API21 부터 UI 뷰들에 elevation 이라는 속성값이 추가 됐는데 이 값을 넣으면 UI가 Z축으로 위로 튀어나와 그림자 효과를 줄 수 있게 된다.
코드는 다음과 같다.
<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">
<FrameLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent">
<ImageView
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_margin="50dp"
android:elevation="20dp"
android:background="@color/colorPrimary" />
</FrameLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
elevation 값을 조정해서 효과를 줄 때는 주의해야할 점이 두가지 있는데 첫번째는 elevation 값에 넣어준 수치 만큼 View 주변에 여백을 충분하게 주어야 한다는 것이다. elevation으로 만든 그림자는 View의 width/height 영역 밖에서 발생하기 때문에 이 부분의 여백을 주지 않으면 그림자 효과가 발생하지 않는다.
두번째로는 background값이 투명하면 안된다. 불투명한 값으로 셋팅을 해줘야한다. 왜 불투명한 background 값을 셋팅해줘야하는지는 아직 잘 모르겠다; 하지만 투명한 값으로 세팅하면 그림자 효과가 나타나지 않는다.
이 방법은 편하긴 하지만 API21 버전부터 사용할 수 있고 하단부에만 그림자 효과를 줄 수 있다는 단점이 있다. 상하좌우 모두 그림자 효과를 주어야 할 때는 사용 할 수 가 없다. 이런 경우에는 직접 리소스 파일로 그림자 효과를 만들어야 한다.
2. 그림자용 리소스 파일 만들기
선이나 사각형을 코드로 만들 때 사용했던 XML 파일을 이용해서 그림자 효과를 줄 수 있다. 설명에 앞서서 아래 예시 코드와 이 코드를 입힌 UI 결과물을 먼저 보자. 코드가 길지만 반복 구문이 많으니 대강 훓어보는 것을 추천한다
shadow_test.xml
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
<!-- Drop Shadow Stack -->
<item>
<shape>
<padding
android:bottom="2.5dp"
android:left="2.5dp"
android:right="2.5dp"
android:top="2.5dp" />
<solid android:color="#00CCCCCC" />
</shape>
</item>
<item>
<shape>
<padding
android:bottom="2.5dp"
android:left="2.5dp"
android:right="2.5dp"
android:top="2.5dp" />
<solid android:color="#06CCCCCC" />
</shape>
</item>
<item>
<shape>
<padding
android:bottom="2.5dp"
android:left="2.5dp"
android:right="2.5dp"
android:top="2.5dp" />
<solid android:color="#09CCCCCC" />
</shape>
</item>
<item>
<shape>
<padding
android:bottom="2.5dp"
android:left="2.5dp"
android:right="2.5dp"
android:top="2.5dp" />
<solid android:color="#0BCCCCCC" />
</shape>
</item>
<item>
<shape>
<padding
android:bottom="2.5dp"
android:left="2.5dp"
android:right="2.5dp"
android:top="2.5dp" />
<solid android:color="#0DCCCCCC" />
</shape>
</item>
<item>
<shape>
<padding
android:bottom="2.5dp"
android:left="2.5dp"
android:right="2.5dp"
android:top="2.5dp" />
<solid android:color="#10CCCCCC" />
</shape>
</item>
<item>
<shape>
<padding
android:bottom="2.5dp"
android:left="2.5dp"
android:right="2.5dp"
android:top="2.5dp" />
<solid android:color="#12CCCCCC" />
</shape>
</item>
<item>
<shape>
<padding
android:bottom="2.5dp"
android:left="2.5dp"
android:right="2.5dp"
android:top="2.5dp" />
<solid android:color="#15CCCCCC" />
</shape>
</item>
<item>
<shape>
<padding
android:bottom="2.5dp"
android:left="2.5dp"
android:right="2.5dp"
android:top="2.5dp" />
<solid android:color="#17CCCCCC" />
</shape>
</item>
<item>
<shape>
<padding
android:bottom="2.5dp"
android:left="2.5dp"
android:right="2.5dp"
android:top="2.5dp" />
<solid android:color="#1ACCCCCC" />
</shape>
</item>
<!-- Background -->
<item>
<shape>
<solid android:color="@android:color/white" />
</shape>
</item>
</layer-list>
ImageView에 위에서 만든 리소스를 background로 넣었다.
<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">
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:ignore="RtlSymmetry">
<ImageView
android:layout_width="150dp"
android:layout_height="150dp"
android:layout_gravity="center"
android:background="@drawable/shadow_test"/>
</androidx.appcompat.widget.LinearLayoutCompat>
</androidx.constraintlayout.widget.ConstraintLayout>
이 코드를 이용해 그림자 효과를 입혀본 결과는 다음과 같다.
사진을 자세히 보면 흰 사각형 바깥쪽에 촘촘히 작은 선들이 있는 것을 볼 수가 있다. 이건 위 예제 코드에서 2.5dp 기준으로 각각 색깔이 다른 사각형을 넣었기 때문이다. 여러개의 작은 장면들을 조합해서 연속된 애니메이션 효과로 보이게 한 것 처럼 이 그라데이션 효과도 작은 사각형들을 합해서 그림자처럼 보이게 만든 효과다.
이 방법은 약간의 노가다가 필요하긴 하지만 개발자가 그림자 효과를 자유자재로 커스텀이 가능하다는 장점이 있다. 어떤 부분에 좀더 강조를 세게 주고 싶다거나 좌측 상단, 우측 하단, 상화좌우 전체에 그림자 효과를 선택해서 줄 수 있다.
elevation을 이용한 방법과 차이가 있다면 이 방법은 그림자 영역이 뷰의 영역에 포함되어 있다는 것이다. 아래 그림을 보면 왼쪽 그림의 보라색 사각형이 elevation을 이용해서 그림자 효과를 준 경우고 하얀색 사각형이 리소스를 이용해서 그림자 효과를 준 경우인데, 미리보기 상으로는 하얀색 사각형이 더 작아보이지만 두 ImageView의 가로 세로 너비 값은 오른쪽 그림에서도 알 수 있듯이 동일하다. 리소스를 사용하면 그림자 영역을 View 내부에서 사용하기 때문에 원래 생각했던 ImageView의 크기와 약간 차이가 발생 할 수 있다. 상황에 따라서 단점이 될 수도 있고 장점이 될 수 도 있는 기능이라 섣불리 판단 할 수는 없을 것 같다. 단 차이점은 유의해서 알고가는 것이 좋을 것 같다.