KOTLIN

[Kotlin] 공부 14일차 (2022-02-01)

HJ39 2022. 2. 2. 02:06

제트팩과 androidx 소개

뷰 페이저2 - 스와이프로 점기는 화면 구성

드로어 레이아웃 - 옆에서 열리는 화면 구성

11장 실습

 

  • 제트팩과 androidx 소개

제트팩 : 안드로이드 앱을 개발하는데 필요한 다양한 라이브러리 모음

 

  • 플랫폼 API

→ 플랫폼 API는 ART(Android runtime)에서 제공하는 안드로이드 앱의 핵심 라이브러리이다.

→ ART는 대부분 android나 java로 시작하는 패키 지명을 사용한다.

→ 구글에서 2018년에 제트팩을 발표했다.

 

  • 제트팩

→ androidx로 시작하는 패키지명을 사용한다.

 

□ 제트팩의 3가지 목적

앱을 개발하는 데 필요한 권장 아키텍처를 제공한다.
API레벨의 호환성 문제를 해결한다.
플랫폼 API에서 제공하지 않는 다양한 기능을 제공한다.

→ 앱을 개발할 때 적용할 수 있는 다양한 아키텍처를 제시하고 이를 위해 뷰 모데, 라이브 데이터, 룸, 페이징 등의 라이브러리를 제공한다.

→ 제트팩은 API 레벨의 호환성 문제를 해결해 준다.

→ 목적이 같은 클래스를 제트팩이 지원하면 대부분 제트팩 라이브러리를 사용한다.

 

  • androidx 라이브러리

□ 화면 구성 라이브러리

라이브러리 설명
androidx.appcompat 앱의 API레벨 호환성을 해결
andoridx.recyclerview 목록 화면을 구성
andoridx.viewpager2 스와이프로 넘기는 화면을 구성
andoridx.fragment 액티비티처럼 동작하는 뷰를 제공
andoridx.drawerlayout 옆에서 서랍처럼 열리는 화면을 구성

 

  • appcompat라이브러리 - API호환성 해결

 

□ appcompat 라이브러리 선언

implementation 'androidx.appcompat:appcompat:1.2.0'

→ appcompat 라이브러리를 사용하려면 그래 들 파일의 dependencies항목에 선언해야 한다.

→ AppCompatActivity 클래스를 상속받아 작성한다.

 

  • 액션바

액션바 : 화면 위쪽에 타이틀 문자열이 출력되는 영역

액션바 구성

→ 내비게이션 아이콘, 타이틀, 액션 아이템, 오버플로 메뉴 등

 

 

# 액션바 색상 설정

 

→ 앱을 실행하면 자동으로 액션바의 색상은 자동으로 적용되는 테마에서 결정된다.

→ 테마 스타일은 res/values 디렉터리에 있는 themes.xml파일에 선언되어있다.

→ 기본으로 작성된 테마 파일은 Theme.AndroidLab인 스타일이 기본으로 선언되어 있다.

 

□ 테마 색상 설정

<resources xmlns:tools="http://schemas.android.com/tools">
	<style name="Theme.AndroidLab" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
		<item name="colorPrimary"> #FF0000 </item>
		<item name="colorPrimaryVariant"> @color/purple_700 </item>
		<item name="colorOnPrimary"> @color/white </item>
		<item name="colorSecondary"> #0000FF </item>
		<item name="colorSecondaryVariant"> @color/teal_700 </item>	
		<item name="colorOnSecondary"> @color/white </item>
		<item name="android:statusBarColor" tools:targetApi="l"> #CC0000 </item>
	</style>
</resources>

→ colorPrimary : 앱의 브랜드를 표현하는 색상, 액션바와 버튼의 배경색으로 사용

→ colorSecondary : 앱의 브랜드를 표현하는 색상, 활성 상태를 표현

ex) 텍스트 뷰의 링크, 체크박스나 라디오 버튼이 체크되었을 때, 스위치가 on 상태일 때 colorSecondary가 사용된다.

