안드로이드(Android)

ImageView나 특정 View에 그림자 커스텀하여 지정하는 방법 + 그림자를 지정하니 이미지의 사이즈가 이상해지는 경우

Gnow 2023. 8. 22. 14:31
728x90
반응형

프로젝트를 진행 중 이미지에 그림자를 넣어야하는 경우가 생겼다.
elevation을 통해서 그림자를 손쉽게 넣어줄 수 있지만 특정 방향의 그림자를 지정하기는 어렵다.

그럴때는 직접 xml 파일로 커스텀하여 그림자를 넣어줘야한다.
원하는 이름.xml로 drawable 폴더에 넣으면 된다.

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    <!-- 그림자 설정 아래로 내려갈 수록 물체와 가까운 그림자로 표현 -->
    <item>
        <shape>
            <padding android:top="0dp" android:right="0dp" android:bottom="1dp" android:left="0dp" />
            <corners android:radius="20dp" />
            <solid android:color="#FFFFFF" />
        </shape>
    </item>
    <item>
        <shape>
            <padding android:top="0dp" android:right="0dp" android:bottom="1dp" android:left="0dp" />
            <corners android:radius="20dp" />
            <solid android:color="#FEFEFE" />
        </shape>
    </item>

    <item><shape
        android:padding="10dp"
        android:shape="rectangle">
        <solid android:color="#FCFDFDFD" />

        <stroke
            android:width="1dp"
            android:color="#FFFFFF"/>
        <corners
            android:bottomLeftRadius="20dp"
            android:bottomRightRadius="20dp"
            android:topLeftRadius="20dp"
            android:topRightRadius="20dp" />
    </shape></item>
</layer-list>

위의 코드를 보면 얼핏보면 매우 복잡해 보인다. 그림자를 표현하기 위해 간략하게 일부만 가져오면 다음과 같다.

    <item> 
        <shape> 
            <padding android:top="0dp" android:right="0dp" android:bottom="1dp" android:left="0dp" />
            <corners android:radius="20dp" /> <solid android:color="#FFFFFF" />
        </shape>
    </item>

이 코드를 설명하자면 <padding 라인의 코드를 통해 그림자의 표현 방향을 정할 수 있다.

 

위, 오른쪽, 아래, 왼쪽 순서로 원하는 너비 만큼을 표현할 수 있다. 위의 코드로 보면 아래쪽으로 1dp 만큼의 그림자를 표현한다는 것이다.

 

그리고 <corners는 그림자에 곡선을 얼마나 줄지 지정할 수 있다. 20dp만큼의 곡선을 주는 것이다.

 

마지막으로 <solid의 경우는 그림자의 색상으로 #FFFFFF로 투명도 100%의 흰색으로 지정하는 것이다.

이러한 방식으로 그림자를 쌓아서 표현할 수 있는데 여러줄로 표현하면 아래처럼 왼쪽, 오른쪽, 아래에 그림자를 표현할 수 있다.

이러한 그림자를 넣으려고 무려 25줄의 item코드를 넣었다. 분명 간단하게하는 방법이 있을텐데...

 

중요한 것은 item 코드를 넣을때 아래줄로 갈 수록 이미지와 가깝다는 것이다.

맨 위줄의 코드가 가장 바깥쪽 그림자, 맨 아래줄의 코드가 가장 안쪽 그림자라는 뜻이다.

즉, 점점 색을 진하게 표현하면 된다.

 

자신만의 그림자 코드를 작성했으면 원하는 View에 background로 해서 적용하면 된다.

<androidx.constraintlayout.widget.ConstraintLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="196dp"
    android:layout_height="305dp"
    android:layout_gravity="center"
    android:layout_marginRight="6dp"
    android:background="@drawable/layout_shadow">

android:background="@drawable/layout_shadow" 로 해서 적용했다.


그러면 여기서 한가지 의문점이 들게 된다. 그림자를 적용할거면 ImageView에 넣어야하는게 아닌가?


중요한 점이 있다. 커스텀한 그림자를 View에 적용시키게 되면 그림자 너비 만큼 해당 View에도 여유 공간이 있어야한다.

 

즉, ImageView의 지정한 android:layout_width="190"이고 그림자의 너비가 android:right="3dp", android:left="3dp"라면

ImageView의 android:layout_width="196"이 되어야 실제로 보이는건 190만큼 보이는 것이다.

그 이유는 그림자가 ImageView의 width 일부를 덮어 씌우기 때문이다.

 

원하는 뷰의 width + 그림자 width = 지정해야하는 진짜 width

ex) 190dp + 6dp = 196dp


이미지로 표현하면 다음과 같다. 주황색이 ImageView, 연한 회색이 그림자, 파란색은 비교를 위한 View이다.

 

< ImageView의 width를 190dp로 하고 그림자의 right과 left가 합쳐서 6인 경우 >

< ImageView의 width를 196dp로 하고 그림자의 right과 left가 합쳐서 6인 경우 >

아래 파란색의 너비가 190dp이다. 그런데 첫번째 경우도 ImageView의 너비가 190dp인데 다른 것을 확인할 수 있다.

즉, 그림자 너비 만큼 줄어든 것이다.

아래는 196dp로 하니까 이제서야 파란색과 너비가 같아지게 된다. 이런 식으로 원래 너비보다 넓게 지정해야한다.


일반적인 View에서는 저런 방식으로해도 상관 없지만 ImageView 경우 문제가 발생한다.

firestore의 이미지의 크기를 ImageView의 너비에 맞추기 위해 android:scaleType="centerCrop" 사용할 경우 이미지의 크기가 이상하게 바뀌게 된다.

그림자 부분까지 Image가 채워지는 것이다. 오른쪽이 원래 원하는 그림자 표현이다.

 

이렇게 되는 이유는 196dp만큼의 width를 지정해줬으니 그만큼 다 채우게 되기 때문이다.


이것을 해결해주기 위해서는 ImagView 바깥쪽에 다른 View를 놓고 그 View에 그림자를 적용하면 된다.

<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="196dp"
    android:layout_height="305dp"
    android:layout_gravity="center"
    android:layout_marginRight="6dp"
    android:background="@drawable/layout_shadow">

    <ImageView
        android:id="@+id/rankImage"
        android:layout_width="190dp"
        android:layout_height="280dp"
        android:scaleType="centerCrop"
        android:src="@drawable/rank_image_default"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        android:layout_marginTop="0dp"
        app:layout_constraintVertical_bias="0.0" />

나는 ImageView를 감싸고 있는 View인 constraintlayout에 적용했다.

그러면 '원하는 뷰의 width + 그림자 width = 지정해야하는 진짜 width' 이것도 constraintlayout에만 적용하면 된다.

ImageView의 width는 원래 원했던 190dp로 지정할 수 있다. 이런식으로하게 되면 아래와 같이 깔끔하게 나온다.

 

728x90
반응형