오늘도 안드 공부
리싸이클러 뷰. 목록 화면을 만들 때 사용하는 뷰
뷰홀더는 각 항목을 구성하는 뷰 객체를 가진다. 어댑터는 뷰홀더에 있는 뷰 객체에 적절한 데이터를 대입해 항목을 완성한다. 레이아웃매니저는 어댑터가 만든 항목을 배치해 리사이클러 뷰에 출력한다.
implementation("androidx.recyclerview:recyclerview:1.2.1")
<androidx.recyclerview.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/recyclerView"/>
목록에 표시할 항목을 디자인한 레이아웃 xml도 등록
뷰홀더 준비
RecyclerView.ViewHolder
를 상속받아 작성
어댑터 준비
RecyclerView.Adapter
를 상속받아 작성. 아래의 함수 재정의
getItemCount()
: 항목 개수를 판단하려고 자동으로 호출
onCreateViewHolder()
: 항목의 뷰를 가지는 뷰홀더를 준비하려고 자동으로 호출.
onBindViewHolder()
: 뷰홀더의 뷰에 데이터를 출력하려고 자동으로 호출. getItemCount()가 반환한 숫자만큼 여기서 호출되어 항목 생성
리사이클러 뷰 출력
레이아웃 매니저를 등록해 화면에 출력
항목을 동적으로 추가 및 제거
adapter의 notifyDataSetChanger()
함수를 호출해서 동적으로 관리
RecyclerView.LayoutManager를 상속받은 클래스.
GridLayoutManager
항목을 그리드로 배치. 생성자의 숫자는 그리드에서 열의 개수를 의미. 가로세로로 배치 가능.
StaggeredGridLayoutManager
항목을 높이가 불규칙한 그리드로 배치. 각 뷰의 크기가 다르면 지그재그로 배치된다.
리사이클러 뷰를 다양하게 꾸밀대 사용. 레이아웃 매니저가 항목을 배치하기 전후로 설정할 수 있다. 필수는 아니다.
class MyDecoration(val context: Context): RecyclerView.ItemDecoration(){
override fun onDraw(c: Canvas, parent: RecyclerView, state: RecyclerView.State) {
super.onDraw(c, parent, state)
}
override fun onDrawOver(c: Canvas, parent: RecyclerView, state: RecyclerView.State) {
super.onDrawOver(c, parent, state)
}
override fun getItemOffsets(
outRect: Rect,
view: View,
parent: RecyclerView,
state: RecyclerView.State
) {
super.getItemOffsets(outRect, view, parent, state)
}
}
화면을 탭해 좌우로 미는 스와이프 이벤트로 화면을 전환할 때 사용한다.
ex. 인스타그램
implementation ("androidx.viewpager2:viewpager2:1.0.0")
<androidx.viewpager2.widget.ViewPager2
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/viewpager"/>
뷰페이저2는 화면을 항목으로 본다. 항목이 순서대로 나열되어있고 한 화면에 항목 하나가 나온다는 개념이다. 따라서 리사이클러뷰의 어댑터를 적용해야 한다.
(RecyclerView.Adapter나 FragmentStateAdapter를 사용)
RecyclerView.Adapter는 위의 리사이클러 어댑터와 차이가 없고 뷰페이저2의 어댑터로 적용만 하면 된다. 하지만 이 방법은 복잡할 수 있다.
각 항목을 보통 프래그먼트로 작성하고, FragmentStateAdapter로 뷰페이저2를 구성하는 경우가 많다.
class MyFragmentPagerAdapter(activity: FragmentActivity): FragmentStateAdapter(activity){
val fragments: List<Fragment>
init{
fragments = listOf(OneFragment(), TwoFragment(), ThreeFragment())
Log.d("event", "fragment size : ${fragments.size}")
}
// 항목의 개수를 구하는 함수
override fun getItemCount(): Int = fragments.size
// 항목을 구성하는 프래그먼트 객체를 얻어 여기서 반환하는 객체가 각 항목에 출력
override fun createFragment(position: Int): Fragment = fragments[position]
}
binding.viewpager.orientation = ViewPager2.ORIENTATION_VERTICAL
액티비티 화면에 보이지 않던 내용이 왼족이나 오른쪽에서 손가락 움직임에 따라 밀려나오는 기능. 서랍처럼 메뉴 구성시 사용
<?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">
<LinearLayout
android:id="@+id/linear"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:text="hi"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="start">
<TextView
android:text="text"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</FrameLayout>
</androidx.drawerlayout.widget.DrawerLayout>
레이아웃 XML에서 최상위에는 androidx.drawerlayout.widget.DrawerLayout
태그를 달고, 하위태그로 2개를 꼭 달아야 적용이 된다.
첫번째 태그는 첫화면에 알아서 나오고, 두번째 태그의 android:layout_gravity 속성에따라 화면에서 나오는 방향을 지정해 해당 방향에서 숨겨져있다가 나온다.
툴바영역에 토글 버튼을 함께 제공하는데, ActionBarDrawerToggle 클래스를 이용하면 된다.
class MainActivity : AppCompatActivity() {
lateinit var toggle: ActionBarDrawerToggle
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
toggle = ActionBarDrawerToggle(this, binding.drawer, R.string.drawer_opened, R.string.drawer_closed)
// 토글 버튼으로 사용할 아이콘이 출력된다.(이것만 있으면 <-버튼)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
// 이거까지 있으면 네비게이션아이콘(가로줄3개)
toggle.syncState()
}
// 이 함수에서 메뉴 이벤트 처리를 해줘야한다.
override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (toggle.onOptionsItemSelected(item)) {
return true
}
return super.onOptionsItemSelected(item)
}
}
제트팩으로 화면 구성하기
androidx로 툴바, 메뉴, 프래그먼트, 리사이클러 뷰, 뷰페이저2, 드로어 레이아웃 이용.
프래그먼트로 화면 3개 만든 후 뷰페이저 2 이용
메인화면
드로어 레이아웃
뷰페이저2
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Theme.AndroidLab" parent="Theme.MaterialComponents.DayNight.NoActionBar">
<!-- Primary brand color. -->
<item name="colorPrimary">@color/purple_500</item>
<item name="colorPrimaryVariant">@color/purple_700</item>
<item name="colorOnPrimary">@color/white</item>
<!-- Secondary brand color. -->
<item name="colorSecondary">@color/teal_200</item>
<item name="colorSecondaryVariant">@color/teal_700</item>
<item name="colorOnSecondary">@color/black</item>
<!-- Status bar color. -->
<item name="android:statusBarColor">?attr/colorPrimaryVariant</item>
<!-- Customize your theme here. -->
</style>
</resources>
<?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"/>
class MyViewHolder(val binding: ItemRecyclerviewBinding) : RecyclerView.ViewHolder(binding.root)
class MyAdapter(val datas: MutableList<String>): RecyclerView.Adapter<RecyclerView.ViewHolder>(){
override fun getItemCount(): Int {
return datas.size;
}
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]
}
}
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, 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..10){
datas.add("Item $i")
}
val adapter = MyAdapter(datas)
val layoutManager = LinearLayoutManager(activity)
binding.recyclerView.layoutManager = layoutManager
binding.recyclerView.adapter = adapter
binding.recyclerView.addItemDecoration(MyDecoration(activity as Context))
return binding.root
}
}
<?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:actionViewClass="androidx.appcompat.widget.SearchView"
app:showAsAction="always"/>
</menu>
<?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:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/toolbar"
android:background="?attr/colorPrimary"/>
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/viewpager"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="start">
<TextView
android:layout_width="300dp"
android:layout_height="match_parent"
android:background="#FF0000"
android:text="Drawer!!"
android:textSize="20dp"
android:textStyle="bold"
android:textColor="#FFFFFF"
android:gravity="center_horizontal"
android:fitsSystemWindows="true" />
</FrameLayout>
</androidx.drawerlayout.widget.DrawerLayout>
class MainActivity : AppCompatActivity() {
lateinit var toggle: ActionBarDrawerToggle
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()
val adapter = MyFragmentPagerAdapter(this)
binding.viewpager.adapter = adapter
}
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 {
Log.d("event", "search text : $query")
return true
}
})
return true
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (toggle.onOptionsItemSelected(item)){
return true
}
return super.onOptionsItemSelected(item)
}
}