→ statusBarColor : 상태바의 배경색으로 사용

→ colorOnPrimary, colorOnSecondary, colorPrimary, colorSecondary : 전경 색으로 사용

→ colorPrimaryVariant, colorSecondaryVariant : 그림자의 색상으로 사용

 

# 액션바 숨기기 설정

→ 액티비티에 액션바를 숨기는 것도 테마 파일에서 설정할 수 있다.

→ Theme.MaterialComponents.DayNight.NoActionBar를 상속받으면 액션 바가 나오지 않는다.

 

□ <item> 속성으로 숨기기

<style name="Theme.AndroidLab" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
		<item name="windowActionBar"> false </item>
		<item name="windowNoTitle"> true </item>
		
	</style>

→ 이미 상속을 받아 NoActionBar 상속을 받을 수 없는 경우라면 <item> 속성에 설정하면 된다.

 

# 업 버튼 설정

업 버튼 : 액티비티 화면이 앱의 첫 화면이 아닐 때 이전 화면으로 되돌아가는 기능

 

□ 매니페스트 파일에서 업 버튼 설정

<activity
	android:name=".TwoActivity"
	android:parentActivityName=".MainActivity"></activity>

 

→ parentActivityName=". MainActivity" 설정하는 것으로도 화면에 업 버튼이 생성된다

 

□ 업 버튼 클릭 시 자동으로 호출되는 함수 재정의

override fun onSupportNavigateUp(): Boolean{
	Log.d("kkang","onSupportNavigateUp")
	return super.onSupportNavigateUp()
}

→ 업 버튼을 눌렀을 때 이전 화면으로 돌아가기 전에 특별한 로직을 실행할 때 사용

 

□ 액티비티 코드에서 업 버튼 생성

supportActionBar?.sestDisplayHomeAsUpEnabled(true)

→ 액션바에 업 버튼이 나오게 할 수 있다.

 

    • 메뉴 구성

→ 액티비티 화면에서 사용자 이벤트를 사용할 수 있도록 한다.

→ 액티비티에 메뉴를 추가하면 액션바 오른쪽에 오버플로 버튼이 나타난다.

→ 오버플로 메뉴 중에서 몇몇은 액션바에 아이콘으로 나오게 할 수 있다.

 

onCreateOptionsMenu()

→ 액티비티가 실행되면서 처음에 한 번만 호출된다.

→ 액티비티에 정적인 메뉴를 구성할 때 사용

→ 대부분 액티비티 메뉴를 구성하는 데 사용된다.

 

onPrepareOptionsMenu()

→ 액티비티가 실행되면서 처음에 한 번만 호출된다.

→ 오버플로 메뉴 실행 시 반복 실행된다.

→ 메뉴가 화면에 나올 때마다 동적으로 구성하고 싶은 경우에 선택

 

□ 메뉴 구성 함수

override fun onCreateOptionsMenu(menu: Menu?): Boolean{
	val menuItem1: MenuItem? = menu?.add(0,0,0,"memu1")
	val menuItem1: MenuItem? = menu?.add(0,0,0,"memu1")
	return super.onCreateOptionsMenu(menu)
}

→ 매개변수 Menu 객체를 추가할 때마다 add() 함수를 이용한다.

 

□ add() 함수

fun add(groupId: Int, itemId: Int,order: Int, title: CharSequence!): MenuItem!

→ 두 번째 매개변수 : 메뉴의 식별자

→ 네 번째 매개변수 : 메뉴의 문자열

→ 반환 값 : MenuItem이며 객체가 메뉴 1개를 의미

 

□ onOptionsItemSelected() 함수

override fun onOptionsItemSelected(item: MenuItem): Boolean = when (item.itemId){
	0 -> {
		Log.d("kkang","menu1 click")
		true
	}
	1 -> {
		Log.d("kkang","menu2 click")
		true
	}
	else-> super.onOptionsItemSelected(item)
}

