androidx 라이브러리에 대해 다뤘던 툴바, 리사이클러뷰, 프레그먼트, 뷰페이저, 드로어를 통합적으로 다루어 보자.
메인 레이아웃에 드로어와 툴바를 구현한다. 콘텐츠 영역에는 프레그먼트를 통해 뷰 페이저를 구현한다. 첫번째 프레그먼트는 리사이클러뷰에 콘텐츠를 표시하도록 한다.
뷰 바인딩만 되도록 하고, 그 외에 implementation 'com.google.android.material:material:1.4.0'
의존성이 있다면 따로 의존성을 추가할 필요가 없다.
root 레이아웃을 드로어 레이아웃으로 두어서 첫번째 뷰 그룹에 툴바와 뷰 페이저를 두었다. 두번째는 텍스트 뷰를 두어서 드로어에서 등장하도록 하였다.
<?xml version="1.0" encoding="utf-8"?>
<androidx.drawerlayout.widget.DrawerLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/drawer"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/colorPrimary"/>
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/viewpager"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
<TextView
android:layout_width="300dp"
android:layout_height="match_parent"
android:background="#FF0000"
android:text="I am Drawer!!"
android:textSize="20dp"
android:textStyle="bold"
android:textColor="#FFFFFF"
android:gravity="center_horizontal"
android:fitsSystemWindows="true"
android:layout_gravity="start"/>
</androidx.drawerlayout.widget.DrawerLayout>
드로어 레이아웃이 리사이클링 뷰를 갖도록 해보고 싶었는데, 직접적으로 그렇게 만들면 제대로 기능을 하지 못함.
res/menu 에 meni_main.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">
<item android:id="@+id/menu_search"
android:title="search"
app:showAsAction="always"
app:actionViewClass="androidx.appcompat.widget.SearchView"
/>
</menu>
첫번째 프레그먼트는 리사이클러 뷰만으로 정의하여 리아시클러 뷰를 담는 컨테이너로서 역할을 하고,
<?xml version="1.0" encoding="utf-8"?>
<androidx.recyclerview.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
</androidx.recyclerview.widget.RecyclerView>
리사이클러 뷰의 아이템을 가질 아이템 레이아웃을 따로 정의한다.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
android:id="@+id/item_root"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="16dp"
android:layout_margin="8dp">
<TextView
android:id="@+id/item_data"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
android:textStyle="bold"
android:textSize="16dp"
/>
</LinearLayout>
그외의 프레그먼트들은 단순히 레이아웃을 스크린에 표시하는 용도의 역할만 하므로 원하는 대로 만들면 된다.
class MainActivity : AppCompatActivity() {
lateinit var toggle: ActionBarDrawerToggle
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
setSupportActionBar(binding.toolbar)
//ActionBarDrawerToggle 버튼 적용
toggle = ActionBarDrawerToggle(this, binding.drawer, R.string.drawer_opened, R.string.drawer_closed)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
toggle.syncState()
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
val inflater = menuInflater
inflater.inflate(R.menu.menu_main, menu)
//MenuItem 객체를 획득하고 그 안에 포함된 ActionView 객체 획득
val menuItem = menu.findItem(R.id.menu_search)
val searchView=menuItem.actionView as SearchView
searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
override fun onQueryTextChange(newText: String?): Boolean {
//검색어 변경 이벤트
return true
}
override fun onQueryTextSubmit(query: String?): Boolean {
//검색을 위해 키보드의 검색 버튼을 클릭한 순간의 이벤트
Log.d("kkang","search text : $query")
return true
}
})
return true
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
//이벤트가 toggle 버튼에서 제공된거라면..
if(toggle.onOptionsItemSelected(item)){
return true
}
return super.onOptionsItemSelected(item)
}
}
첫번째 프레그먼트는 리사이클러 뷰를 이용하기 때문에 뷰홀더와 뷰 어댑터를 만들고 프레그먼트에서 이를 적용하도록 해야한다. 추가로 데코레이션까지 하자. 데코레이션의 onDrawOver 함수에서 그림을 가져오고 있는데, 이 그림은 BitmapFactory 클래스가 decode 할 수 있는 리소스면 아무거나 괜찮다.
// 리사이클러 뷰의 항목(아이템)을 담을 레이아웃을 뷰 홀더에서 갖도록 한다.
class MyViewHolder(val binding: ItemRecyclerviewBinding): RecyclerView.ViewHolder(binding.root)
// 뷰 홀더를 이용하여 뷰어댑터를 만든다.
class MyAdapter(val datas: MutableList<String>) : RecyclerView.Adapter<RecyclerView.ViewHolder> () {
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val binding = (holder as MyViewHolder).binding
binding.itemData.text = datas[position]
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder = MyViewHolder(
ItemRecyclerviewBinding.inflate(LayoutInflater.from(parent.context), parent, false))
override fun getItemCount(): Int = datas.size
}
class MyDecoration(val context: Context) : RecyclerView.ItemDecoration() {
override fun onDrawOver(c: Canvas, parent: RecyclerView, state: RecyclerView.State) {
super.onDrawOver(c, parent, state)
val width = parent.width
val height = parent.height
val dr: Drawable? = ResourcesCompat.getDrawable(context.resources, R.drawable.kbo, null)
val drWidth = dr?.intrinsicWidth
val drHeight = dr?.intrinsicHeight
val left = width / 2 - drWidth?.div(2) as Int
val top = height /2 -drHeight?.div(2) as Int
c.drawBitmap(BitmapFactory.decodeResource(context.resources, R.drawable.kbo),
left.toFloat(), top.toFloat(), null)
}
override fun getItemOffsets(
outRect: Rect,
view: View,
parent: RecyclerView,
state: RecyclerView.State
) {
super.getItemOffsets(outRect, view, parent, state)
val index = parent.getChildAdapterPosition(view) + 1
if (index % 3 == 0)
outRect.set(10, 10, 10, 50)
else
outRect.set(10,10,10,0 )
view.setBackgroundColor(Color.parseColor("#28A0FF"))
ViewCompat.setElevation(view, 20.0f)
}
}
뷰어댑터
뷰데코레이션
프레그먼트 코드에서는 액티비티에서 리사이클러 뷰를 적용했던 것 처럼, onCreateView 함수 내부에서 뷰 어댑터를 구현하고, 레이아웃 매니저를 적용해주는 등의 작업을 해야한다. 그리고 그리려고 하는 View 를 반환해주면 된다.
class OneFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val binding = FragmentOneBinding.inflate(inflater, container, false)
val datas = mutableListOf<String>()
for (i in 1..9) {
datas.add("Item $i")
}
val layoutManager = LinearLayoutManager(activity)
binding.recyclerView.layoutManager = layoutManager
val adapter = MyAdapter(datas)
binding.recyclerView.adapter = adapter
binding.recyclerView.addItemDecoration(MyDecoration(activity as Context))
return binding.root
}
}
그냥 그리고자 하는 UI view 를 반환해주면 된다.
class TwoFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return FragmentTwoBinding.inflate(inflater, container, false).root
}
}
뷰 페이저를 프레그먼트를 통해 구현하려면 FragmentStateAdapter 를 상속하는 어댑터를 만들어야 한다.
class MyFragmentPagerAdapter(activity: FragmentActivity): FragmentStateAdapter(activity){
val fragments: List<Fragment>
init {
fragments= listOf(OneFragment(), TwoFragment(), ThreeFragment())
}
override fun getItemCount(): Int = fragments.size
override fun createFragment(position: Int): Fragment = fragments[position]
}
이전에 만든 툴바가 액티비티 코드에 포함되어 있을 것이다. 이제 여기에 뷰 페이저에 어댑터를 더하는 작업을 해줘야한다.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
setSupportActionBar(binding.toolbar)
toggle = ActionBarDrawerToggle(this, binding.drawer, R.string.drawer_opened, R.string.drawer_closed)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
toggle.syncState()
//ViewPager 에 Adapter 적용
val adapter=MyFragmentPagerAdapter(this)
binding.viewpager.adapter = adapter
}