Android androidx: 통합 실습

timothy jeong·2021년 11월 10일
0

Android with Kotlin

목록 보기
29/69

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)
    }
}

뷰어댑터

  • getItemCount : 뷰 홀더에 있는 뷰를 몇개 만들지
  • onCreateViewHolder : 사용될 뷰 홀더
  • onBindViewHolder : 뷰 홀더에 바인딩될 데이터 관리

뷰데코레이션

  • onDrawOver : Draw 함수가 호출되고, Item 이 다 그려진 후 캔버스에 그린게 화면에 출력된다.
  • getItemOffset : 아이템의 여백을 설정한다.

프레그먼트 코드

프레그먼트 코드에서는 액티비티에서 리사이클러 뷰를 적용했던 것 처럼, 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
    }

profile
개발자

0개의 댓글