→ onOptionsItemSelected : 사용자가 메뉴 선택 시 이벤트 처리하는 함수

→ MenuItem의 itemId 속성으로 이벤트가 발생한 메뉴 객체의 식별 값을 얻어서 이벤트를 처리한다.

 

# 리소스로 메뉴 구현

<menu xmlns:android="http://schemas.android.com/apk/res/android" 
	xmlns:app="http://schemas.android.com/apk/res-auto>

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

→ <item> 메뉴 하나가 메뉴 하나에 대응한다.

→ id 속성 : 레이아웃 XML에서 뷰의 id값과 같이 메뉴를 식별하는 데 사용

→ title과 icon 속성 : 메뉴 문자열과 아이콘을 지정

→ showAsAction 속성 : 액션바에 아이콘으로 나타나게 한다.

 

showAsAction 속성

→ never(기본) : 항상 오버플로 메뉴로 출력

→ ifRoom : 만약 액션바에 공간이 있다면 액션 아이템으로, 없다면 오버플로 메뉴로 출력

→ always : 항상 액션 아이템으로 출력

 

 

□ 액티비티 코드에 메뉴 XML 적용

override fun onCreateOptionsMenu(menu: Menu?): Boolean{
	menuInflater.inflate(R.menu.menu_main,menu)
	return super.onCreateOptionsMenu(menu)
}

→ inflate() 함수에 매개변수로 메뉴 XML 파일을 명시하면 액티비티에 메뉴가 적용된다.

 

# 액션 뷰 이용

액션 뷰 : 액션바에서 특별한 기능을 제공한다.

ex) andoirdx.appcompat.widget.SearchView

→ 서치 뷰는 액션바에서 검색 기능을 제공한다.

 

□ 서치 뷰 이용

<item android:id="@+id/menu_search"
	android:title="search"
	app:showAsAction="always"
	app:actionViewClass="androidx.appcompat.widget.SearchView"/>

→ 액션 뷰를 메뉴에 적용할 때 actionViewClass 속성을 이용한다.

 

□ 서치 뷰 검색 기능 구현

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{
			return true
		}
	})
	return true
}

→ MenuItem객체는 findItem 함수의 매개변수에 MenuItem의 식별 값을 주어 얻는다.

→ MenuItem에 등록된 액션 뷰는 actionView 속성으로 얻는다.

→ 검색과 관련된 이벤트를 처리할 때는 SearchView의 setOnQueryTextListener() 함수로 이벤트 핸들러를 지정한다.

 

      • 툴바

→ 툴바는 개발자가 직접 제어하는 뷰이다.

→ androidx.appcompat.widget.Toolbar 클래스를 이용한다.

→ XMl파일에 등록하여 사용

 

□ 레이아웃 XML에 툴바 등록

<androidx.appcompat.widget.Toolbar
	android:id="@+id/toolbar"
	android:layout_width="match_parent"
	android:layout_height="wrap_content"
	style="@style/Widget.MaterialComponents.Toolbar.Primary"/>

→ 액션바의 내용이 툴바에 적용되도록 지정해야 한다.

setSupportActionBar(binding.toolbar)

 

      • 호환성을 고려한 기본 뷰 클래스

플랫폼 API에서 제공하는 기본 뷰를 appcompat 라이브러리에서도 제공한다.

 

      • 프래그먼트 -액티비티처럼 동작하는 뷰

플랫폼 API에서 android.app.Fragment로 제공하지만 대부분 androidx.fragment 라이브러리를 이용해 구현한다.

 

  • 프래그먼트 소개

→ 프래그먼트 : 텍스트 뷰나 버튼처럼 액티비티 화면을 구성하는 뷰이지만 그 자체만으로 화면에 아무것도 출력되지 않는다.(액티비티처럼 동작한다)

→ 액티비티에서 작성할 수 있는 모든 코드는 프래그먼트에도 사용할 수 있다.

→ 태블릿처럼 화면이 넓은 기기에서 동작하는 앱을 개발할 수 있도록 제공되었다.

