개발노트/Kotlin

5주차 3일 TapLayout과 Viewpager2,RecyclearView

시계속세상은아직돌아가는중 2023. 8. 9. 20:01

1.TapLayOut&ViewPager2

<androidx.viewpager2.widget.ViewPager2
        xmlns:android="http://schemas.android.com/apk/res/android" //중요! 이 부분이 꼭 필요
        android:id="@+id/viewPage2"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/tabLayout"/>

 

TapLayout의 구성은 이러한데, 여기서 주의해야할 것은 TabItem 속 text는 진짜 그려지는게 아니라 레이아웃 구성상 보기 편하도록 나오는 것이다.

 

재밌는 것은 adapter에서 size를 주면 아이템 선언은 내가 구성한 레이아웃 상에서는 필요 없다는 것이다.

fragment나 activicy에서 따로 속성을 지정해줄 수 있기 때문인데, 이러한 상황이 아니라면 item을 줘서 제어가 가능하다.

 

해당 TapLayout과 ViewPager2는

 

각 탭의 Fragment를 생성한 뒤 연결되었는데,

 

package com.example.class1

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 androidx.recyclerview.widget.RecyclerView

// TODO: Rename parameter arguments, choose names that match
// the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
private const val ARG_PARAM1 = "param1"
private const val ARG_PARAM2 = "param2"

/**
 * A simple [Fragment] subclass.
 * Use the [TodoFragment.newInstance] factory method to
 * create an instance of this fragment.
 */
class TodoFragment : Fragment() {
    // TODO: Rename and change types of parameters
    private var param1: String? = null
    private var param2: String? = null



    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        arguments?.let {
            param1 = it.getString(ARG_PARAM1)
            param2 = it.getString(ARG_PARAM2)
        }
    }

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        val rootView = inflater.inflate(R.layout.fragment_todo, container, false)
        val recyclerView = rootView.findViewById<RecyclerView>(R.id.todo_recyclear)
        var todoList = arrayListOf<Todoitem>()

        for(i in 1 .. 100) {
            todoList.add(Todoitem("todo ${i}"))
        }
        val todoAdapter=TodoAdapter(todoList)
        recyclerView.adapter = todoAdapter
        recyclerView.layoutManager = LinearLayoutManager(requireContext())
        return rootView
    }

    companion object {
        /**
         * Use this factory method to create a new instance of
         * this fragment using the provided parameters.
         *
         * @param param1 Parameter 1.
         * @param param2 Parameter 2.
         * @return A new instance of fragment Viewpager2Fragment.
         */
        // TODO: Rename and change types and number of parameters
        @JvmStatic
        fun newInstance(param1: String, param2: String) =
            TodoFragment().apply {
                arguments = Bundle().apply {
                    putString(ARG_PARAM1, param1)
                    putString(ARG_PARAM2, param2)
                }
            }
    }
}

 

해당 Fragment가 Todo탭의 fragment다.

 

onCreate Fragment가 생성될 때 마다 호출.
초기화 인자를 가져옴
onCreateView() Fragment에서 레이아웃 인플레이트를 하고 UI구성요소를 설정함
newInstance() 지정 매개 변수로 Fragment의 새 인스터를 생성하는 보조 메서드. 인자를 전달하는게 일반적인 형태

 

여기서 주의해야할 점은 OnCreateView는 Activity.kt와 다른 역활을 한다는거다.

비슷한 역활을 하나 생명 주기가 다른데, OnCreateView는 fragment의 생명 주기를 따른다.

또한 OnCreateView는 View 인스턴스를 반환해야하며, 이 반환한 View는 Fragment의 UI를 구성하는데 사용된다.

 

class FragmentAdapter(fragmentActivity: FragmentActivity) : FragmentStateAdapter(fragmentActivity) {

    override fun getItemCount(): Int {
        return 2
    }

    override fun createFragment(position: Int): Fragment {
        return when (position) {
            0 -> TodoFragment()
            else -> BookMarkFragment()
        }
    }

}

이 Adapter는 위에 생성된 framgent 두 개와 ViewPager2를 연결하는 adpter다.

 

getItemCount() 표시할 fragment의 수를 반환
createFragment() 지정 위치의 fragment를 생성하고 반환

 

package com.example.class1

import android.annotation.SuppressLint
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.viewpager2.widget.ViewPager2
import com.google.android.material.tabs.TabLayout
import com.google.android.material.tabs.TabLayoutMediator

class MainActivity : AppCompatActivity() {
    @SuppressLint("MissingInflatedId")

    val title = listOf("Todo","Bookmark")

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val viewPageer2:ViewPager2 = findViewById(R.id.viewPage2)
        val tapLayout: TabLayout = findViewById(R.id.tabLayout)

        val adapter = FragmentAdapter(this)
        viewPageer2.adapter = adapter

        TabLayoutMediator(tapLayout, viewPageer2){ tab,position->
                tab.text = title[position]
        }.attach()

    }
}

 이렇게 만들어준 adapter를 이용하여 MainActivity에서 해당 기능을 연결해준다.

먼저 viewpager2와 taplayout을 호출한 뒤 viewpager.adapter를 FragmentAdapter와 연결해준다.

 

이후 TapLayoutMediator를 이용하여 위에 저장된 리스트에서 타이틀 이름을 가져와 그려준다.

