SPECIALIZED/Android

11. 제트팩 라이브러리

Tiny Commit 2025. 5. 6. 01:34

 

 

 

 

 

 

0. 준비

1. res - values - string.xml

<resources>
    <string name="app_name">MyProject</string>
    <string name="drawer_opened">Opened Drawer</string>
    <string name="drawer_closed">Closed Drawer</string>
    <!-- TODO: Remove or change this placeholder text -->
    <string name="hello_blank_fragment">Hello blank fragment</string>
</resources>

 

 

2. res - values - themes - themes.xml

<resources xmlns:tools="http://schemas.android.com/tools">
    <!-- Base application theme. -->
    <style name="Base.Theme.MyProject" parent="Theme.Material3.DayNight.NoActionBar">
        <!-- Customize your light theme here. -->
        <!-- <item name="colorPrimary">@color/my_light_primary</item> -->
    </style>

    <style name="Theme.MyProject" parent="Base.Theme.MyProject" />

    <style name="ToolbarIconColor" parent="ThemeOverlay.AppCompat.ActionBar">
        <item name="colorControlNormal">#FFFFFF</item>
    </style>
</resources>

 

 

 

 

 

 

 

 

 

1. 툴바와 메뉴바를 위한 activit_main.xml

<androidx.drawerlayout.widget.DrawerLayout
    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:id="@+id/main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity"
    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="?attr/actionBarSize"
            android:background="?attr/colorPrimary"
            android:elevation="4dp"
            app:theme="@style/ToolbarIconColor"
            app:titleTextColor="#FFFFFF"/>
        <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:layout_gravity="start"
        android:background="#FF0000"
        android:fitsSystemWindows="true"
        android:gravity="center_horizontal"
        android:id="@+id/drawer"
        android:text="I am Drawer"
        android:textColor="#FFFFFF"
        android:textSize="20dp"
        android:textStyle="bold"/>
</androidx.drawerlayout.widget.DrawerLayout>

 

 

 

 

 

 

 

 

2. 툴바

1. menu폴더 만들고 menu.xml만들기

  • 툴바는 androidx에서 제공되고 있다.
<?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" //never, ifRoom
        app:actionViewClass="androidx.appcompat.widget.SearchView"/>
</menu>

 

 

 

2. main_activity.kt 와 연결

 

override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)
		
        // 이거 추가해주기
        setSupportActionBar(binding.toolbar)

    }

 

 

 

  • menu에 있는 것들과 툴바를 연결해준다.
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
        val inflater = menuInflater
        inflater.inflate(R.menu.menu_main, menu)

        return super.onCreateOptionsMenu(menu)
    }

 

 

 

 

  • 사용자 입력 후, 검색이벤트 처리하기
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
        val inflater = menuInflater
        inflater.inflate(R.menu.menu_main, menu)

        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 {
                val intent = Intent(Intent.ACTION_WEB_SEARCH)
                intent.putExtra(SearchManager.QUERY, query)
                startActivity(intent)
                Log.d(TAG, "search text: $query")
                return true
            }
        })
        return super.onCreateOptionsMenu(menu)
    }

 

 

 

 

 

 

3. 메뉴 추가하기

  • menu_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"/>

    <item
        android:id="@+id/menu0"
        android:title="Menu0"/>
</menu>

 

 

  • main_activity에서 수정하기

 

 

 

 

 

 

 

4. 메뉴 선택 이벤트 처리하기

override fun onOptionsItemSelected(item: MenuItem): Boolean {

        when(item.itemId){
            R.id.menu0 ->{
                val lat = 37.651450
                val lon = 127.016637
                var uri = Uri.parse("geo:" + lat + "," + lon)
                var intent = Intent(Intent.ACTION_VIEW, uri)
                startActivity(intent)
                true
            }
            1 -> {
                Log.d(TAG, "menu1")
                true
            }
            2 -> {
                Log.d(TAG, "menu2")
                true
            }
        }
        return super.onOptionsItemSelected(item)
    }

 

 

 

 

 

 

 

 

 

 

 

3. 드로우 레이아웃

  • actitity_main.xml
    • 드로우 레이아웃은 2개의 자식만을 갖을 수 있다.

1. 토글 버튼 만들기

  • main_activity.kt
// 추가
lateinit var toggle: ActionBarDrawerToggle


override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        setSupportActionBar(binding.toolbar)
		
        
        // 이 부분 추가하기
        toggle = ActionBarDrawerToggle(this, binding.main, R.string.drawer_opened,
            R.string.drawer_closed) // drawer가 열렸을 때와 갇혔을 때 스트링 ( strings.xml에 저장되어 있다.)
        supportActionBar?.setDisplayHomeAsUpEnabled(true) 
        toggle.syncState() // 토글 열때마다 싱크 하겠다.

    }

 

 

 