ex) 구글 플레이스토어 탭들

 

  • 프래그먼트 구현

→ androidx.fragment 라이브러리에서 제공한다.

 

□ 프래그먼트 선언

dependencies{
	implementation 'androidx.fragment:fragment-ktx:1.2.5'
}

→ -ktx : 코틀린으로 작성되었다는 의미

→ androidx.fragment:fragment : 자바로 작성된 프래그먼트

→ Fragment를 상속받는다.

 

□ 프래그먼트 구현

import androidx.fragment.app.Fragment

class OneFragment : Fragment() {
	lateinit var binding: FragmentOneBinding
	override fun onCreateView(
		inflater: LayoutInflater, container: ViewGroup?,
		savedInstanceState: Bundle?
	): View?{
		binding = FragmentOneBinding.inflate(inflater,container,false)
		return binding.root
	}
}

→ onCreate() 함수로 프래그먼트 클래스에 최소한으로 작성해야 한다.

→ onCreate() 함수가 자동으로 호출되며 반환한 View 객체가 화면에 출력된다

 

# 액티비티의 레이아웃 XML에 등록하여 프래그먼트 출력

→ 프래그먼트도 뷰이므로 액티비티의 화면을 구성하는 레이아웃 XML에 등록하여 액티비티 화면에 나오게 할 수 있다.

→ <fragment> 태그의 name속성에 프래그먼트 클래스를 지정하면 된다.

 

□ 프래그먼트 출력

<fragment
	android:name="com.example.test11.OneFragemnt"
	android:id="@+id/fragmentView"
	android:layout_width="match_parent"
	android:layout_height="match_parent" />

 

→ 코드에서 프래그먼트를 동적으로 제어(추가, 제거)하려면 FragmentManager로 만든 FragmentTransaction 클래스 함수를 사용한다.

 

□ 프래그먼트 동작 제어

val fragmentManager: FragmentManager = supportFragmentManager
val transaction: FragmentTransaction = fragmentManager.beginTransaction()
val fragment = OneFragment()
transaction.add(R.id.fragment_content, fragment)
transaction.commit()

→ FragmentTransaction 객체의 add함수를 이용해 프래그먼트 객체를 화면에 출력하는 코드이다.

→ add() 함수 : 첫 번째 매개변수가 프래그먼트가 출력될 뷰의 id값

→ commit() 함수 : 호출하는 순간 화면에 적용

→ replace() : 추가된 프래그먼트를 대체

→ remove() : 추가된 프래그먼트를 제거

 

# 프래그먼트 생명주기

→ onReume() 함수가 호출되어 활성(Active) 상태가 된 후의 생명주기 흐름이 2가지로 나뉜다

→ 프래그먼트가 백 스택에 포함되었는지에 따라 달라진다

→ 백 스택은 프래그먼트가 화면에 보이지 않는 순간 제거하지 않고 저장했다가 다시 이용할 수 있게 하는 기능이다.

 

백 스탭을 이용하지 않는 경우

→ 프래그먼트 생명주기가 왼쪽 화살표의 흐름대로 진행된다.

→ 프래그먼트가 화면에서 제거 or 대체되는 순간에

onPause() -> onStop() -> onDestroyView() -> onDestroy() -> onDetach()

함수까지 호출된다.

→ 이런 과정을 거치면 프래그먼트가 완벽히 제거되어 뒤로 가기 버튼을 눌러도 나오지 않는다.

 

백 스탭을 이용하는 경우

→ 프래그먼트가 제거 or 대체되는 순간 

onPause() -> onStop() -> onDestroyView() 까지만 호출된다.

→ 뒤로 가기 버튼을 누르게 되면 다시 onCreateView()부터 활성 상태가 된다

 

→ FragmentTransaction의 addToBack Stack() 함수로 백 스택 이용 여부를 결정한다. 

 

□ 백 스택 이용 설정

