지난 포스팅에서는 Firebase Storage를 사용하여 이미지를 다중 업로드 하는 예제를 만들어 봤는데, 업로드에 이어 아이템의 버튼을 클릭했을 때 아이템이 삭제 되는 기능을 추가하였다. 본 포스팅에서는 추가 된 부분의 코드만 작성하려고 하니 자세한 내용은 다중 이미지 업로드 (1) 을 참고해보자
- 아이템의 우측 상단에 삭제 버튼을 추가하였다.
- 삭제 버튼을 눌러야지만 아이템이 삭제되며 삭제된 아이템의 개수도 반영되어 실시간으로 선택 된 이미지의 개수를 확인할 수 있다.
📌 FrameLayout
여러 개의 뷰를 중첩으로 배치하고 그 중 하나를 레이아웃의 전면에 표시할 때 사용하는 레이아웃
이미지뷰 위에 삭제 버튼을 겹쳐서 배치하기 위해 FrameLayout을 사용하였다.
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 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="100dp"
android:layout_height="100dp">
<ImageView
android:id="@+id/imageArea"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="10dp"
android:scaleType="center" />
<Button
android:id="@+id/btnDelete"
android:layout_width="35dp"
android:layout_height="35dp"
android:layout_gravity="right"
android:background="@drawable/delete"
tools:ignore="RtlHardcoded" />
</FrameLayout>
// onItemClickListener 인터페이스 선언하기
interface onItemClickListener {
fun onItemClick(position: Int)
}
// onItemClickListener 참조 변수 선언하기
private lateinit var itemClickListener: onItemClickListener
// onItemClickListener 등록 메서드
fun setItemClickListener(itemClickListener: onItemClickListener) {
this.itemClickListener = itemClickListener
}
// ViewHolder에서 delete 버튼을 눌렀을 때 Listener에 onItemClick 등록하기
delete.setOnClickListener {
val position = adapterPosition
itemClickListener.onItemClick(position)
}
class ImageAdapter(val context: Context, val items: ArrayList<Uri>) :
RecyclerView.Adapter<ImageAdapter.ViewHolder>() {
interface onItemClickListener {
fun onItemClick(position: Int)
}
private lateinit var itemClickListener: onItemClickListener
fun setItemClickListener(itemClickListener: onItemClickListener) {
this.itemClickListener = itemClickListener
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ImageAdapter.ViewHolder {
val v =
LayoutInflater.from(parent.context).inflate(R.layout.item_image, parent, false)
return ViewHolder(v)
}
override fun getItemCount(): Int {
return items.count()
}
override fun onBindViewHolder(holder: ImageAdapter.ViewHolder, position: Int) {
holder.bindItems(items[position])
}
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
fun bindItems(item: Uri) {
val imageArea = itemView.findViewById<ImageView>(R.id.imageArea)
val delete = itemView.findViewById<Button>(R.id.btnDelete)
delete.setOnClickListener {
val position = adapterPosition
itemClickListener.onItemClick(position)
}
Glide.with(context).load(item).into(imageArea)
}
}
}
adapter.setItemClickListener(object : ImageAdapter.onItemClickListener {
override fun onItemClick(position: Int) {
// 해당 position의 요소 삭제
uriList.removeAt(position)
// notifyDataSetChanged()를 호출하여 adapter에게 값이 변경 되었음을 알려준다.
adapter.notifyDataSetChanged()
printCount()
}
class MultiImageFragment : Fragment() {
private lateinit var binding: FragmentMultiImageBinding
lateinit var mainActivity: MainActivity
private var uriList = ArrayList<Uri>()
private val maxNumber = 10
lateinit var adapter: ImageAdapter
override fun onAttach(context: Context) {
super.onAttach(context)
mainActivity = context as MainActivity
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = DataBindingUtil.inflate(inflater, R.layout.fragment_multi_image, container, false)
printCount()
adapter = ImageAdapter(mainActivity, uriList)
binding.recyclerview.adapter = adapter
binding.recyclerview.layoutManager =
LinearLayoutManager(mainActivity, LinearLayoutManager.HORIZONTAL, false)
binding.imageArea.setOnClickListener {
if (uriList.count() == maxNumber) {
Toast.makeText(
getActivity(),
"이미지는 최대 ${maxNumber}장까지 첨부할 수 있습니다.",
Toast.LENGTH_SHORT
).show();
return@setOnClickListener
}
val intent = Intent(Intent.ACTION_PICK)
intent.type = "image/*"
intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)
registerForActivityResult.launch(intent)
}
binding.btnRegister.setOnClickListener {
for (i in 0 until uriList.count()) {
imageUpload(uriList.get(i), i)
try {
Thread.sleep(500)
} catch (e: InterruptedException) {
e.printStackTrace()
}
}
}
adapter.setItemClickListener(object : ImageAdapter.onItemClickListener {
override fun onItemClick(position: Int) {
uriList.removeAt(position)
adapter.notifyDataSetChanged()
printCount()
}
})
return binding.root
}
private val registerForActivityResult =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
when (result.resultCode) {
AppCompatActivity.RESULT_OK -> {
val clipData = result.data?.clipData
if (clipData != null) { // 이미지를 여러 개 선택할 경우
val clipDataSize = clipData.itemCount
val selectableCount = maxNumber - uriList.count()
if (clipDataSize > selectableCount) { // 최대 선택 가능한 개수를 초과해서 선택한 경우
Toast.makeText(
getActivity(),
"이미지는 최대 ${selectableCount}장까지 첨부할 수 있습니다.",
Toast.LENGTH_SHORT
).show();
} else {
// 선택 가능한 경우 ArrayList에 가져온 uri를 넣어준다.
for (i in 0 until clipDataSize) {
uriList.add(clipData.getItemAt(i).uri)
}
}
} else { // 이미지를 한 개만 선택할 경우 null이 올 수 있다.
val uri = result?.data?.data
if (uri != null) {
uriList.add(uri)
}
}
// notifyDataSetChanged()를 호출하여 adapter에게 값이 변경 되었음을 알려준다.
adapter.notifyDataSetChanged()
printCount()
}
}
}
private fun printCount() {
val text = "${uriList.count()}/${maxNumber}"
binding.countArea.text = text
}
private fun imageUpload(uri: Uri, count: Int) {
// storage 인스턴스 생성
val storage = Firebase.storage
// storage 참조
val storageRef = storage.getReference("image")
// storage에 저장할 파일명 선언
val fileName = SimpleDateFormat("yyyyMMddHHmmss_${count}").format(Date())
val mountainsRef = storageRef.child("${fileName}.png")
val uploadTask = mountainsRef.putFile(uri)
uploadTask.addOnSuccessListener { taskSnapshot ->
// 파일 업로드 성공
Toast.makeText(getActivity(), "사진 업로드 성공", Toast.LENGTH_SHORT).show();
}.addOnFailureListener {
// 파일 업로드 실패
Toast.makeText(getActivity(), "사진 업로드 실패", Toast.LENGTH_SHORT).show();
}
}
}
데이터 불러오기는 없나요?