activity_creature.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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="match_parent"
android:layout_height="match_parent"
tools:context="com.raywenderlich.android.creatures.ui.CreatureActivity">
<!-- 中略 -->
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/foodRecyclerView"
android:layout_width="0dp"
android:layout_height="@dimen/list_item_food_height"
android:layout_marginTop="@dimen/padding_standard"
android:layout_marginBottom="@dimen/padding_standard"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/planet" />
</androidx.constraintlayout.widget.ConstraintLayout>
list_item_food.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="@dimen/list_item_food_height"
android:layout_height="@dimen/list_item_food_height"
xmlns:app="http://schemas.android.com/apk/res-auto">
<ImageView
android:id="@+id/foodImage"
android:layout_width="@dimen/list_item_food_height"
android:layout_height="@dimen/list_item_food_height"
android:contentDescription="@string/content_description_food_image"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:srcCompat="@drawable/food_banana"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
FoodAdapter
class FoodAdapter(private val foods: MutableList<Food>) : RecyclerView.Adapter<FoodAdapter.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FoodAdapter.ViewHolder {
return ViewHolder(parent.inflate(R.layout.list_item_food))
}
override fun getItemCount() = foods.size
override fun onBindViewHolder(holder: FoodAdapter.ViewHolder, position: Int) {
holder.bind(foods[position])
}
fun updateFoods(foods: List<Food>) {
this.foods.clear()
this.foods.addAll(foods)
notifyDataSetChanged()
}
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
private lateinit var food: Food
fun bind(food: Food) {
this.food = food
val context = itemView.context
itemView.foodImage.setImageResource(
context.resources.getIdentifier(food.thumbnail, null, context.packageName)
)
}
}
}
CreatureActivity.kt
class CreatureActivity : AppCompatActivity() {
private lateinit var creature: Creature
private val adapter = FoodAdapter(mutableListOf())
companion object {
private const val EXTRA_CREATURE_ID = "EXTRA_CREATURE_ID"
fun newIntent(context: Context, creatureId: Int): Intent {
val intent = Intent(context, CreatureActivity::class.java)
intent.putExtra(EXTRA_CREATURE_ID, creatureId)
return intent
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_creature)
setupCreature()
setupTitle()
setupViews()
setupFavoriteButton()
setupFoods()
}
// 中略
private fun setupFoods(){
foodRecyclerView.layoutManager = LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false)
foodRecyclerView.adapter = adapter
val foods = CreatureStore.getCreatureFoods(creature)
adapter.updateFoods(foods)
}
}
Food.kt
data class Food(val id: Int, val name: String, val image: String) {
val thumbnail: String
get() = "drawable/thumbnail_$image"
}
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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="match_parent"
android:layout_height="@dimen/list_item_creature_height">
<ImageView
android:id="@+id/creatureImage"
android:background="@color/colorAccent"
android:layout_width="@dimen/list_item_creature_height"
android:layout_height="@dimen/list_item_creature_height"
android:contentDescription="@string/content_description_creature_image"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/creature_cat_derp" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/foodRecyclerView"
android:layout_width="0dp"
android:layout_height="@dimen/list_item_creature_height"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/creatureImage"
app:layout_constraintTop_toTopOf="parent"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
CreatureWithFoodAdapter
class CreatureWithFoodAdapter(private val creatures: MutableList<Creature>): RecyclerView.Adapter<CreatureWithFoodAdapter.ViewHolder>() {
private val viewPool = RecyclerView.RecycledViewPool()
class ViewHolder(itemView: View) : View.OnClickListener, RecyclerView.ViewHolder(itemView) {
private lateinit var creature: Creature
private val adapter = FoodAdapter(mutableListOf())
init {
itemView.setOnClickListener(this)
}
fun bind(creature: Creature) {
this.creature = creature
val context = itemView.context
itemView.creatureImage.setImageResource(
context.resources.getIdentifier(creature.uri, null, context.packageName))
setupFoods()
}
override fun onClick(view: View?) {
view?.let {
val context = it.context
val intent = CreatureActivity.newIntent(context, creature.id)
context.startActivity(intent)
}
}
private fun setupFoods() {
itemView.foodRecyclerView.layoutManager =
LinearLayoutManager(itemView.context, LinearLayoutManager.HORIZONTAL, false)
itemView.foodRecyclerView.adapter = adapter
val foods = CreatureStore.getCreatureFoods(creature)
adapter.updateFoods(foods)
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
// return ViewHolder(parent.inflate(R.layout.list_item_creature_with_food))
val holder = ViewHolder(parent.inflate(R.layout.list_item_creature_with_food))
holder.itemView.foodRecyclerView.setRecycledViewPool(viewPool)
return holder
}
override fun getItemCount() = creatures.size
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(creatures[position])
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
// return ViewHolder(parent.inflate(R.layout.list_item_creature_with_food))
val holder = ViewHolder(parent.inflate(R.layout.list_item_creature_with_food))
holder.itemView.foodRecyclerView.setRecycledViewPool(viewPool)
LinearSnapHelper().attachToRecyclerView(holder.itemView.foodRecyclerView)
return holder
}
list_item_creature_card.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:card_view="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/creatureCard"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="@dimen/padding_half"
card_view:cardCornerRadius="@dimen/card_corner_radius"
card_view:cardElevation="@dimen/card_elevation">
<ImageView
android:id="@+id/creatureImage"
android:layout_width="@dimen/card_image_size"
android:layout_height="@dimen/card_image_size"
android:layout_gravity="center"
android:contentDescription="@string/content_description_creature_image"
android:scaleType="fitXY"
tools:srcCompat="@drawable/creature_cat_derp" />
<LinearLayout
android:id="@+id/cardRipple"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?android:selectableItemBackground"
android:orientation="horizontal" />
<LinearLayout
android:id="@+id/nicknameHolder"
android:layout_width="match_parent"
android:layout_height="@dimen/card_nickname_holder_height"
android:layout_gravity="bottom"
android:alpha="0.9"
android:background="@color/colorAccent"
android:orientation="horizontal">
<TextView
android:id="@+id/cardNickname"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:gravity="center_horizontal"
android:paddingStart="@dimen/padding_half"
android:paddingEnd="@dimen/padding_half"
android:textAppearance="?android:attr/textAppearanceLarge"
android:textColor="@android:color/white"
android:textStyle="bold" />
</LinearLayout>
</androidx.cardview.widget.CardView>
CreatureCardAdapter
class CreatureCardAdapter(private val creatures: MutableList<Creature>): RecyclerView.Adapter<CreatureCardAdapter.ViewHolder>() {
class ViewHolder(itemView: View) : View.OnClickListener, RecyclerView.ViewHolder(itemView) {
private lateinit var creature: Creature
init {
itemView.setOnClickListener(this)
}
fun bind(creature: Creature) {
this.creature = creature
val context = itemView.context
val imageResource = context.resources.getIdentifier(creature.uri, null, context.packageName)
itemView.creatureImage.setImageResource(imageResource)
itemView.cardNickname.text = creature.nickname
setBackgroundColors(context, imageResource)
}
override fun onClick(view: View?) {
view?.let {
val context = it.context
val intent = CreatureActivity.newIntent(context, creature.id)
context.startActivity(intent)
}
}
private fun setBackgroundColors(context: Context, imageResource: Int) {
val image = BitmapFactory.decodeResource(context.resources, imageResource)
Palette.from(image).generate { palette ->
palette?.let {
val backgroundColor = it.getDominantColor(ContextCompat.getColor(context, R.color.colorPrimaryDark))
itemView.creatureCard.setBackgroundColor(backgroundColor)
itemView.nicknameHolder.setBackgroundColor(backgroundColor)
val textColor = if (isColorDark(backgroundColor)) Color.WHITE else Color.BLACK
itemView.cardNickname.setTextColor(textColor)
}
}
}
private fun isColorDark(color: Int) : Boolean {
val darkness = 1 - (0.299 * Color.red(color) +
0.587 * Color.green(color) +
0.114 * Color.blue(color) / 255)
return darkness >= 0.5
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder(parent.inflate(R.layout.list_item_creature_card))
}
override fun getItemCount() = creatures.size
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(creatures[position])
}
}
AllFragment
class AllFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val layoutManager = GridLayoutManager(activity, 2, GridLayoutManager.VERTICAL, false)
layoutManager.spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() {
override fun getSpanSize(position: Int): Int {
return if ((position + 1) % 3 == 0) 2 else 1
}
}
creatureRecyclerView.layoutManager = layoutManager
creatureRecyclerView.adapter = adapter
}
AllFragment
class AllFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val layoutManager = GridLayoutManager(activity, 3, GridLayoutManager.VERTICAL, false)
layoutManager.spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() {
override fun getSpanSize(position: Int): Int {
return if ((position + 1) % 7 == 0) 3 else 1
}
}
creatureRecyclerView.layoutManager = layoutManager
creatureRecyclerView.adapter = adapter
}
AllFragment
class AllFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val layoutManager = StaggeredGridLayoutManager(2, GridLayoutManager.VERTICAL)
creatureRecyclerView.layoutManager = layoutManager
creatureRecyclerView.adapter = adapter
}
list_item_creature_card.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:card_view="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/creatureCard"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="@dimen/padding_half"
card_view:cardCornerRadius="@dimen/card_corner_radius"
card_view:cardElevation="@dimen/card_elevation">
<LinearLayout
android:id="@+id/cardRipple"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?android:selectableItemBackground"
android:orientation="horizontal" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<ImageView
android:id="@+id/creatureImage"
android:layout_width="@dimen/card_image_size"
android:layout_height="@dimen/card_image_size"
android:layout_gravity="center"
android:contentDescription="@string/content_description_creature_image"
android:scaleType="fitXY"
tools:srcCompat="@drawable/creature_cat_derp" />
<LinearLayout
android:id="@+id/nameHolder"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:alpha="0.9"
android:background="@color/colorAccent"
android:orientation="horizontal">
<TextView
android:id="@+id/cardFullName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:gravity="center_horizontal"
android:maxLines="3"
android:padding="@dimen/padding_half"
android:textColor="@android:color/white"
android:textSize="@dimen/creature_card_text_size"
android:textStyle="bold" />
</LinearLayout>
</LinearLayout>
</androidx.cardview.widget.CardView>
menu_all.xml
<?xml version="1.0" encoding="utf-8"?>
<menu 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"
tools:context=".MainActivity">
<item
android:id="@+id/action_span_1"
android:orderInCategory="100"
android:title="@string/menu_span_1"
app:showAsAction="never" />
<item
android:id="@+id/action_span_2"
android:orderInCategory="100"
android:title="@string/menu_span_2"
app:showAsAction="never" />
</menu>
AllFragment
class AllFragment : Fragment() {
private val adapter = CreatureCardAdapter(CreatureStore.getCreatures().toMutableList())
private lateinit var layoutManager: StaggeredGridLayoutManager
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
layoutManager = StaggeredGridLayoutManager(2, GridLayoutManager.VERTICAL)
creatureRecyclerView.layoutManager = layoutManager
creatureRecyclerView.adapter = adapter
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
val id = item.itemId
when (id) {
R.id.action_span_1 -> {
showListView()
return true
}
R.id.action_span_2 -> {
showGridView()
return true
}
}
return super.onOptionsItemSelected(item)
}
private fun showListView() {
layoutManager.spanCount = 1
}
private fun showGridView() {
layoutManager.spanCount = 2
}