이 방식은 튜터님께 질의하여 앞으로 다른 사람과 협업할 때 어느 모습이 더 편한가에 대해서 질문하여 나온 형태다.

 

2. TapLayout & ViewPager2 & RecyclerView

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".TodoFragment">

    <!-- TODO: Update blank fragment layout -->
    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/todo_recyclear"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>

</FrameLayout>

해당 Fragment는 Todo의 Fragment를 구성한 요소로, recyclerview를 가진다.

그리고 recycleview는 데이터를 받아올 아이템이 필요한데 이는

 

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content">

    <TextView
        android:id="@+id/texttodo"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="todo"
        android:textSize="20dp"
        android:layout_marginTop="10dp"
        android:layout_marginBottom="10dp"/>

</LinearLayout>

해당 item_todo 레이아웃이다.

 

그렇다면 이제 남은 것은 리사이클 뷰와 프레그먼트를 연결하는 것이다.

 

package com.example.class1

import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import org.w3c.dom.Text

class TodoAdapter(val todoList: ArrayList<Todoitem>) :
    RecyclerView.Adapter<TodoAdapter.TodoHolder>() {
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TodoAdapter.TodoHolder {
        val view = LayoutInflater.from(parent.context).inflate(R.layout.item_todo, parent, false)
        return TodoHolder(view)
    }

    override fun onBindViewHolder(holder: TodoAdapter.TodoHolder, position: Int) {
        holder.todo.text = todoList[position].todo
    }

    override fun getItemCount(): Int {
        return todoList.count()
    }

    inner class TodoHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        val todo = itemView.findViewById<TextView>(R.id.texttodo)
    }
}
package com.example.class1

data class Todoitem(var todo:String)

해당 adapter는 recyclerview와 item todo를 이어주기 위한 어뎁터로

 

onCreateViewHolder 뷰가 만들어질 때 호출되는 메소드
레이아웃을 인플레이트해서 뷰 홀더를 리턴함
onBindViewHolder 뷰가 바인드될 때 호출되는 메소드 .
데이터 소스에서 가져온 데이터를 사용하여 아이템의 뷰에 값을 설정함
getItemCount 표시할 아이템의 갯수

+ TodoHolder라는 inner class를 선언해서 Holder를 직접 설정해주었다.

 

이런 과정을 거친 뒤

 

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        val rootView = inflater.inflate(R.layout.fragment_todo, container, false)
        val recyclerView = rootView.findViewById<RecyclerView>(R.id.todo_recyclear)
        var todoList = arrayListOf<Todoitem>()

        for(i in 1 .. 100) {
            todoList.add(Todoitem("todo ${i}"))
        }
        val todoAdapter=TodoAdapter(todoList)
        recyclerView.adapter = todoAdapter
        recyclerView.layoutManager = LinearLayoutManager(requireContext())
        return rootView
    }

TodoFragment로 돌아와서 onCreateView에서 해당 UI를 그려주도록 한다.

fragment의 레이아웃을 가져온 뒤 recycleview를 불러온다.

 

이후 recyclearview를 adapter를 통해서 item과 연결해준 뒤

item에 있는 textview에 todoList에 있는 값들을 넣어 출력하도록 한다.

 

이후 rootView를 return하여 해당 UI가 fragment를 호출했을 때 보일 수 있도록 한다.

 

결과물

 

 

참고 블로그

https://hanyeop.tistory.com/196

 

[Android] ViewPager2와 TabLayout 사용하여 레이아웃 만들기

탭과 뷰페이저를 사용하면 스와이프해서 다른 프래그먼트를 볼 수 있는 탭을 만들 수 있다. 사용해보기 종속성 추가 android { buildFeatures { viewBinding true } } dependencies { // 뷰페이저 2 implementation "andro

hanyeop.tistory.com

https://uknowblog.tistory.com/29

 

[안드로이드/코틀린] 리사이클러뷰(RecyclerView) 사용법 및 예제

리사이클러뷰(RecyclerView) 리사이클러뷰는 안드로이드에서 리스트를 만들기 위해 사용되는 뷰 입니다. 리스트를 만들기 위해 사용된다는 점에서 리스트뷰(ListView)와 비슷하지만, 뷰를 재활용(Recyc

uknowblog.tistory.com

https://developer.android.com/guide/topics/ui/layout/recyclerview?hl=ko

 

RecyclerView로 동적 목록 만들기  |  Android 개발자  |  Android Developers

컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. RecyclerView로 동적 목록 만들기   Android Jetpack의 구성요소 RecyclerView를 사용하면 대량의 데이터 세트를 효율적

developer.android.com

https://developer.android.com/guide/navigation/navigation-swipe-view-2?hl=ko

 

ViewPager2를 사용하여 탭으로 스와이프 뷰 만들기  |  Android 개발자  |  Android Developers

ViewPager2를 사용하여 탭으로 스와이프 뷰 만들기 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. 스와이프 뷰를 사용하면 손가락의 가로 동작이나 스와이프

developer.android.com

 

오늘 푼 문제

https://clockstillticktockticktock.tistory.com/48

 

프로그래머스 영어가 싫어요 코틀린

1.문제 https://school.programmers.co.kr/learn/courses/30/lessons/120894 프로그래머스 코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이

clockstillticktockticktock.tistory.com