2. 토글 실행시키기

override fun onOptionsItemSelected(item: MenuItem): Boolean {

        when(item.itemId){
        (...)

        if(toggle.onOptionsItemSelected(item)){
            return true
        }
        return super.onOptionsItemSelected(item)
    }

 

 

 

 

 

4. 프레그먼트

1. 프레그먼트 만들기

 

 

 

 

2. ViewPasser2에 프리그먼트 연결하기

class MyFragmentPagerAdapter(activity: FragmentActivity): FragmentStateAdapter(activity){

        // 초기화
        val fragments: List<Fragment>
        init {
            //세개의 프레그먼트 아답터 만들기
            fragments = listOf(OneFragment(), TwoFragment(), ThreeFragment())
        }
    }
override fun onCreate(savedInstanceState: Bundle?) {
        (...)

        // 프레그먼트 뷰페이저와 연동
        val fadapter = MyFragmentPagerAdapter(this)
        binding.viewpager.adapter = fadapter
    }
  • 마이 프리그먼트 페이지 아답터: 세개의 프레그먼트를 하나의 뷰 페이저에 넣고 뷰페이저에서 어떤 프레그먼트를 보여줄지를 결정
  • <Fragment>가 빨간줄이라면: ALt + Enter로 프레스먼트를 import하고 androidx 선택

 

 

 

3. 각각의 프레그먼트 구분을 위해 배경색칠하기

 

 

 

 

4. 각 프레그먼트 동작 구현

 

  1. OneFragment
(...)
// 프레그먼트가 구성하는 화면
    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        //Inflate the layout for this fragment
        val binding = FragmentOneBinding.inflate(inflater,container, false)
        // activity는 layoutInflater매개변수를 이용하지만, 프레그먼트는 inflate를 괄호 안에 쓴다.

        //return inflater.inflate(R.layout.fragment_one, container, false)
        return binding.root
    }
(...)

 

 

 

 

 

 

4. 리사이클러 뷰 - 목록 화묜 구성

1. 리사이클러 뷰 

  • 동일한 형태의 뷰를 여러개 가지고 있는 것들을 관리하는데, 가장 많이 사용된다.
  • 아이템 하나에 대해서 레이아웃을 구성해 줘야 한다.
  • ViewHolder():하나의 아이템에 대한 구성을 하게 된다.
  • Adapter(): 리사이클러 뷰에 있는 것들을 관리, ViewHolder를 가지고 여러개이 아이템으로 구성되는 아답터 구성
  • LayoutManager():
  • ItemDecoration(): 꾸미기 

 

 

 

 

2. OneFragment - 리사이클러 뷰가 화면전체를 차지하게끔 만들기

// fragment.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.recyclerview.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="0dp"
    android:layout_weight="1"
    android:id="@+id/recyclerView"
    />

 

 

 

 

 

 

3. 리사이클러뷰가 각각의 아이템들의 항목을 구성하지 위한 파일 업로드

 

 

item_recyclerview.xml
0.00MB

 

 

 

 

 

 

 

 

 

 

 

 

 

4. OneFragmetn.kt에 recycleview에 필요한 내용들

 

 

// 화면을 구성하는 화면의 바이딩이 필요
class MyVeiwHolder(val binding: ItemRecyclerviewBinding): RecyclerView.ViewHolder(binding.root)
// 리사이클러 뷰관리, 리사이클러 뷰에 사용자가 만든 데이터를 전달하고 그 데이터로 MyViewHolder내용을 리사이클러 뷰에 넣는다.
// 이때 Myadapter은 밑에 세개의 함수가 필수적이다.
class Myadapter(val data: MutableList<String>): RecyclerView.Adapter<RecyclerView.ViewHolder>(){
    override fun getItemCount(): Int {
        TODO("Not yet implemented")
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        TODO("Not yet implemented")
    }

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        TODO("Not yet implemented")
    }
}

 

 

 

 

// 화면을 구성하는 화면의 바이딩이 필요
class MyVeiwHolder(val binding: ItemRecyclerviewBinding): RecyclerView.ViewHolder(binding.root)
// 리사이클러 뷰관리, 리사이클러 뷰에 사용자가 만든 데이터를 전달하고 그 데이터로 MyViewHolder내용을 리사이클러 뷰에 넣는다.
// 이때 Myadapter은 밑에 세개의 함수가 필수적이다.
class Myadapter(val datas: MutableList<String>): RecyclerView.Adapter<RecyclerView.ViewHolder>(){

