본문 바로가기
개발 이야기/안드로이드 개발

쉽고 빠르게 이미지 처리하는 방법, Glide Library

by 정선한 2022. 10. 18.
728x90
반응형

Glide Logo Image

오늘은 안드로이드에서 이미지를 로드하기 위해서 사용하는 Glide 라이브러리에 대해서 정리를 해보려고 합니다. 저는 현업에서 개발을 할 때에도 이미지와 관련한 처리는 Glide를 통해서 개발을 해왔었습니다.

왜 Glide를 사용했느냐고 한다면 “… 빨라서…”라고밖에 답을 할 수 없을 것 같습니다. 보통 이미지를 불러올 때에는 app자체의 drawable 이미지를 로드해오기도 하지만 서버 혹은 외부 이미지를 불러오는 경우가 대다수입니다. 따라서 빠르게 이미지 로드 기능을 처리하고 편리하게 코드를 완성하는 것의 주된 목적입니다. 이때 Glide에서 지원하는 다양한 메소드 및 클래스들을 이용하면 정말 쉽게 다양한 이미지 로드를 구현해낼 수 있습니다.

Glide 적용 방법

Module 의 build.gradle에 라이브러리 종속성 추가

// Glide
    implementation 'com.github.bumptech.glide:glide:4.12.0'
    implementation 'com.github.bumptech.glide:annotations:4.12.0'
    annotationProcessor 'com.github.bumptech.glide:compiler:4.12.0'
    kapt 'com.github.bumptech.glide:compiler:4.12.0'

ImageView에 Glide 라이브러리를 통해 Url로 Image를 적용

Glide.with(albumThumbnail)
      .load(items.contentUri)
      .into(albumThumbnail)

id=”@+id/albumThumbnail”을 가진 ImageView에 items.contentUri의 이미지를 로드할 수 있습니다.

정말 간단하지 않나요? 어떤 곳에 어떤 이미지를 등록한다! 이게 사실 끝입니다.

하지만 이렇게만 사용한다면 굳이 라이브러리를 사용할 필요가 없겠죠. 더 많은 기능을 편하게 사용할 수 있어야 라이브러리를 사용하는 의미가 있습니다.

Glide.with()
    .load()
    .transform() 
    /*
        .centerCrop() 
        .centerInside() 
        .circleCrop() 
        .fitCenter() 
        .rotate() 
        .roundedCorners()
    */
    .placeholder()
    .error()
    .fallback()
    .thumbnail()
    .into()

위의 메소드들로 기본적인 이미지에 대한 처리는 대부분 해줄 수 있습니다.

  • with() : Activity, Fragment의 context를 등록
  • load() : 이미지 로드 (다양한 방식의 이미지를 로드할 수 있음)
  • into() : 이미지를 로드할 View를 등록
  • transform() : 이미지를 변형하여 View에 표시 (다양한 변형 관련 메소드 사용)
  • placeholder() : 이미지를 로드하기 전에 보여줄 임시 이미지 등록
  • error() : 이미지를 로드하는 과정에서 error가 발생했을 때, 보여줄 이미지 등록
  • fallback() : 로드할 이미지가 null일때, 보여줄 이미지 등록
  • thumbnail() : 이미지의 thumbnail을 생성 (해상도가 큰 이미지의 경우 로드 속도 개선을 위해 thumbnail로 일정 이미지의 해상도를 낮추어 노출할 수 있도록 함.)

예제 코드를 통해서 추가적으로 설명을 더해보자면 위 이미지를 자세히 보시면 위쪽에 radius처리가 된 것을 확인할 수 있는데 해당 내용을 어떻게 구현하였는지 코드를 통해서 함께 설명해보겠습니다.

  • 디바이스의 이미지, 동영상을 읽어와 저장 폴더별 리스트를 구현.
  • 폴더 명, 폴더 별 이미지 개수, 폴더의 첫번째 이미지를 썸네일을 아이템으로 구성하여 노출.

추가적으로 외부 서버를 통한 이미지 로드 과정이 아니기 때문에 로드 시간 및 오류 처리는 따로 해주지 않고 로드하는 과정만 구현하였습니다.

이미지 라운딩 처리를 위한 예제코드

fragment_main.xml 에서 RecyclerView를 표현하기 위해 include 하여 해당 xml을 로드하였습니다.

<include
    android:id="@+id/folder_content"
    layout="@layout/folder_content_scrolling"/>

folder_content_scrolling.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.recyclerview.widget.RecyclerView
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:android="http://schemas.android.com/apk/res/android"
    app:layout_behavior="@string/appbar_scrolling_view_behavior"
    android:id="@+id/recycler_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:visibility="visible" />

MainFragment 코드에서 RecyclerView에 대한 설정을 하도록 합니다.

val gridLayoutManager = GridLayoutManager(requireContext(), 2)

binding.folderContent.recyclerView.apply {
    layoutManager = gridLayoutManager
    adapter = mainAdapter
}
  • GridLayoutManager를 이용하여 2열을 가지는 RecylerView를 적용합니다. mvvm, databinding의 적용으로 folderContent(RecyclerView)에 대한 기본 설정을 적용합니다.

