-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
✨ implement category layout #89
Changes from 3 commits
8a3cc0e
cd96daa
870470c
dec35d5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
package net.pengcook.android.presentation.category | ||
|
||
data class Category( | ||
val id: Long, | ||
val title: String, | ||
val imageUrl: String, | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
package net.pengcook.android.presentation.category | ||
|
||
import android.view.LayoutInflater | ||
import android.view.ViewGroup | ||
import androidx.recyclerview.widget.DiffUtil | ||
import androidx.recyclerview.widget.ListAdapter | ||
import net.pengcook.android.databinding.ItemCategoryBinding | ||
|
||
class CategoryAdapter( | ||
private val categoryEventListener: CategoryEventListener, | ||
) : ListAdapter<Category, CategoryViewHolder>(diffUtil) { | ||
override fun onCreateViewHolder( | ||
parent: ViewGroup, | ||
viewType: Int, | ||
): CategoryViewHolder { | ||
val layoutInflater = LayoutInflater.from(parent.context) | ||
val binding = ItemCategoryBinding.inflate(layoutInflater, parent, false) | ||
return CategoryViewHolder(binding, categoryEventListener) | ||
} | ||
|
||
override fun onBindViewHolder( | ||
holder: CategoryViewHolder, | ||
position: Int, | ||
) { | ||
holder.bind(getItem(position)) | ||
} | ||
|
||
companion object { | ||
private val diffUtil = | ||
object : DiffUtil.ItemCallback<Category>() { | ||
override fun areItemsTheSame( | ||
oldItem: Category, | ||
newItem: Category, | ||
): Boolean { | ||
return oldItem.id == newItem.id | ||
} | ||
|
||
override fun areContentsTheSame( | ||
oldItem: Category, | ||
newItem: Category, | ||
): Boolean { | ||
return oldItem == newItem | ||
} | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
package net.pengcook.android.presentation.category | ||
|
||
interface CategoryEventListener { | ||
fun onNavigateToRecipesByCategory(categoryId: Long) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이 함수는 카테고리가 클릭되었을때 이동하는 내용을 추상화한 함수같은데, 네이밍이 너무 명시적이어서 문제가 될 소지가 있지 않을까 싶습니다. 오히려 추상화를 한다면 행위 자체에 초점을 맞추어 "onCategorySelected"와 같은 네이밍은 어떨까 싶습니다 :) |
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
package net.pengcook.android.presentation.category | ||
|
||
import android.os.Bundle | ||
import android.view.LayoutInflater | ||
import android.view.View | ||
import android.view.ViewGroup | ||
import androidx.fragment.app.Fragment | ||
import androidx.fragment.app.viewModels | ||
import net.pengcook.android.R | ||
import net.pengcook.android.databinding.FragmentCategoryBinding | ||
import net.pengcook.android.presentation.core.style.GridSpacingItemDecoration | ||
|
||
class CategoryFragment : Fragment() { | ||
private var _binding: FragmentCategoryBinding? = null | ||
private val binding: FragmentCategoryBinding | ||
get() = _binding!! | ||
private val viewModel: CategoryViewModel by viewModels() | ||
private val adapter: CategoryAdapter by lazy { CategoryAdapter(viewModel) } | ||
|
||
override fun onCreateView( | ||
inflater: LayoutInflater, | ||
container: ViewGroup?, | ||
savedInstanceState: Bundle?, | ||
): View { | ||
_binding = FragmentCategoryBinding.inflate(inflater, container, false) | ||
return binding.root | ||
} | ||
|
||
override fun onViewCreated( | ||
view: View, | ||
savedInstanceState: Bundle?, | ||
) { | ||
super.onViewCreated(view, savedInstanceState) | ||
binding.adapter = adapter | ||
val categories = | ||
List(17) { id -> | ||
Category( | ||
id.toLong(), | ||
"category ${id + 1}", | ||
"https://www.alphafoodie.com/wp-content/uploads/2021/06/Authentic-Kimchi-1-of-1-2.jpeg", | ||
) | ||
} | ||
adapter.submitList(categories) | ||
|
||
val spacingInPixels = resources.getDimensionPixelSize(R.dimen.item_spacing_category) | ||
binding.rvCategory.addItemDecoration(GridSpacingItemDecoration(3, spacingInPixels, false)) | ||
} | ||
|
||
override fun onDestroy() { | ||
super.onDestroy() | ||
_binding = null | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. onDestory?????????? 장난한번 쳐봤습니다ㅋㅋ |
||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
package net.pengcook.android.presentation.category | ||
|
||
import androidx.recyclerview.widget.RecyclerView | ||
import net.pengcook.android.databinding.ItemCategoryBinding | ||
|
||
class CategoryViewHolder( | ||
private val binding: ItemCategoryBinding, | ||
categoryEventListener: CategoryEventListener, | ||
) : RecyclerView.ViewHolder(binding.root) { | ||
init { | ||
binding.categoryEventListener = categoryEventListener | ||
} | ||
|
||
fun bind(category: Category) { | ||
binding.category = category | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
package net.pengcook.android.presentation.category | ||
|
||
import androidx.lifecycle.ViewModel | ||
|
||
class CategoryViewModel : ViewModel(), CategoryEventListener { | ||
override fun onNavigateToRecipesByCategory(categoryId: Long) { | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
package net.pengcook.android.presentation.core.style | ||
|
||
import android.graphics.Rect | ||
import android.view.View | ||
import androidx.recyclerview.widget.RecyclerView | ||
|
||
class GridSpacingItemDecoration( | ||
private val spanCount: Int, | ||
private val spacing: Int, | ||
private val includeEdge: Boolean, | ||
) : RecyclerView.ItemDecoration() { | ||
override fun getItemOffsets( | ||
outRect: Rect, | ||
view: View, | ||
parent: RecyclerView, | ||
state: RecyclerView.State, | ||
) { | ||
val position = parent.getChildAdapterPosition(view) | ||
val column = position % spanCount | ||
|
||
if (includeEdge) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 함수 분리 가능할까요? 좀 길군요..?ㅋㅋ 이중 if로 depth 도 커졌군요... |
||
outRect.left = spacing - column * spacing / spanCount | ||
outRect.right = (column + 1) * spacing / spanCount | ||
|
||
if (position < spanCount) { | ||
outRect.top = spacing | ||
} | ||
outRect.bottom = spacing / 2 | ||
} else { | ||
outRect.left = column * spacing / spanCount | ||
outRect.right = spacing - (column + 1) * spacing / spanCount | ||
if (position >= spanCount) { | ||
outRect.top = spacing | ||
} | ||
outRect.bottom = spacing | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
<?xml version="1.0" encoding="utf-8"?> | ||
<shape xmlns:android="http://schemas.android.com/apk/res/android" | ||
android:shape="rectangle"> | ||
<solid android:color="@android:color/transparent"/> | ||
<corners android:radius="16dp"/> | ||
</shape> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
<?xml version="1.0" encoding="utf-8"?> | ||
<shape xmlns:android="http://schemas.android.com/apk/res/android" | ||
android:shape="rectangle"> | ||
<solid android:color="@color/gray_300"/> | ||
<corners android:radius="16dp"/> | ||
<stroke android:color="@color/gray" android:width="1dp" /> | ||
</shape> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
<?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" | ||
xmlns:tools="http://schemas.android.com/tools" | ||
xmlns:bind="http://schemas.android.com/apk/res-auto"> | ||
|
||
<data> | ||
<variable | ||
name="adapter" | ||
type="net.pengcook.android.presentation.category.CategoryAdapter" /> | ||
</data> | ||
|
||
<androidx.constraintlayout.widget.ConstraintLayout | ||
android:layout_width="match_parent" | ||
android:layout_height="match_parent" | ||
tools:context=".category.CategoryFragment"> | ||
|
||
<include | ||
android:id="@+id/abl_category" | ||
layout="@layout/item_appbar_default" | ||
android:layout_width="match_parent" | ||
android:layout_height="?attr/actionBarSize" | ||
bind:title="@{@string/category_appbar}"/> | ||
|
||
<androidx.recyclerview.widget.RecyclerView | ||
android:id="@+id/rv_category" | ||
android:layout_width="match_parent" | ||
android:layout_height="0dp" | ||
android:adapter="@{adapter}" | ||
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager" | ||
app:layout_behavior="@string/appbar_scrolling_view_behavior" | ||
app:spanCount="3" | ||
tools:listitem="@layout/item_category" | ||
app:layout_constraintTop_toBottomOf="@id/abl_category" | ||
app:layout_constraintBottom_toBottomOf="parent" | ||
tools:itemCount="6" | ||
android:layout_marginHorizontal="12dp"/> | ||
|
||
</androidx.constraintlayout.widget.ConstraintLayout> | ||
</layout> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
<?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" | ||
xmlns:tools="http://schemas.android.com/tools"> | ||
|
||
<data> | ||
|
||
<variable | ||
name="category" | ||
type="net.pengcook.android.presentation.category.Category" /> | ||
|
||
<variable | ||
name="categoryEventListener" | ||
type="net.pengcook.android.presentation.category.CategoryEventListener" /> | ||
</data> | ||
|
||
<androidx.constraintlayout.widget.ConstraintLayout | ||
android:layout_width="wrap_content" | ||
android:layout_height="wrap_content" | ||
android:onClick="@{() -> categoryEventListener.onNavigateToRecipesByCategory(category.id)}"> | ||
|
||
<ImageView | ||
android:id="@+id/iv_category" | ||
android:layout_width="120dp" | ||
android:layout_height="0dp" | ||
android:background="@drawable/bg_radius_filled" | ||
android:clipToOutline="true" | ||
android:layout_margin="8dp" | ||
android:scaleType="centerCrop" | ||
app:imageUrl="@{category.imageUrl}" | ||
app:layout_constraintDimensionRatio="1:1" | ||
app:layout_constraintEnd_toEndOf="parent" | ||
app:layout_constraintStart_toStartOf="parent" | ||
app:layout_constraintTop_toTopOf="parent" | ||
tools:src="@drawable/ic_launcher_background" /> | ||
|
||
<TextView | ||
android:id="@+id/tv_category" | ||
style="@style/Body.B2" | ||
android:layout_width="0dp" | ||
android:layout_height="wrap_content" | ||
android:layout_marginTop="16dp" | ||
android:gravity="center" | ||
app:layout_constraintStart_toStartOf="parent" | ||
app:layout_constraintEnd_toEndOf="parent" | ||
app:layout_constraintTop_toBottomOf="@id/iv_category" | ||
android:text="@{category.title}" | ||
tools:text="Category" | ||
app:layout_constraintBottom_toBottomOf="parent"/> | ||
|
||
</androidx.constraintlayout.widget.ConstraintLayout> | ||
</layout> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,5 @@ | ||
<?xml version="1.0" encoding="utf-8"?> | ||
<resources> | ||
<dimen name="text_margin">16dp</dimen> | ||
</resources> | ||
<dimen name="item_spacing_category">8dp</dimen> | ||
</resources> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ListAdapter 활용 굿입니다 bb