transaction.addToBackStack(null)

 

 

 

  • 리사이클러 뷰 - 목록 화면 구성

여러 가지 항목을 나열하는 목록 화면을 만들 때 사용

목록을 만들 때 RecyclerVeiw 클래스만으로는 화면에 아무것도 출력되지 않는다.

→ ViewHolder(필수) : 항목에 필요한 뷰 객체를 가진다.

→ Adapter(필수) : 항목을 구성한다.

→ LayoutManager(필수) : 항목을 배치한다.

→ ItemDecoration(옵션) : 항목을 꾸민다.

 

□ 리사이클러 뷰 선언

implementation 'androidx.recyclerview:recyclerview:1.1.0'

→ 그래들 파일에 등록해야 하지만 안드로이드 스튜디오 4.1 버전부터 자동적으로 추가해 주고 있다.

 

□ 리사이클러 뷰 등록

<android.recyclerview.widget.RecyclerView
	xmlns:android="http://schemas.android.com/apk/res/android"
	android:id="@+id/recyclerView"
	android:layout_width="match_parent"
	android:layout_height="match_parent" />

→ 리사이클러 뷰를 레이아웃 XML 파일에 등록

 

# 뷰 홀더 준비

 

□ 뷰 홀더 준비

class MyViewHolder(val binding: ItemMainBinding): RecyclerView.ViewHolder(binding.root)

→ RecyclerView.ViewHolder를 상속받아 작성한다.

→ 뷰 바인딩 기법을 이용하면 뷰 홀더는 항목 레이아웃 XML 파일에 해당하는 바인딩 객체만 가지면 되므로 코드 길이가 짧아진다.

 

# 어댑터 준비

→ 어댑터 : 뷰 홀더의 뷰에 데이터를 출력해 각 항목을 만들어 주는 역할

→ RecyclerView.Adapter를 상속받는다

 

□ 어댑터 준비

class MyAdapter(val binding: ItemMainBinding):
	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")
	}
}

→ getItemCount() : 항목 개수를 판단하려고 자동으로 호출

→ onCreateViewHolder() : 항목의 뷰를 가지는 뷰 홀더를 준비하려고 자동으로 호출

→ onBindViewHolder() : 뷰 홀더의 뷰에 데이터를 출력하려고 자동으로 호출

 

□ 항목의 개수 구하기

override fun getItemCount(): Int = datas.size

→ getItemCount() 함수는 자동 호출되어 항목의 개수를 판단한다.

→ getItemCount() 함수가 반환한 숫자만큼 onBindViewHolder() 함수가 호출되어 항목을 만든다.

→ getItemCount() 함수가 0을 반환하면 화면에는 아무것도 나오지 않는다.

 

□ 항목 구성에 필요한 뷰 홀더 객체 준비

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder = 
	MyViewHolder(ItemMainBinding.inflate(LayoutInflater.from(parent,context),parent,false))

 

□ 뷰에 데이터 출력

override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int){
	Log.d("kkang","onBindViewHolder : $position")
	val binding = (holder as MyViewHolder).binding
	//뷰에 디어터 출력
    binding.itemData.text = datas[position]
    // 뷰에 이벤트 추가
	binding.itemRoot.setOnClickListener{
		Log.d("kkang","item root click : $position")
	}
}

→ 뷰 바인딩 기법으로 뷰 홀더 객체를 생성해 반환하는 구문

→ onCreateViewHolder() 함수에서 반환한 뷰 홀더 객체는 자동으로 onBindViewHolder() 함수 매개변수로 전달된다.

→ 두 번째 매개변수가 항목의 인덱스이다.

 

# 리사이클러 뷰 출력

 

