[Android] 반사효과 이미지 뷰

Mgmix

·

2021. 7. 18. 20:44

안드로이드의 ImageView 를 사용함에 있어서,  바닥의 물에 비친 반사효과가 필요할 때가 있었는데

구현 과정을 코드로 정리 해보았습니다.

 

구현 화면

 

구현하려는 효과는 위의 이미지와 같이, 이미지가 물에 비친듯한 효과로 반사되어 보여주도록 구현 할 예정입니다.

 

일단 해당 효과를 구현할 ImageView 클래스를 만들어 주어야합니다.

AppCompatImageView 를 상속한 ReflectionImageView 클래스를 생성 해줍니다.

 

이미지 뷰 이므로 전달받은 이미지 리소스를 적용 할 수있도록, setImageDrawable() 과 setImageResource() 를 오버라이드 해줍니다. 

 

효과를 적용하는 메소드를 생성하고, 기본적인 예외 처리등의 코드를 작성 해주도록 합니다.

 

초기 코드

class ReflectionImageView(context: Context, attrs: AttributeSet) :
    AppCompatImageView(context, attrs) {

    override fun setImageDrawable(drawable: Drawable?) {
        if (drawable == null) return
        if (drawable is BitmapDrawable) {
            drawable.bitmap?.let { setReflectionEffect(it) }
        }
    }

    override fun setImageResource(resId: Int) {
        setReflectionEffect(BitmapFactory.decodeResource(resources, resId))
    }

    private fun setReflectionEffect(origin: Bitmap) {
        super.setImageDrawable(BitmapDrawable(resources, origin))
    }
}

 

 

 

구현 작업 순서를 간단하게 풀어서 생각해보면 

  1. 원본 이미지를 출력
  2. 반사되어 비치는 영역 그려주기
  3. 반사 효과에 맞게 하단의 이미지를 반전
  4. 투명한 효과 적용 및 적절한 길이 조절

정도로 나눠서 구현을 진행 하도록 하겠습니다.

 

 

1. 원본 이미지 출력 

    private fun setReflectionEffect(origin: Bitmap) {

        val width = origin.width
        val height = origin.height

        val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)

        val canvas = Canvas(bitmap)
        canvas.drawBitmap(origin, 0f, 0f, null)

        super.setImageDrawable(BitmapDrawable(resources, bitmap))
    }

 

 

2. 반사되어 비치는 영역 그리기

    private fun setReflectionEffect(origin: Bitmap) {

        val width = origin.width
        val height = origin.height

        // 하단 이미지 표기 위해, 원본 이미지 만큼 길이를 확장
        val bitmap = Bitmap.createBitmap(width, height + height, Bitmap.Config.ARGB_8888)

        val canvas = Canvas(bitmap)
        canvas.drawBitmap(origin, 0f, 0f, null)

        // 이미지 그려주기
        val reflectImage = Bitmap.createBitmap(origin, 0, 0, width, height, null, false)
        canvas.drawBitmap(reflectImage, 0f, height.toFloat(), null)

        super.setImageDrawable(BitmapDrawable(resources, bitmap))
    }

 

3. 반사 효과에 맞게 하단의 이미지를 반전

반전을 시킬 때는 Matrix 의 preScale 또는 postScale 을 사용하여 Bitmap 의 Scale 을 조작해 줄 수있습니다.

    private fun setReflectionEffect(origin: Bitmap) {

        val width = origin.width
        val height = origin.height

        // 하단 이미지 표기 위해, 원본 이미지 만큼 길이를 확장
        val bitmap = Bitmap.createBitmap(width, height + height, Bitmap.Config.ARGB_8888)

        val canvas = Canvas(bitmap)
        canvas.drawBitmap(origin, 0f, 0f, null)

        // 반사 영역에 그려질 이미지를 대칭으로 만들어준다.
        val matrix = Matrix().apply { postScale(1f, -1f) }

        // 이미지 그려주기, Matrix 적용
        val reflectImage = Bitmap.createBitmap(origin, 0, 0, width, height, matrix, false)
        canvas.drawBitmap(reflectImage, 0f, height.toFloat(), null)

        super.setImageDrawable(BitmapDrawable(resources, bitmap))
    }

4. 투명한 효과 적용과 적절한 길이 조절 

점점 투명해 지는 듯한 효과를 주기 위해서, LinearGradient 를 사용한 그라데이션 효과와 그라데이션을 이미지에 합성하기 위한 PorterDuff.Mode 를 이용 한다. PotterDuff Mode 는 이미지에 대한 다양한 연산자를 통해서 (SRC, SRC_IN, DST, DST_IN, DST_OUT .. 등) 이미지를 합성을 도와줍니다.

 

그라데이션 shader 효과가 Source Image 로 들어가고, 반사될 이미지가 Destination Image 로 들어 가게되는데 DST_IN 연산자를 통해 겹치는 부분에서 Destination 의 색이 적용되고, 겹치지 않는 영역에서는 alpha 값이 0으로 적용됩니다.

    private fun setReflectionEffect(origin: Bitmap) {

        val width = origin.width
        val height = origin.height

        // 하단 이미지 표기 위해, 원본 이미지 만큼 길이를 확장
        val bitmap = Bitmap.createBitmap(width, height + height, Bitmap.Config.ARGB_8888)

        val canvas = Canvas(bitmap)
        canvas.drawBitmap(origin, 0f, 0f, null)

        // 반사 영역에 그려질 이미지를 대칭으로 만들어준다.
        val matrix = Matrix().apply { postScale(1f, -1f) }

        // 이미지 그려주기, Matrix 적용
        val reflectImage = Bitmap.createBitmap(origin, 0, 0, width, height, matrix, false)
        canvas.drawBitmap(reflectImage, 0f, height.toFloat(), null)

        // LinearGradient 를 통해 그라데이션 효과 적용
        val effect = LinearGradient(0f, origin.height.toFloat(), 0f, bitmap.height.toFloat(),
            0x70ffffff, 0x00000000, Shader.TileMode.MIRROR)

        val paint = Paint().apply {
            shader = effect
            xfermode = PorterDuffXfermode(PorterDuff.Mode.DST_IN)
        }

        // Canvas 에 그리기
        canvas.drawRect(0f, height.toFloat(), width.toFloat(), bitmap.height.toFloat(), paint)

        super.setImageDrawable(BitmapDrawable(resources, bitmap))
    }

전체가 모두 비치는게 약간은 부자연스럽기 때문에, 비치는 이미지를 절반 정도의 크기로 보여주도록 합니다.

        // 하단 이미지 표기 위해, 비치는 이미지를 원본의 절반 크기로 수정 
        val bitmap = Bitmap.createBitmap(width, height + height/2, Bitmap.Config.ARGB_8888)