    // 리사이클러뷰 안에 있는 아이템의 개수가 몇개냐 = 데이터의 갯수에 따라 다르다.
    override fun getItemCount(): Int {
        return datas.size
    }

    // MyVeiwHolder와 Myadapter연결
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        return MyVeiwHolder((ItemRecyclerviewBinding.inflate(LayoutInflater.from(parent.context), parent, false)))
    }

    // 전달받은 데이터를 뷰홀더에 연결, 하나하나 대응
    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        val binding = (holder as MyVeiwHolder).binding
        binding.itemData.text = datas[position]
    }
}

 

 

 


 

 

// 프레그먼트가 구성하는 화면
    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        //Inflate the layout for this fragment
        val binding = FragmentOneBinding.inflate(inflater,container, false)
        // activity는 layoutInflater매개변수를 이용하지만, 프레그먼트는 inflate를 괄호 안에 쓴다.

        var datas = mutableListOf<String>()
        for (i in 1..10){
            datas.add("item $i")
        }
        
        //return inflater.inflate(R.layout.fragment_one, container, false)
        return binding.root
    }

 

override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        //Inflate the layout for this fragment
        val binding = FragmentOneBinding.inflate(inflater,container, false)
        // activity는 layoutInflater매개변수를 이용하지만, 프레그먼트는 inflate를 괄호 안에 쓴다.

        var datas = mutableListOf<String>()
        for (i in 1..10){
            datas.add("item $i")
        }

        
        val radapter = Myadapter(datas)
        binding.recyclerView.layoutManager = LinearLayoutManager(activity)
        binding.recyclerView.adapter = radapter
        
        //return inflater.inflate(R.layout.fragment_one, container, false)
        return binding.root
    }

 

 

 

 

 

 

 

 

5. Item들의 배치를 그리드로 배치하기

// 프레그먼트가 구성하는 화면
    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        //Inflate the layout for this fragment
        val binding = FragmentOneBinding.inflate(inflater,container, false)
        // activity는 layoutInflater매개변수를 이용하지만, 프레그먼트는 inflate를 괄호 안에 쓴다.

        var datas = mutableListOf<String>()
        for (i in 1..10){
            datas.add("item $i")
        }

        val radapter = Myadapter(datas)
        //binding.recyclerView.layoutManager = LinearLayoutManager(activity)
        // 그리드로 표현
        binding.recyclerView.layoutManager = GridLayoutManager(activity, 2)
        binding.recyclerView.adapter = radapter



        //return inflater.inflate(R.layout.fragment_one, container, false)
        return binding.root
    }

 

 

 

 

 

 

 

 

6. 꾸미기

  • 데코레이션 만들기
//꾸미기
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 = 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, 60)
        else
            outRect.set(10, 10, 10, 0)

        view.setBackgroundColor(Color.parseColor("#28A0FF"))
        ViewCompat.setElevation(view, 20.0f)
    }
}

 

 


  • 데코레이션 반영하기
// 프레그먼트가 구성하는 화면
    override fun onCreateView(
        (...)

        // 데코레이션 반영
        binding.recyclerView.addItemDecoration(MyDecoration(activity as Context))


        //return inflater.inflate(R.layout.fragment_one, container, false)
        return binding.root
    }

 

 

 

 


  • 그림 뒤로 보내기
override fun onDrawOver(c: Canvas, parent: RecyclerView, state: RecyclerView.State) {
        // Darw : kbo -> item
    //override fun onDrawOver(c: Canvas, parent: RecyclerView, state: RecyclerView.State) {
        // DrawOver : item => kbo
        super.onDraw(c, parent, state)

 

 

 

 

 

 

7. 별도의 파일로 관리하기

 

 

 

 

 

8. 아이템 클릭 했을때 이벤트 처리 - MyAdapter.kt 수정

 

class Myadapter(val datas: MutableList<String>): RecyclerView.Adapter<RecyclerView.ViewHolder>(){

    (...)

    // 전달받은 데이터를 뷰홀더에 연결, 하나하나 대응
    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        val binding = (holder as MyVeiwHolder).binding
        binding.itemData.text = datas[position]

        // 이벤트 처리
        binding.itemRoot.setOnClickListener {
            Toast.makeText(it.context, "${datas[position]}이 선택되었습니다.", Toast.LENGTH_SHORT).show()
            AlertDialog.Builder(it.context).run{
                setTitle("알림")
                setIcon(android.R.drawable.ic_dialog_alert)
                setMessage("${datas[position]}이 선택되었습니다.")
                setPositiveButton("예", null)
                show()
            }
        }
    }
}