class RecyclerViewActivity: AppCompatActivity{
	override fun onCreate(savedInstanceState: Bundle?){
		super.onCreate(savedInstanceState)
		val binding = ActivityRecyclerviewBinding.inflate(layoutInflater)
		setContentView(binding.root)
		val datas = mutableListOf<String>()
		for(i in 1..10){
			datas.add("Item $i")
		}
	binding.recyclerView.layoutManager = LinearLayoutManager(this)
	binding.recyclerView.adapter = MyAdapter(datas)
	biding.recyclerView.addItemDecoration(DividerItemDecoration(this, 
	LinearLayout Manager.VERTICAL)
	}
}

→ 리사이클러 뷰에 레이아웃 매니저를 등록해 화면에 출력한다.

 

# 항목을 동적으로 추가, 제거

□ 항목 추가

datas.add("new data")
adpater.notifyDataSetChanged()

→ 항목을 구성하는 데이터에 새로운 데이터를 추가하거나 제거한 후 어댑터의 notifyDataSetChanged() 함수를 호출

 

  • 레이아웃 매니저

→ 레이아웃 매니저는 어댑터로 만든 항목을 리사이클러 뷰에 배치한다.

→ RecyclerView.LayoutManager를 상속받는다.

→ LinearLayoutManager : 항목을 가로나 세로 방향으로 배치

→ GridLayoutManager : 항목을 그리드로 배치

→ StaggeredGridLayoutManager : 항목을 높이가 불규칙한 그리드로 배치

 

# 항목을 가로, 세로 방향으로 배치

→ LinearLayoutManager를 사용한다.

 

□ 항목을 세로로 배치

 binding.recyclerview.layoutManager = LinearLayoutMaanger(this)

→ 세로 방향이 기본으로 적용

 

□ 항목을 가로로 배치

val layoutManager = LinearLayoutManager(this)
layoutManager.orientation = LinearLayoutManager.HORIZONTAL
binding.recyclerView.layoutManager = layoutManager

→ orientation 값을 HORIZONTAL로 지정하면 가로방향으로 바뀐다.

 

# 그리드로 배치하기

→ GridLayoutManager를 사용

 

□ 항목을 그리드로 배치

val layoutManager = GridLayoutManager(this,2)
binding.recyclerView.layoutManager = layoutManager

→ GridLayoutManager 생성자의 숫자는 그리드에서 열의 개수를 뜻한다.

ex) 2로 지정하면 2열, 3으로 지정하면 3열로 구성

→ 기본인 세로로 설정

 

□ 그리드에서 가로로 배치

val layoutManager = GridLayoutManager(this,3,GridLayoutManager.HORIZONTAL, false)
binding.recyclerView.layoutManager = layoutManager

→ LinearLayout과 같이 HORIZONTAL로 지정하면 가로로 전환된다.

→ 네 번째 매개변수가 true인 경우 세로 방향일 때 뷰가 아래부터 배치되고 가로 방향일 때 오른쪽부터 배치된다.

 

# 높이가 불규칙한 그리드로 배치

val layoutManager = StaggeredGridLayoutManager(2,StaggeredGridLayoutMaanger.VERTICAL)
binding.recyclerView.layoutManager = layoutManager

→ GridLayoutManager처럼 뷰를 그리드 구조로 배치하지만 뷰의 크기가 다르면 지그재그 형태로 배치한다.

 

  • 아이템 데커레이션

→ 리사이클러 뷰를 다양하게 꾸밀 때 사용

→ 아이템 데커레이션은 필수가 아니므로 필요하면 리사이클러 뷰에 적용하면 된다.

→ 라이브러리 제공 아이템 데코레이션 : DividerItemDecoration, 구분선을 출력해 준다.

→ 대부분 ItemDecoration을 상속받는 개발자 클래스를 만들고 이 클래스에서 다양한 꾸미기 작업을 한다.

 

# 아이템 데커레이션 List

onDraw() 항목이 배치되기 전에 호출
onDrawOver() 항목이 모두 배치된 후 호출
매개변수 Canvas 객체로 그림을 그린다.
getItemOffsets() 개별 항목을 꾸밀 때 호출
항목 하나당 한 번씩 호출되어 항목을 꾸미는데 사용한다
네 방향(left, top, right, bottom)의 여백을 설정할수 있다.

