diff --git a/app/src/main/java/com/example/customtilelistpractice/adapter/MainAdapter.kt b/app/src/main/java/com/example/customtilelistpractice/MainAdapter.kt similarity index 76% rename from app/src/main/java/com/example/customtilelistpractice/adapter/MainAdapter.kt rename to app/src/main/java/com/example/customtilelistpractice/MainAdapter.kt index 1369e69..09abe55 100644 --- a/app/src/main/java/com/example/customtilelistpractice/adapter/MainAdapter.kt +++ b/app/src/main/java/com/example/customtilelistpractice/MainAdapter.kt @@ -6,9 +6,12 @@ import android.view.ViewGroup import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.RecyclerView -import com.example.customtilelistpractice.databinding.ActivityMainRvTileBinding -import com.example.customtilelistpractice.databinding.ActivityMainTitleBinding -import com.example.customtilelistpractice.model.TileEntity +import com.example.customtilelistpractice.databinding.ActivityMainRvTileListBinding +import com.example.customtilelistpractice.databinding.ActivityMainRvTitleBinding +import com.example.customtilelistpractice.tile.model.RectangleTile +import com.example.customtilelistpractice.tile.model.SquareTile +import com.example.customtilelistpractice.tile.ui.ItemTouchHelperCallback +import com.example.customtilelistpractice.tile.ui.TileAdapter /** * MainAdapter.kt @@ -27,9 +30,9 @@ class MainAdapter : RecyclerView.Adapter() { val inflater = LayoutInflater.from(parent.context) return if (viewType == TYPE_TITLE) - TitleViewHolder(ActivityMainTitleBinding.inflate(inflater, parent, false)) + TitleViewHolder(ActivityMainRvTitleBinding.inflate(inflater, parent, false)) else - TileViewHolder(ActivityMainRvTileBinding.inflate(inflater, parent, false)) + TileListViewHolder(ActivityMainRvTileListBinding.inflate(inflater, parent, false)) } override fun onBindViewHolder(holder: MainCommonViewHolder, position: Int) { @@ -49,34 +52,19 @@ class MainAdapter : RecyclerView.Adapter() { const val TYPE_TILE = 1 private val TEST_TILE_LIST = listOf( - TileEntity("1", "A", 1, 1), - TileEntity("2", "B", 2, 1), - TileEntity("3", "C", 1, 1), - TileEntity("5", "D", 3, 1), - TileEntity("6", "E", 1, 1), - TileEntity("7", "F", 2, 1), - TileEntity("8", "G", 1, 1), - TileEntity("9", "h", 3, 2), - TileEntity("1", "A", 1, 1), - TileEntity("2", "B", 2, 1), - TileEntity("3", "C", 1, 1), - TileEntity("5", "D", 3, 2), - TileEntity("6", "E", 1, 1), - TileEntity("7", "F", 2, 1), - TileEntity("8", "G", 1, 1), - TileEntity("9", "h", 3, 2), + SquareTile("메뉴1"), + SquareTile("메뉴2"), + SquareTile("메뉴3"), + SquareTile("메뉴4"), + SquareTile("메뉴5"), + SquareTile("메뉴6"), + RectangleTile("금융뉴스"), + RectangleTile("이번달 소비"), + RectangleTile("소비습관 추천"), ) } - - - - - - - - - inner class TitleViewHolder(private val binding: ActivityMainTitleBinding) : + inner class TitleViewHolder(private val binding: ActivityMainRvTitleBinding) : MainCommonViewHolder(binding.root) { init { binding.okButton.setOnClickListener { @@ -89,7 +77,7 @@ class MainAdapter : RecyclerView.Adapter() { override fun bind() {} } - inner class TileViewHolder(private val binding: ActivityMainRvTileBinding) : + inner class TileListViewHolder(private val binding: ActivityMainRvTileListBinding) : MainCommonViewHolder(binding.root) { init { @@ -124,4 +112,4 @@ class MainAdapter : RecyclerView.Adapter() { abstract class MainCommonViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { abstract fun bind() -} \ No newline at end of file +} diff --git a/app/src/main/java/com/example/customtilelistpractice/adapter/TileAdapter.kt b/app/src/main/java/com/example/customtilelistpractice/adapter/TileAdapter.kt deleted file mode 100644 index 1e5a294..0000000 --- a/app/src/main/java/com/example/customtilelistpractice/adapter/TileAdapter.kt +++ /dev/null @@ -1,117 +0,0 @@ -package com.example.customtilelistpractice.adapter - -import android.view.LayoutInflater -import android.view.ViewGroup -import androidx.recyclerview.widget.ListAdapter -import androidx.recyclerview.widget.DiffUtil -import androidx.recyclerview.widget.RecyclerView -import com.example.customtilelistpractice.databinding.ItemTileBinding -import com.example.customtilelistpractice.model.TileEntity -import java.util.* - -/** - * MainAdapter.kt - * - * Created by Hayeong Lee on 2022/03/02 - * Copyright © 2021 Shinhan Bank. All rights reserved. - */ - -class TileAdapter : ListAdapter(DiffCallback()), - ItemTouchHelperCallback.OnItemMoveListener { - - var startEditModeEvent: (() -> Unit)? = null // 타일 아이템을 길게 클릭했을 때 이벤트 - override var isEditMode: Boolean = false // 현재 편집 모드인지 (= 아이템이 움직일 수 있는지) - - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { - return ViewHolder( - ItemTileBinding.inflate(LayoutInflater.from(parent.context), parent, false) - ) - } - - override fun onBindViewHolder(holder: ViewHolder, position: Int) { - holder.bind(getItem(position)) - } - - override fun getItemCount(): Int { - return currentList.size - } - - /** - * ItemTouchHelperCallback 에서 drag and drop 으로 아이템의 위치 변경이 감지되었을 때 (= onMove 가 호출되었을 때) 이벤트 구현 - */ - override fun onItemMoved(fromPosition: Int, toPosition: Int) { - // 편집모드일 때에만 아이템 위치 변경이 가능해야하므로, - // 편집모드가 아닐때에는 함수 종료 - if (!isEditMode) return - - // diffUtil 의 currentList 는 수정이 불가능한 List 타입이기 때문에 - // mutableList 로 변경 후에 아이템 위치를 swap 하고 submitList 수행 - val newList = currentList.toMutableList() - if (fromPosition < toPosition) { - for (i in fromPosition until toPosition) { - Collections.swap(newList, i, i + 1) - } - } else { - for (i in fromPosition downTo toPosition + 1) { - Collections.swap(newList, i, i - 1) - } - } - submitList(newList) - } - - private fun startEditMode() { - isEditMode = true - startEditModeEvent?.invoke() - notifyDataSetChanged() - } - - fun endEditMode() { - isEditMode = false - notifyDataSetChanged() - } - - private class DiffCallback : DiffUtil.ItemCallback() { - override fun areItemsTheSame(oldItem: TileEntity, newItem: TileEntity): Boolean = - oldItem.id == newItem.id - - override fun areContentsTheSame(oldItem: TileEntity, newItem: TileEntity): Boolean = - oldItem == newItem - } - - inner class ViewHolder(private val binding: ItemTileBinding) : RecyclerView.ViewHolder(binding.root) { - - init { - /** - * setOnLongClickListener 반환값 - * false: default. 다음 이벤트 진행함. longClick 후 click 이벤트 실행됨. - * true: 이벤트 종료됨. longClick 후 click 이벤트가 실행되지 않음. longClick 이벤트만 활성화하고 싶을 때 사용. - */ - binding.root.setOnLongClickListener { - // 편집모드라면 long pressed 가 감지되었을 때 애니메이션을 동작하게 하면 안되므로, - // 편집모드가 아닐 때에만 편집모드 시작하여 애니메이션 등이 동작하도록 함 - if (!isEditMode) startEditMode() - return@setOnLongClickListener true - } - - // TODO: 뷰타입을 나눠서 TileEntity 에서 셋팅하고 있던 애니메션과 높이를 여기서 지정해보자.. - } - - fun bind(item: TileEntity) { - binding.tile = item - - // TODO 얘는 고정값인데 매번 바인드될때마다 해야할까? 더 좋은 위치가 있나? - binding.root.apply { - layoutParams.height = item.heightDp - animation = item.shakingAnimation - } - binding.root.requestLayout() - - // 편집모드일때만 흔들리는 애니메이션 시작 - if (isEditMode) { - binding.root.animation.start() - } else { - binding.root.clearAnimation() - } - } - } -} diff --git a/app/src/main/java/com/example/customtilelistpractice/extensions/IntExtensions.kt b/app/src/main/java/com/example/customtilelistpractice/common/IntExtensions.kt similarity index 83% rename from app/src/main/java/com/example/customtilelistpractice/extensions/IntExtensions.kt rename to app/src/main/java/com/example/customtilelistpractice/common/IntExtensions.kt index 5f1ebae..1f02f03 100644 --- a/app/src/main/java/com/example/customtilelistpractice/extensions/IntExtensions.kt +++ b/app/src/main/java/com/example/customtilelistpractice/common/IntExtensions.kt @@ -1,4 +1,4 @@ -package com.example.customtilelistpractice.extensions +package com.example.customtilelistpractice.common import android.content.res.Resources diff --git a/app/src/main/java/com/example/customtilelistpractice/RotateAnimations.kt b/app/src/main/java/com/example/customtilelistpractice/common/RotateAnimations.kt similarity index 93% rename from app/src/main/java/com/example/customtilelistpractice/RotateAnimations.kt rename to app/src/main/java/com/example/customtilelistpractice/common/RotateAnimations.kt index b6614de..8df3c20 100644 --- a/app/src/main/java/com/example/customtilelistpractice/RotateAnimations.kt +++ b/app/src/main/java/com/example/customtilelistpractice/common/RotateAnimations.kt @@ -1,4 +1,4 @@ -package com.example.customtilelistpractice +package com.example.customtilelistpractice.common import android.view.animation.Animation import android.view.animation.RotateAnimation diff --git a/app/src/main/java/com/example/customtilelistpractice/model/TileEntity.kt b/app/src/main/java/com/example/customtilelistpractice/model/TileEntity.kt deleted file mode 100644 index 9cdf02b..0000000 --- a/app/src/main/java/com/example/customtilelistpractice/model/TileEntity.kt +++ /dev/null @@ -1,23 +0,0 @@ -package com.example.customtilelistpractice.model - -import android.view.animation.RotateAnimation -import com.example.customtilelistpractice.bigShakingAnimation -import com.example.customtilelistpractice.extensions.dp -import com.example.customtilelistpractice.smallShakingAnimation - -/** - * TileEntity.kt - * - * Created by Hayeong Lee on 2022/03/02 - * Copyright © 2021 Shinhan Bank. All rights reserved. - */ - -data class TileEntity( - val id: String, - val name: String, - val widthSpan: Int, - val heightSpan: Int, -) { - val shakingAnimation: RotateAnimation = if (widthSpan == 1) bigShakingAnimation else smallShakingAnimation - val heightDp: Int = if (heightSpan == 1) 100.dp else 200.dp -} diff --git a/app/src/main/java/com/example/customtilelistpractice/tile/model/TileEntity.kt b/app/src/main/java/com/example/customtilelistpractice/tile/model/TileEntity.kt new file mode 100644 index 0000000..9ac7237 --- /dev/null +++ b/app/src/main/java/com/example/customtilelistpractice/tile/model/TileEntity.kt @@ -0,0 +1,52 @@ +package com.example.customtilelistpractice.tile.model + +/** + * TileEntity.kt + * + * Created by Hayeong Lee on 2022/03/02 + * Copyright © 2021 Shinhan Bank. All rights reserved. + */ + +enum class TileType { + SQUARE, RECTANGLE +} + +open class TileEntity( + val type: TileType, + val widthSpan: Int, + val title: String, +) { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is TileEntity) return false + + if (type != other.type) return false + if (widthSpan != other.widthSpan) return false + if (title != other.title) return false + + return true + } + + override fun hashCode(): Int { + var result = type.hashCode() + result = 31 * result + widthSpan + result = 31 * result + title.hashCode() + return result + } +} + +class SquareTile( + title: String +) : TileEntity( + TileType.SQUARE, + 1, + title +) + +class RectangleTile( + title: String +) : TileEntity( + TileType.RECTANGLE, + 3, + title +) diff --git a/app/src/main/java/com/example/customtilelistpractice/adapter/ItemTouchHelperCallback.kt b/app/src/main/java/com/example/customtilelistpractice/tile/ui/ItemTouchHelperCallback.kt similarity index 97% rename from app/src/main/java/com/example/customtilelistpractice/adapter/ItemTouchHelperCallback.kt rename to app/src/main/java/com/example/customtilelistpractice/tile/ui/ItemTouchHelperCallback.kt index 93d2b0e..d3c186a 100644 --- a/app/src/main/java/com/example/customtilelistpractice/adapter/ItemTouchHelperCallback.kt +++ b/app/src/main/java/com/example/customtilelistpractice/tile/ui/ItemTouchHelperCallback.kt @@ -1,4 +1,4 @@ -package com.example.customtilelistpractice.adapter +package com.example.customtilelistpractice.tile.ui import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.RecyclerView diff --git a/app/src/main/java/com/example/customtilelistpractice/tile/ui/TileAdapter.kt b/app/src/main/java/com/example/customtilelistpractice/tile/ui/TileAdapter.kt new file mode 100644 index 0000000..c63e2c8 --- /dev/null +++ b/app/src/main/java/com/example/customtilelistpractice/tile/ui/TileAdapter.kt @@ -0,0 +1,113 @@ +package com.example.customtilelistpractice.tile.ui + +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ListAdapter +import com.example.customtilelistpractice.databinding.ItemTileRectangleBinding +import com.example.customtilelistpractice.databinding.ItemTileSquareBinding +import com.example.customtilelistpractice.tile.model.TileEntity +import com.example.customtilelistpractice.tile.model.TileType +import java.util.* + +/** + * MainAdapter.kt + * + * Created by Hayeong Lee on 2022/03/02 + * Copyright © 2021 Shinhan Bank. All rights reserved. + */ + +class TileAdapter : ListAdapter(DiffCallback()), + ItemTouchHelperCallback.OnItemMoveListener { + + var startEditModeEvent: (() -> Unit)? = null // 타일 아이템을 길게 클릭했을 때 이벤트 + override var isEditMode: Boolean = false // 현재 편집 모드인지 (= 아이템이 움직일 수 있는지) + + /** + * setOnLongClickListener 반환값 + * false: default. 다음 이벤트 진행함. longClick 후 click 이벤트 실행됨. + * true: 이벤트 종료됨. longClick 후 click 이벤트가 실행되지 않음. longClick 이벤트만 활성화하고 싶을 때 사용. + */ + private val longClickListener = View.OnLongClickListener { + // 편집모드라면 long pressed 가 감지되었을 때 애니메이션을 동작하게 하면 안되므로, 편집모드가 아닐 때에만 편집모드 시작하여 애니메이션 등이 동작하도록 함 + if (!isEditMode) startEditMode() + return@OnLongClickListener true + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TileViewHolder { + val inflater = LayoutInflater.from(parent.context) + return when (viewType) { + TileType.SQUARE.ordinal -> { + SquareTileViewHolder( + ItemTileSquareBinding.inflate(inflater, parent, false), + longClickListener + ) + } + TileType.RECTANGLE.ordinal -> { + RectangleTileViewHolder( + ItemTileRectangleBinding.inflate(inflater, parent, false), + longClickListener + ) + } + else -> { + SquareTileViewHolder( + ItemTileSquareBinding.inflate(inflater, parent, false), + longClickListener + ) + } + } + } + + override fun onBindViewHolder(holder: TileViewHolder, position: Int) { + holder.bind(currentList[position], isEditMode) + } + + override fun getItemCount(): Int { + return currentList.size + } + + override fun getItemViewType(position: Int): Int { + return currentList[position].type.ordinal + } + + /** + * ItemTouchHelperCallback 에서 drag and drop 으로 아이템의 위치 변경이 감지되었을 때 (= onMove 가 호출되었을 때) 이벤트 구현 + */ + override fun onItemMoved(fromPosition: Int, toPosition: Int) { + // 편집모드일 때에만 아이템 위치 변경이 가능해야하므로, 편집모드가 아닐때에는 함수 종료 + if (!isEditMode) return + + // diffUtil 의 currentList 는 수정이 불가능한 List 타입이기 때문에 mutableList 로 변경 후에 아이템 위치를 swap 하고 submitList 수행 + val newList = currentList.toMutableList() + if (fromPosition < toPosition) { + for (i in fromPosition until toPosition) { + Collections.swap(newList, i, i + 1) + } + } else { + for (i in fromPosition downTo toPosition + 1) { + Collections.swap(newList, i, i - 1) + } + } + submitList(newList) + } + + private fun startEditMode() { + isEditMode = true + startEditModeEvent?.invoke() + notifyDataSetChanged() + } + + fun endEditMode() { + isEditMode = false + notifyDataSetChanged() + } + + private class DiffCallback : DiffUtil.ItemCallback() { + override fun areItemsTheSame(oldItem: TileEntity, newItem: TileEntity): Boolean = + oldItem.title == newItem.title + + override fun areContentsTheSame(oldItem: TileEntity, newItem: TileEntity): Boolean = + oldItem == newItem + } +} diff --git a/app/src/main/java/com/example/customtilelistpractice/tile/ui/TileViewHolders.kt b/app/src/main/java/com/example/customtilelistpractice/tile/ui/TileViewHolders.kt new file mode 100644 index 0000000..ba049d3 --- /dev/null +++ b/app/src/main/java/com/example/customtilelistpractice/tile/ui/TileViewHolders.kt @@ -0,0 +1,74 @@ +package com.example.customtilelistpractice.tile.ui + +import android.view.View +import androidx.databinding.ViewDataBinding +import androidx.recyclerview.widget.RecyclerView +import com.example.customtilelistpractice.common.bigShakingAnimation +import com.example.customtilelistpractice.databinding.ItemTileRectangleBinding +import com.example.customtilelistpractice.databinding.ItemTileSquareBinding +import com.example.customtilelistpractice.common.smallShakingAnimation +import com.example.customtilelistpractice.tile.model.RectangleTile +import com.example.customtilelistpractice.tile.model.SquareTile +import com.example.customtilelistpractice.tile.model.TileEntity + +/** + * TileViewHolders.kt + * + * Created by Hayeong Lee on 2022/04/08 + * Copyright © 2022 Shinhan Bank. All rights reserved. + */ + +abstract class TileViewHolder(binding: ViewDataBinding) : RecyclerView.ViewHolder(binding.root) { + + abstract fun bind(tileEntity: TileEntity, isEditMode: Boolean) +} + +/** + * 1X1 정사각형 ViewHolder + */ +class SquareTileViewHolder( + private val binding: ItemTileSquareBinding, + onLongClickListener: View.OnLongClickListener +) : TileViewHolder(binding) { + + init { + binding.root.setOnLongClickListener(onLongClickListener) + } + + override fun bind(tileEntity: TileEntity, isEditMode: Boolean) { + val item = tileEntity as? SquareTile ?: return + binding.tile = item + binding.root.animation = bigShakingAnimation + + if (isEditMode) { + binding.root.animation.start() + } else { + binding.root.clearAnimation() + } + } +} + +/** + * 3X1 직사각형 ViewHolder + */ +class RectangleTileViewHolder( + private val binding: ItemTileRectangleBinding, + onLongClickListener: View.OnLongClickListener +) : TileViewHolder(binding) { + + init { + binding.root.setOnLongClickListener(onLongClickListener) + } + + override fun bind(tileEntity: TileEntity, isEditMode: Boolean) { + val item = tileEntity as? RectangleTile ?: return + binding.tile = item + binding.root.animation = smallShakingAnimation + + if (isEditMode) { + binding.root.animation.start() + } else { + binding.root.clearAnimation() + } + } +} diff --git a/app/src/main/res/drawable/tile_background.xml b/app/src/main/res/drawable/tile_background.xml new file mode 100644 index 0000000..accd5dd --- /dev/null +++ b/app/src/main/res/drawable/tile_background.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main_rv.xml b/app/src/main/res/layout/activity_main_rv.xml index 61329a8..9dbda47 100644 --- a/app/src/main/res/layout/activity_main_rv.xml +++ b/app/src/main/res/layout/activity_main_rv.xml @@ -10,5 +10,7 @@ android:id="@+id/mainRvContainer" android:layout_width="match_parent" android:layout_height="wrap_content" - app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"/> + android:background="#F6FAFF" + app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" /> + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main_rv_tile.xml b/app/src/main/res/layout/activity_main_rv_tile_list.xml similarity index 91% rename from app/src/main/res/layout/activity_main_rv_tile.xml rename to app/src/main/res/layout/activity_main_rv_tile_list.xml index 4463192..9b7fa4d 100644 --- a/app/src/main/res/layout/activity_main_rv_tile.xml +++ b/app/src/main/res/layout/activity_main_rv_tile_list.xml @@ -14,5 +14,5 @@ android:layout_margin="10dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintTop_toBottomOf="@id/title" - tools:listitem="@layout/item_tile" /> + tools:listitem="@layout/item_tile_square" /> \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main_title.xml b/app/src/main/res/layout/activity_main_rv_title.xml similarity index 100% rename from app/src/main/res/layout/activity_main_title.xml rename to app/src/main/res/layout/activity_main_rv_title.xml diff --git a/app/src/main/res/layout/item_tile.xml b/app/src/main/res/layout/item_tile.xml deleted file mode 100644 index f37c654..0000000 --- a/app/src/main/res/layout/item_tile.xml +++ /dev/null @@ -1,45 +0,0 @@ - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/item_tile_rectangle.xml b/app/src/main/res/layout/item_tile_rectangle.xml new file mode 100644 index 0000000..e94d8f2 --- /dev/null +++ b/app/src/main/res/layout/item_tile_rectangle.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_tile_square.xml b/app/src/main/res/layout/item_tile_square.xml new file mode 100644 index 0000000..1e87881 --- /dev/null +++ b/app/src/main/res/layout/item_tile_square.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + \ No newline at end of file