[Android] 리사이클러 뷰 아이템에 즐겨찾기 추가하기
개요
안녕하세요!
저는 지금 학교에서 학교 웹 페이지의 공지사항을 크롤링해서 리사이클러 뷰 아이템으로 띄워주는 작업을 하고 있는데요, 여기에 즐겨찾기 기능을 추가하는 포스팅을 작성해보고자 합니다!
인터넷에 한 번에 쭉 정리되어 있는 자료들이 없더라구요ㅠㅠ
그럼 각설하고 코드를 보겠습니다! (주석이 달려있는 부분의 코드들을 보시면 됩니다.)
즐겨찾기된 아이템을 출력할 공간 만들기
위에서 보면 알 수 있듯이, 저는 탭 레이아웃에 [내 공지] 탭을 추가할 것입니다.
- NoticeActivity.kt
...
class NoticeActivity : AppCompatActivity() {
companion object{
...
/* 탭 클릭 시 가져올 데이터 */
const val COMMON_TAB = "[일반]"
const val BACHELOR_TAB = "[학사]"
const val STUDENT_TAB = "[학생]"
const val ENROLL_TAB = "[등록/장학]"
const val MY_TAB = "즐겨찾기" // 상수 선언
}
val binding by lazy{ActivityNoticeBinding.inflate(layoutInflater)}
var helper: RoomHelper? = null
lateinit var adapter: NoticeRecyclerAdapter
// 수정할 데이터를 임시 저장할 프로퍼티
var updateNotice: NoticeItem? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(binding.root)
helper = Room.databaseBuilder(this, RoomHelper::class.java, "notice_item")
.allowMainThreadQueries().build()
adapter = NoticeRecyclerAdapter()
adapter.helper = helper
// 어댑터에 this(본인 액티비티) 전달
adapter.noticeActivity = this
for (category in arrayOf(0,1,2,4)) {
for (page in 1..MAX_PAGE) {
var thread: Thread
if (page == 1) {
thread = Thread(UrlRun(PAGE1_FRONT_BASE_URL+"$category"+PAGE1_BACK_BASE_URL, page, applicationContext))
} else {
thread = Thread(UrlRun(AFTER_PAGE2_FRONT_BASE_URL+"$page"+ AFTER_PAGE2_BACK_BASE_URL+"$category",page,applicationContext))
}
thread.start()
thread.join()
}
}
Log.d("NoticeActivity/OnCreate", "웹 크롤링 완료")
var data: MutableList<NoticeItem>
data = loadData(COMMON_TAB)
setData(data)
// TODO: 탭 리스너 - Room에서 가져올 데이터 지정
binding.tabLayout.addOnTabSelectedListener(object: TabLayout.OnTabSelectedListener{
override fun onTabSelected(tab: TabLayout.Tab?) {
when(tab?.position){
0 -> {
data = loadData(COMMON_TAB)
}
1 -> {
data = loadData(BACHELOR_TAB)
}
2 -> {
data = loadData(STUDENT_TAB)
}
3 -> {
data = loadData(ENROLL_TAB)
}
4 -> { // 내 공지 탭
data = loadData(MY_TAB)
}
}
setData(data)
}
override fun onTabUnselected(tab: TabLayout.Tab?) {
}
override fun onTabReselected(tab: TabLayout.Tab?) {
}
})
}
// 리사이클러 뷰에 아이템 출력하기
private fun setData(data: List<NoticeItem>){
adapter.listData.clear()
adapter.listData.addAll(data)
adapter.notifyDataSetChanged()
binding.recyclerViewNotice.adapter = adapter
binding.recyclerViewNotice.layoutManager = LinearLayoutManager(this)
}
// Room에 저장된 아이템 리스트 불러오기
private fun loadData(category: String): MutableList<NoticeItem>{
var data: MutableList<NoticeItem> = mutableListOf()
// TODO: Room의 데이터 가져오기
if (category == MY_TAB){
data = helper?.noticeItemDAO()?.getFavoriteData()!!
}else {
data = helper?.noticeItemDAO()?.getCategoryData(category)!!
}
return data
}
// 웹 크롤링 스레드 클래스
inner class UrlRun(var url: String, var pages: Int, var context: Context): Runnable{
@Synchronized
override fun run() {
try{
val noticeHtml = Jsoup.connect(url).get()
val items = noticeHtml.select(ITEM_ROUTE)
// 가져올 정보
var url: String // 주소
var category: String // 카테고리
var title: String // 제목
var info: String // 정보
for (item in items){
// url, category, title, info 파싱
// 즐겨찾기는 false로 초기값 설정
url = KW_URL + item.select("a").attr("href")
category = item.select("strong.category").text()
title = item.select("a").text().split("]").last()
info = item.select("p.info").text()
val noticeItem = NoticeItem(url, category, title, info, "false")
helper?.noticeItemDAO()?.insert(noticeItem)
Log.i("NoticeActivity/UrlRun", "$url, $category, $title, $info, 'false'")
}
}
catch(e: Exception){
Log.e("NoticeActivity/UrlRun", e.toString())
}
}
}
}
- activity_notice.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".NoticeActivity">
<com.google.android.material.tabs.TabLayout
android:id="@+id/tabLayout"
android:layout_width="409dp"
android:layout_height="wrap_content"
android:layout_marginStart="1dp"
android:layout_marginEnd="1dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<com.google.android.material.tabs.TabItem
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="일반" />
<com.google.android.material.tabs.TabItem
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="학사" />
<com.google.android.material.tabs.TabItem
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="학생" />
<com.google.android.material.tabs.TabItem
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="등록/장학" />
<com.google.android.material.tabs.TabItem
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="내 공지" />
</com.google.android.material.tabs.TabLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerViewNotice"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_margin="4dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tabLayout" />
</androidx.constraintlayout.widget.ConstraintLayout>
아이템 레이아웃에 즐겨찾기 아이콘 추가하기
- notice_recycler_item.xml
즐겨찾기를 표시하기 위한 방법으로는 ‘이미지 버튼’을 사용하였고, 저기에 나타난 이미지는 안드로이드 기본 리소스인 @android:drawable/btn_star_big_off
입니다.
그리고 이 아이콘의 크기가 자동으로 적절히 조정되도록 scaleType=fitCenter
로 지정합니다.
데이터베이스에 컬럼과 수정 메서드 추가하기
여기서는 Room 라이브러리를 이용합니다.
- NoticeItem.kt
...
// NoticeActivity에서 사용할 테이블을 나타내는 클래스
@Entity(tableName = "notice_item")
class NoticeItem {
@ColumnInfo
@PrimaryKey(autoGenerate = true)
var no: Long? = null
@ColumnInfo
var url: String = ""
@ColumnInfo
var category = ""
@ColumnInfo
var title = ""
@ColumnInfo
var info = ""
// 즐겨찾기 컬럼 추가
@ColumnInfo
var favorite = ""
// 생성자 작성하기
constructor(url: String, category: String, title: String, info: String, favorite: String){
this.url = url
this.category = category
this.title = title
this.info = info
// 즐겨찾기 여부
this.favorite = favorite
}
}
- NoticeItemDAO.kt
...
// NoticeActivity와 NoticeItem(Room)을 연결해주는 인터페이스
@Dao
interface NoticeItemDAO {
@Query("select * from notice_item")
fun getAll(): MutableList<NoticeItem>
@Query("select * from notice_item where category=:category")
fun getCategoryData(category: String): MutableList<NoticeItem>
// 즐겨찾기 공지 조회
@Query("select * from notice_item where favorite=\"true\"")
fun getFavoriteData(): MutableList<NoticeItem>
// REPLACE를 import 할 때는 androidx.room 패키지로 시작하는 것을 선택
// 동일한 키를 가진 값이 입력되었을 경우 UPDATE 쿼리로 실행
@Insert(onConflict = REPLACE)
fun insert(noticeItem: NoticeItem)
@Delete
fun delete(noticeItem: NoticeItem)
// 즐겨찾기는 값이 바뀌기 때문에 update 메서드 추가
@Update
fun update(memo: NoticeItem)
}
어댑터 설정하기
이제 마지막으로 어댑터에 코드를 추가해주면 됩니다.
...
class NoticeRecyclerAdapter: RecyclerView.Adapter<NoticeRecyclerAdapter.NoticeRecyclerHolder>() {
var listData = mutableListOf<NoticeItem>()
var helper: RoomHelper? = null
// 데이터 수정을 위해서 NoticeActivity 프로퍼티 생성
var noticeActivity: NoticeActivity? = null
inner class NoticeRecyclerHolder(val binding: NoticeRecyclerItemBinding): RecyclerView.ViewHolder(binding.root){
var tmpNoticeItem: NoticeItem? = null
// 아이템에 데이터를 세팅
fun setNoticeItem(item: NoticeItem){
binding.textCategory.text = item.category
binding.textTitle.text = item.title
binding.textInfo.text = item.info
// 즐겨찾기 버튼 세팅
if(item.favorite == "true"){
binding.btnFavorite.setImageResource(android.R.drawable.btn_star_big_on)
}else{
binding.btnFavorite.setImageResource(android.R.drawable.btn_star_big_off)
}
// 즐겨찾기 버튼 클릭 시 즐겨찾기 등록/해제 및 아이콘 변경
binding.btnFavorite.setOnClickListener {
if(item.favorite == "true"){
binding.btnFavorite.setImageResource(android.R.drawable.btn_star_big_off)
item.favorite = "false"
Toast.makeText(binding.root.context,
"즐겨찾기 해제되었습니다.",
Toast.LENGTH_SHORT).show()
}else{
binding.btnFavorite.setImageResource(android.R.drawable.btn_star_big_on)
item.favorite = "true"
Toast.makeText(binding.root.context,
"즐겨찾기 등록되었습니다.",
Toast.LENGTH_SHORT).show()
}
// 데이터 변경 알리기
helper?.noticeItemDAO()?.update(item)
notifyDataSetChanged()
}
itemView.setOnClickListener{
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(item.url))
binding.root.context.startActivity(intent)
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): NoticeRecyclerHolder {
val binding = NoticeRecyclerItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return NoticeRecyclerHolder(binding)
}
override fun onBindViewHolder(holder: NoticeRecyclerHolder, position: Int) {
val noticeItem = listData.get(position)
holder.setNoticeItem(noticeItem)
}
override fun getItemCount(): Int {
return listData.size
}
}
정리
지금까지 코드로 즐겨찾기 기능을 추가하는 법을 보았습니다!
흐름을 정리하면 다음과 같습니다.
- 즐겨찾기된 아이템을 출력할 탭(목록) 생성
- 아이템 레이아웃에 즐겨찾기 아이콘 추가하기
- DB에 컬럼을 추가하고 수정 메서드 추가하기
- 어댑터에 코드 추가하기
다들 도움이 되셨기를 바랍니다~
Leave a comment