→ 아이템 데커레이션 객체를 리사이클러 뷰에 적용할 때는 addItemDecoration() 함수를 이용한다.

 

  • 뷰 페이저2 - 스와이프로 점기는 화면 구성

뷰 페이저(view pager) : 스와이프 이벤트로 화면을 전환할 때 사용

→ 뷰 페이저는 플랫폼 API에서 제공하지 않으므로 androidx 라이브러리를 이용해 개발해야 한다.

 

□ 뷰 페이저2 선언

implementation 'androidx.viewpager2:viewpager2:1.0.0'

→ 뷰 페이저2를 사용하려면 그래들 파일의 dependencies 항목에 선언해야 한다.

→ 뷰 페이저 2는 화면을 항목으로 본다.

→ 뷰 페이저 2에 사용할 수 있는 어댑터는 2가지인데 리사이클러 뷰에서 봤던 RecyclerView.Adapter를 그대로 이용하거나 FragmentStateAdapter를 사용할 수도 있다.

 

# 리사이클러 뷰 어댑터 이용

→ RecyclerView.Adapter는 리사이클러 뷰에서 뷰 페이저 2의 어댑터로 적용하면 된다.

 

# 프래그먼트 어댑터 이용

 

□ 뷰 페이저2 구현 - 프래그먼트 어댑터 이용

class MyFragmentPagerAdapter(activity: FragmentActivity): FragmentStateAdapter(activity){
	val fragments: List<Fragment>
	init{
		fragments = listOf(OneFragment(), TwoFragment(), ThreeFragment())
		Log.d("kkang", "fragments size: ${fragmnets.size}")
	}
	override fun getItemCount(): Int= fragments.size
	override fun createFragment(position: Int): Fragment = fragments[position]
}

→ FragmentStateAdapter를 상속받아 어댑터를 작성한 것이다.

 

□ 뷰 페이저2 세로로 적용

binding.viewpager.orientation = ViewPager2.ORIENTATION_VERTICAL

→ orientation 속성 값을 VERTICAL로 바꾸면 세로로 전환된다.

 

  • 드로어 레이아웃 - 옆에서 열리는 화면 구성

드로어 레이아웃 : 액티비티 화면에 보이지 않던 내용이 왼쪽이나 오른쪽에서 움직임에 따라 밀려 나오는 기능

 

□ 드로어 레이아웃 선언

implementation 'androidx.drawerlayout:drawerlayout:1.1.1'

→ 액티비티 레이아웃 XML 파일의 루트 태그를 DrawerLayout으로 선언한다.

→ 첫 번째 하위 태그 부분을 액티비티 화면에 출력

→ 두 번째 하위 태그 부분이 안 보이다가 끌려 나온다.

 

□ 드로어 태그

<android.drawerlayout.widget.DrawerLayout>

</androidx.drawerlayout.widget.DrawerLayout>

→ 하위 태그가 2개여야 한다.

→ 첫 번째 태그에 해당하는 화면이 알아서 나온다

→ 두 번째 태그에 해당하는 화면은 사용자가 화면 끝을 밀어야 나타난다.

→ 두 번째 태그 android:layout_gravity 속성 값을 이용하여 화면에 나오는 방향을 지정할 수 있다.

(left, right, start를 지정 , start는 사용하는 언어의 방향에 따라 left / right가 자동으로 결정)

→ 레이아웃 XML만으로 드로어 레이아웃을 이용할 수 있는데, 툴바 영역에 토글(toggle) 버튼을 제공한다.

 

 

lateinit var toggle = ActionBarDrawerToggle
toggle = ActionBarDrawerToggle(this, binding.drawer, R.string.drawer_opened, 
R.stringdrawer_closed)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
toggle.syncState()

→ ActionBarDrawerToggle 생성자의 두 번째 매개변수는 토글 버튼으로 여닫는 드로어 객체이다.