MainFragment에서 mainAdapter에 관한 이벤트들을 받아 처리하는 코드도 구성합니다.

private val mainAdapter by lazy {
    MainAdapter { itemClick ->
        val action = MainFragmentDirections.actionMainFragmentToDetailFragment()
        findNavController().navigate(action)
        viewModel.setSelectedFolder(itemClick)
    }
}
  • MainAdapter를 mainAdapter에 구성하고 itemClick 파라미터를 통해 RecyclerView의 클릭 이벤트를 연결하여 해당 아이템을 클릭했을 때 어떤 이벤트를 처리할지를 MainFragment내부에 구현합니다.
  • 저는 Navigation코드를 연결하여 다음 페이지로 이동할 수 있도록 이벤트 콜백을 구현하였습니다.

MainAdapter을 구성하여 RecyclerView의 아이템들을 연결해줍니다.

package com.seonhan_dev.imagepicker.ui.main

import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
import com.bumptech.glide.load.resource.bitmap.CenterCrop
import com.bumptech.glide.load.resource.bitmap.GranularRoundedCorners
import com.seonhan_dev.imagepicker.data.model.MediaStoreFolder
import com.seonhan_dev.imagepicker.databinding.ItemAlbumThumbBinding

class MainAdapter(private var itemClick: (MediaStoreFolder) -> Unit) :
    ListAdapter<MediaStoreFolder, MainAdapter.ViewHolder> (MediaStoreFolder.DiffCallback) {

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        val binding = ItemAlbumThumbBinding.inflate(
            LayoutInflater.from(parent.context),
            parent,
            false
        )

        return ViewHolder(binding)
    }

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        getItem(position)?.let {
            holder.bind(it, holder)
        }
    }

    inner class ViewHolder(private val binding: ItemAlbumThumbBinding) :
        RecyclerView.ViewHolder(binding.root) {

        fun bind(items: MediaStoreFolder, holder: ViewHolder) {
            with(binding) {
                albumName.text = items.item
                albumCount.text = items.count.toString() + " Images"

                Glide.with(albumThumbnail)
                    .load(items.contentUri)
                    .transform(
                        CenterCrop(),
                        GranularRoundedCorners(27f, 27f, 0f, 0f)
                    )
                    .into(albumThumbnail)
            }

            itemView.setOnClickListener {
                itemClick(items)
            }
        }
    }

}

item_album_thumb.xml

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <LinearLayout
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="@dimen/mainFragment_content_margin"
        android:elevation="@dimen/mainFragment_content_elevation">

        <ImageView
            android:id="@+id/album_thumbnail"
            android:layout_width="match_parent"
            android:layout_height="@dimen/mainFragment_content_image_height"
            android:scaleType="centerCrop"/>

        <androidx.constraintlayout.widget.ConstraintLayout
            android:layout_width="match_parent"
            android:layout_height="@dimen/mainFragment_content_title_height"
            android:background="@drawable/bottom_corner_radius">

            <TextView
                android:id="@+id/album_name"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="9dp"
                android:layout_marginStart="8dp"
                android:textSize="@dimen/content_title_text_size"
                android:textColor="@color/colorTextDark"
                android:textStyle="bold"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toTopOf="parent" />

            <TextView
                android:id="@+id/album_count"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginBottom="@dimen/content_count_margin_bottom"
                android:layout_marginStart="8dp"
                android:textSize="@dimen/content_count_text_size"
                android:textColor="@color/colorTextDark"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toBottomOf="@id/album_name"/>

        </androidx.constraintlayout.widget.ConstraintLayout>


    </LinearLayout>


</layout>
  • RecyclerView의 구현에 관하여 자세히 다루지는 않고 Glide 구현부를 더 자세히 보도록 하겠습니다.
  • 디바이스의 이미지 정보는 MediaStoreFolder 모델에 저장되어있습니다. 해당 모델을 items로 받아 화면에 노출해야 할 정보들을 등록합니다.
  • 저는 이때 이미지의 윗부분에 radius처리를 해주어야 했기 때문에 transform() 함수를 이용하여 CenterCrop(), GranularRoundedCorners(27f, 27f, 0f, 0f)의 내용을 등록하였습니다.
  • GranularRoundedCorners 메소드는 Glide의 public final class GranularRoundedCorners extends BitmapTransformation로 구현되어있는 내용입니다.
  private final float topLeft;
  private final float topRight;
  private final float bottomRight;
  private final float bottomLeft;

  /** Provide the radii to round the corners of the bitmap. */
  public GranularRoundedCorners(
      float topLeft, float topRight, float bottomRight, float bottomLeft) {
    this.topLeft = topLeft;
    this.topRight = topRight;
    this.bottomRight = bottomRight;
    this.bottomLeft = bottomLeft;
  }

이런 과정들을 통하여 간단하게 해당 예시 이미지와 같은 내용을 구성할 수 있었습니다.

위와 같은 방법으로 쉽고 간단하게 Glide 라이브러리를 통해 필요한 곳에 적용해보시면 좋을 것 같습니다.

 

728x90
반응형