Android - ViewPager2, RecyclerView , NavigationLayout 사용하기
이번 실습은 ViewPager2와 NavigationLayout과 RecyclerView를 이용한 앱을 만들어 보았다.
간단하게 Home, Add, Profile 3개의 Fragment를 만들어주고 HomeFragment에만 RecyclerVeiw를 적용시켜 보았다.
1. BottomNavigationBar 생성
ㅁ BottomNavigationBar 를 만들어 준뒤 ViewPager2와 연결시켜준다. 그 이유는 ViewPager로 화면을 넘길때 BottomNavigationBar도 같이 넘어가야 하고 그 반대도 적용돼야하기 때문이다.
MainActivty Xml은 ViewPager와 BottomNavigation으로 만들어준다.
그리고 res -> menu -> bottomnavi.xml에 item을 추가시켜준다
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/home"
android:title="Home"
android:icon="@drawable/home">
</item>
<item
android:id="@+id/add"
android:title="Add"
android:icon="@drawable/add"
>
</item>
<item
android:id="@+id/profile"
android:title="Profile"
android:icon="@drawable/profile"
>
</item>
</menu>
이러면 3개의 아이콘을 가진 Item이 추가됐다. 그대로 MainActivity.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=".MainActivity">
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/viewPager2"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="@id/bottomNavi"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:layout_width="match_parent"
android:layout_height="0dp"/>
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/bottomNavi"
android:layout_width="match_parent"
android:layout_height="64dp"
app:menu="@menu/bottom_navi"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
app:menu=""에 만들어주었던 menu item을 넣어준다.
2. framgent 생성하기
3개의 fragmentItem을 만들어 주었으니 3개의 fragment를 생성시켜준다.
여기서 3개의 fragment를 생성해주면 된다. 그러면 fragment_layout도 자동으로 생성이 된다.
3. Viewpager2 연동하기
먼저 viewPagerApdapter를 만들어주어야 model로 정의된 값을 adpater를 통해 보여질 수 있다.
package com.example.viewpagerpracticeagain
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.viewpager2.adapter.FragmentStateAdapter
import com.example.viewpagerpracticeagain.fragment.AddFragment
import com.example.viewpagerpracticeagain.fragment.HomeFragment
import com.example.viewpagerpracticeagain.fragment.ProfileFragment
class viewPagerAdapter(fragment : FragmentActivity) : FragmentStateAdapter(fragment) {
override fun getItemCount(): Int = 3
override fun createFragment(position: Int): Fragment {
return when(position){
0 -> HomeFragment()
1 -> AddFragment()
else -> ProfileFragment()
}
}
}
viewpagerAdapter는 fragmentActivity를 받고 그 activty를 FragmentStateAdapter(fragmet)에 집어넣은 값을 상속시켜준다.
4. MainActivity 설정
package com.example.viewpagerpracticeagain
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.LayoutInflater
import android.view.MenuItem
import androidx.viewpager2.widget.ViewPager2
import com.example.viewpagerpracticeagain.databinding.ActivityMainBinding
import com.google.android.material.bottomnavigation.BottomNavigationView
class MainActivity : AppCompatActivity(), BottomNavigationView.OnNavigationItemSelectedListener {
private lateinit var binding : ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.viewPager2.adapter = viewPagerAdapter(this)
binding.viewPager2.registerOnPageChangeCallback(
object : ViewPager2.OnPageChangeCallback() {
override fun onPageSelected(position: Int) {
super.onPageSelected(position)
binding.bottomNavi.menu.getItem(position).isChecked = true
}
}
)
binding.bottomNavi.setOnItemSelectedListener(this)
}
override fun onNavigationItemSelected(item: MenuItem): Boolean {
when(item.itemId){
R.id.home -> {
binding.viewPager2.currentItem = 0
return true
}
R.id.add ->{
binding.viewPager2.currentItem = 1
return true
}
R.id.profile -> {
binding.viewPager2.currentItem = 2
return true
}
else ->{
return false
}
}
}
}
viewpager에 만들어준 어댑터를 등록시키고 bottomnavigation을 onNavigationItemSelected으로 itemId마다 다른 currentItem을 주어 viewpager2와 연동시켜준다.
5. RecyclerView 설정
recyclerView를 만들기 위해서는 Model, recyclerview를 담을 xml 그리고 가장중요한 recyclerview Adapter가 필요하다.
1. 모델 data class 생성
package com.example.viewpagerpracticeagain.model
data class Model(
val sellerId: String,
val title: String,
val createAt : String,
val price: String,
val imageUrl: String
){
constructor(): this("","","","","")
}
2. recyclerView 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"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="16dp"
android:paddingTop="16dp"
android:paddingEnd="16dp">
<ImageView
android:id="@+id/thumbnailImageView"
android:layout_width="110dp"
android:layout_height="110dp"
android:layout_marginBottom="16dp"
android:scaleType="center"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/titleTextView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:maxLines="2"
android:textColor="@color/black"
android:textSize="16sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/thumbnailImageView"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/dateTextView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@id/titleTextView"
app:layout_constraintTop_toBottomOf="@id/titleTextView" />
<TextView
android:id="@+id/priceTextView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
android:textColor="@color/black"
android:textSize="15sp"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@id/titleTextView"
app:layout_constraintTop_toBottomOf="@id/dateTextView" />
<View
android:layout_width="0dp"
android:layout_height="1dp"
android:background="@color/teal_200"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
3. recyclerViewAdapter 생성
package com.example.viewpagerpracticeagain
import android.content.Context
import android.view.LayoutInflater
import android.view.ViewGroup
import android.widget.Toast
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
import com.example.viewpagerpracticeagain.databinding.ItemArticleBinding
import com.example.viewpagerpracticeagain.model.Model
class RecyclerViewAdapter(val onItemClicked : (Model) -> Unit) : ListAdapter<Model, RecyclerViewAdapter.ViewHolder>(diffUtil) {
inner class ViewHolder(private val binding: ItemArticleBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(model: Model){
binding.titleTextView.text = model.title
binding.dateTextView.text = model.createAt
binding.priceTextView.text = model.price
if (model.imageUrl.isNotEmpty()) {
Glide.with(binding.thumbnailImageView)
.load(model.imageUrl)
.into(binding.thumbnailImageView)
}
binding.root.setOnClickListener {
onItemClicked(model)
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = ItemArticleBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
return holder.bind(currentList[position])
}
companion object{
val diffUtil = object : DiffUtil.ItemCallback<Model>(){
override fun areItemsTheSame(oldItem: Model, newItem: Model): Boolean {
return oldItem.createAt == newItem.createAt
}
override fun areContentsTheSame(oldItem: Model, newItem: Model): Boolean {
return oldItem == newItem
}
}
}
}
Diffutill을 통해 동일한 객체의 출력을 막아준다.
1) onCreateViewHolder 2) onBindViewHolder 3) ViewHolder 실행 순서이다.
4. HomeFragment.activity 최종 완료
package com.example.viewpagerpracticeagain.fragment
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.LinearLayoutManager
import com.example.viewpagerpracticeagain.R
import com.example.viewpagerpracticeagain.RecyclerViewAdapter
import com.example.viewpagerpracticeagain.databinding.FragmentHomeBinding
import com.example.viewpagerpracticeagain.model.Model
import com.google.android.material.snackbar.Snackbar
class HomeFragment : Fragment(R.layout.fragment_home) {
private val articleList = mutableListOf<Model>()
private lateinit var recyclerViewAdapter: RecyclerViewAdapter
private var binding : FragmentHomeBinding? = null
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val fragmentHomeBinding = FragmentHomeBinding.bind(view)
binding = fragmentHomeBinding
initialize()
refreshRecyclerView(view)
}
private fun initialize(){
for (num in 1..100){
val articleModel = Model("user${num}", "title${num}", "${num}", "10${num}", "http://picsum.photos/536/354")
articleList.add(articleModel)
}
}
private fun refreshRecyclerView(view: View) {
recyclerViewAdapter = RecyclerViewAdapter(onItemClicked = {
articleModel -> if (articleModel != null){
Snackbar.make(view, "${articleModel.sellerId}입니다.", Snackbar.LENGTH_SHORT).show()
}
})
recyclerViewAdapter.submitList(articleList)
binding!!.itemRecyclerView.adapter = recyclerViewAdapter
binding!!.itemRecyclerView.layoutManager = LinearLayoutManager(context)
}
}
반복문을 통해 articleModel에 100개의 model을 넘어주었다. 그리고 클릭리스너 또한 람다식으로 Adpater에 입력 받게하였으므로 간단하게 snackbar형태로 클릭하면 Id가 나오도록 만들어주었다.