→ 세 번째, 네 번째 매개변수는 문자열 리소스로 드로어가 열리거나 닫혔을 때의 상태를 표현한 문자열

→ supportActionBar?.setDisplayHomeAsUpEnabled(true) 구문은 토글 버튼으로 사용할 아이콘이 출력

→ 액션바 영역의 HomeAsUp 아이콘은 왼쪽 화살표(←) 모양이고 내비게이션 아이콘(≡)이 나오게 하려면 toggle.syncState() 함수까지 설정해야 한다.

 

  • 11장 실습

□ Activity_main.xml

<?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>

□ fragment_one.xml

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

□ OneFragment.kt

import android.content.Context
import android.graphics.BitmapFactory
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Rect
import android.graphics.drawable.Drawable
import android.os.Bundle
import android.util.Log
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.content.res.ResourcesCompat
import androidx.core.view.ViewCompat
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.example.ch11_jetpack.databinding.FragmentOneBinding
import com.example.ch11_jetpack.databinding.ItemRecyclerviewBinding

//항목 View를 가지는 역활
class MyViewHolder(val binding: ItemRecyclerviewBinding): RecyclerView.ViewHolder(binding.root)
//항목 구성자. Adapter
class MyAdapter(val datas: MutableList<String>): RecyclerView.Adapter<RecyclerView.ViewHolder>(){
    //항목 갯수를 판단하기 위해서 자동 호출
    override fun getItemCount(): Int{
        return datas.size
    }
    //항목의 뷰를 가지는 Holder 를 준비하기 위해 자동 호출
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder
            = MyViewHolder(ItemRecyclerviewBinding.inflate(LayoutInflater.from(parent.context), parent, false))
    //각 항목을 구성하기 위해서 호출
    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        val binding=(holder as MyViewHolder).binding
        //뷰에 데이터 출력
        binding.itemData.text= datas[position]
    }
}
//RecyclerView 꾸미기
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.getResources(), 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.getResources(), 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) //left, top, right, bottom
            outRect.set(10, 10, 10, 60)
        else
            outRect.set(10, 10, 10, 0)

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

    }
}

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")
        }
        //리사이클러뷰에 LayoutManager, Adapter, ItemDecoration 적용
        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
    }
}

□ fragment_two.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    >
    <TextView
        android:id="@+id/fragment_textView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:textSize="20dp"
        android:textStyle="bold"
        android:gravity="center"
        android:text="Two Fragment!!"
        android:background="#FFBEC3"
        android:textColor="#FFFFFF"/>
</LinearLayout>

□ TwoFragment.kt

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import com.example.ch11_jetpack.databinding.FragmentTwoBinding

class TwoFragment : Fragment() {

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        return FragmentTwoBinding.inflate(inflater, container, false).root
    }
}

□ fragment_three.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    >
    <TextView
        android:id="@+id/fragment_textView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:textSize="20dp"
        android:textStyle="bold"
        android:gravity="center"
        android:text="Three Fragment!!"
        android:background="#FFA500"
        android:textColor="#FFFFFF"/>
</LinearLayout>

□ ThreeFragment.kt

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import com.example.ch11_jetpack.databinding.FragmentThreeBinding

class ThreeFragment : Fragment() {

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        // Inflate the layout for this fragment
        return return FragmentThreeBinding.inflate(inflater, container, false).root
    }
}

□ item_recyclerview.xml

<?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>

□ MainActivity.kt

import android.os.Bundle
import android.util.Log
import android.view.Menu
import android.view.MenuItem
import androidx.appcompat.app.ActionBarDrawerToggle
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.SearchView
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.viewpager2.adapter.FragmentStateAdapter
import com.example.ch11_jetpack.databinding.ActivityMainBinding

class MainActivity : AppCompatActivity() {

    lateinit var toggle: ActionBarDrawerToggle

    //ViewPager Adapter.......
    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)

        //ActionBarDrawerToggle 버튼 적용
        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
    }

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

 

 

14일 차 공부 끝!