연락처를 보면 가나다라 순으로 정렬되면서 헤더에 초성 'ㄱ''ㄴ'등이 들어간걸 볼 수 있다.
정렬을 할때 처음엔 sort를 쓰긴했는데 그러면 한글은 후순위로 쭉 밀리게 된다
어떻게 해결할까?
일단 정렬하는 함수를 만들어줬다
fun sortContacts(contacts: MutableList<User>): List<User> {
val collator = java.text.Collator.getInstance(Locale.KOREAN)
return contacts.sortedWith { contact1, contact2 ->
collator.compare(contact1.name, contact2.name)
}
}
Java에서 Collator라는 클래스를 이용하면 되는데
Collator는 문자열을 특정 로케일에 맞게 비교하고 정렬한다
여기서는 한국어를 비교하는데 사용했다
compare메소드를 사용해서 두 문자를 비교한다
문자열이 동일하면 0, 첫번째 문자열이 두번째 문자열보다 앞서면 음수, 뒤면 양수를 반환하는식으로 정렬하는데
이렇게 각문자열을 비교해서 한글순으로 비교하게된다
정렬을 했으니 이걸 자음으로 변환 시켜보자
fun getKorean(name: String): String {
if (name.isEmpty()) return "#"
val firstChar = name.first()
return when {
firstChar in '가'..'깋' -> "ㄱ"
firstChar in '나'..'닣' -> "ㄴ"
firstChar in '다'..'딯' -> "ㄷ"
firstChar in '라'..'맇' -> "ㄹ"
firstChar in '마'..'밓' -> "ㅁ"
firstChar in '바'..'빟' -> "ㅂ"
firstChar in '사'..'싷' -> "ㅅ"
firstChar in '아'..'잏' -> "ㅇ"
firstChar in '자'..'짛' -> "ㅈ"
firstChar in '차'..'칳' -> "ㅊ"
firstChar in '카'..'킿' -> "ㅋ"
firstChar in '타'..'팋' -> "ㅌ"
firstChar in '파'..'핗' -> "ㅍ"
firstChar in '하'..'힣' -> "ㅎ"
firstChar.isDigit() -> "0"
firstChar.isLetter() -> "A-Z"
else -> "#"
}
}
데이터에서 name의 first에 오는 문자가 어디에 속했는지 범위를 정했고
가
..깋
내에 속하면 "ㄱ"을 반환하는 식으로 구성을 했다.
getKorean에 넣으면 문자들은 전부 자음으로 변하게 되는 함수를 만들었고 이걸 이용해서 헤더를 만들것이다
class HeaderList {
operator fun invoke(contacts: MutableList<User>): List<Any> {
val header = mutableListOf<Any>()
var lastHeader = ""
contacts.forEach { user ->
val initial = getKorean(user.name)
if (initial != lastHeader) {
header.add(initial)
lastHeader = initial
}
header.add(user)
}
return header
}
유저데이터의 리스트를 받아서 이걸 전부 getKorean에 넣어준다
그냥 넣기만하면 자음들이 겹칠테니 스택방식을 이용해서 같은글자는 한번만 받고 유저가 어디에 속한지 알아야하니 유저의 정보도 같이 받는다
그러면 ㄱ
,,,,,ㄴ
,,, 이런식의 배열이 완성되는데
프래그먼트에서 타입이 String인지 User인지 구별해주면된다
createHeader(UserList.userList).map {
when (it) {
is String -> ContactViewType.Header(it)
is User -> ContactViewType.ContactUser(it)
else -> ContactViewType.Header("error")
}
}
여기서 뷰타입을 지정해주면 끝
여기서 숫자는 한글순으로 비교해도 숫자가 더 앞서기 때문에 추가적으로 정렬을하는 함수를 써야할것 같다고 느꼈지만 일단 생략
오른쪽위 아이콘을 보면 그리드 배열로 바꿔주는 아이콘이 있는데
이걸 그냥 넣으면
헤더 | 유저 | 유저 | 유저 |
---|---|---|---|
유저 | 유저 |
처럼 이상하게 된다
헤더는 한줄을 전부 채우고 유저들이
헤더 | |||
---|---|---|---|
유저 | 유저 | 유저 | 유저 |
이런식으로 되게하려면 Grid레이아웃 매니저에서
spansize를 변경시켜줘야한다
spansize를 간략하게 설명한다면
그리드 레이아웃에서 이 녀석이 몇칸을 차지하는지 정의하는것이라고 보면된다
예를 들어 spancount가 4면 각행에 들어갈 수 있는 아이템의 갯수는 기본적으로 4칸인데
spansize를 2로설정하면 혼자서 2칸을 4로 설정하면 혼자서 모든칸을 차지 할수있다.
앞서 전에 정의한 어댑터 인터페이스를 보면
interface AdapterInterface {
fun updateColor(newColorTheme: ColorTheme)
fun changeLayout(layoutType: LayoutType)
fun notifyDataSetChanged()
fun getItemViewType(position: Int): Int
}
getItemViewType을 받았는데 이걸 어댑터에서 보면
override fun getItemViewType(position: Int): Int {
// return when (mItem[position]) {
return when (filteredList[position]) {
is ContactViewType.ContactUser -> ITEM_VIEW_TYPE_ITEM
is GridUser -> ITEM_VIEW_TYPE_GRID
is ContactViewType.Header -> ITEM_VIEW_TYPE_HEAD
is ContactViewType.MyProfile -> Item_VIEW_TYPE_SELF
}
}
이렇게 리사이클러뷰의 아이템을 정의한걸 볼 수 있다
여기서 각타입을 정의한부분을 보면
companion object {
private const val ITEM_VIEW_TYPE_GRID = 0
private const val ITEM_VIEW_TYPE_ITEM = 1
private const val ITEM_VIEW_TYPE_HEAD = 2
private const val Item_VIEW_TYPE_SELF = 3
}
이런식으로 숫자로 되어있는걸 볼 수있는데
어댑터의 getItemViewType을 불러내면
override fun onLayoutTypeChanged(type: LayoutType) {
val context = binding.recyclerView.context
when (type) {
LayoutType.GRID -> {
val gridManager = GridLayoutManager(context, 4)
gridManager.spanSizeLookup = object :SpanSizeLookup() {
override fun getSpanSize(position: Int): Int {
return when (adapter?.getItemViewType(position)){
0 -> 1
else -> 4
}
}
}
binding.recyclerView.layoutManager = gridManager
}
else -> {
binding.recyclerView.layoutManager = LinearLayoutManager(context)
}
}
}
이런식으로 adapter?.getItemViewType(position) 타입의 숫자를 받고 값이 0이면 아이템 하나당 1칸
타입의 숫자가 0이아니면 아이템 하나당 4칸을 차지하게해서
그리드 배열에서도
이런식으로 헤더를 구별해서 나타낼 수 있다.