250x250
Notice
Recent Posts
Recent Comments
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
Tags
- 안드로이드 Sunflower 스터디
- 막내의막무가내 안드로이드 에러 해결
- 막내의 막무가내
- 막내의막무가내 코볼 COBOL
- 막내의막무가내 목표 및 회고
- 막내의막무가내 알고리즘
- 안드로이드
- 막무가내
- 2022년 6월 일상
- 막내의막무가내 코틀린
- 막내의 막무가내 알고리즘
- 막내의막무가내 프로그래밍
- 막내의막무가내
- 막내의막무가내 안드로이드 코틀린
- 막내의막무가내 안드로이드
- 주엽역 생활맥주
- 부스트코스에이스
- 막내의막무가내 SQL
- 막내의막무가내 플러터
- 안드로이드 sunflower
- 프로그래머스 알고리즘
- 프래그먼트
- 막내의막무가내 일상
- 부스트코스
- 막내의막무가내 코틀린 안드로이드
- flutter network call
- 막내의막무가내 플러터 flutter
- 막내의막무가내 rxjava
- Fragment
- 주택가 잠실새내
Archives
- Today
- Total
막내의 막무가내 프로그래밍 & 일상
[안드로이드] 코틀린 리사이클러뷰 예제 본문
728x90
[2021-04-13 업데이트]
최근에 사용(?), 작성한 리사이클러뷰 예제는 다음 링크에 있습니다. 참고해주세요 :)
youngest-programming.tistory.com/478
코틀린으로 짠 리사이클러뷰 표본입니다. 기록용으로 작성했습니다.
어댑터 (클릭리스너를 어댑터 생성자로 받아도 됩니다. -> 생성자로 받는게 더 깔끔한것같습니다.)
movieAdapter = MovieAdapter { movie ->
Intent(Intent.ACTION_VIEW, Uri.parse(movie.link)).takeIf {
it.resolveActivity(packageManager) != null
}?.run(this::startActivity)
}
class MovieAdapter(private val clickCallBack: (Movie) -> Unit) :
RecyclerView.Adapter<MovieAdapter.ViewHolder>() {
어댑터에서 context를 받을 필요가 없습니다.
어댑터 생성자로 아이템리스트를 해놓지 않았습니다.
package com.mtjin.androidarchitecturestudy.ui
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.RatingBar
import android.widget.TextView
import androidx.core.text.HtmlCompat
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
import com.mtjin.androidarchitecturestudy.R
import com.mtjin.androidarchitecturestudy.data.Movie
class MovieAdapter :
RecyclerView.Adapter<MovieAdapter.ViewHolder>() {
private lateinit var callback: (Movie) -> Unit
private val items: ArrayList<Movie> = ArrayList()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view: View = LayoutInflater.from(parent.context)
.inflate(R.layout.item_movie, parent, false)
val viewHolder = ViewHolder(view)
view.setOnClickListener {
callback(items[viewHolder.adapterPosition])
}
return viewHolder
}
override fun getItemCount(): Int = items.size
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
items[position].let {
holder.bind(it)
}
}
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
private val ivPoster = itemView.findViewById<ImageView>(R.id.iv_poster)
private val rvRating = itemView.findViewById<RatingBar>(R.id.rb_rating)
private val tvTitle = itemView.findViewById<TextView>(R.id.tv_title)
private val tvReleaseDate = itemView.findViewById<TextView>(R.id.tv_release_date)
private val tvActor = itemView.findViewById<TextView>(R.id.tv_actor)
private val tvDirector = itemView.findViewById<TextView>(R.id.tv_director)
fun bind(movie: Movie) {
with(movie) {
Glide.with(itemView).load(image)
.placeholder(R.drawable.ic_default)
.into(ivPoster!!)
rvRating.rating = (userRating.toFloatOrNull() ?: 0f) / 2
tvTitle.text = HtmlCompat.fromHtml(title, HtmlCompat.FROM_HTML_MODE_COMPACT)
tvReleaseDate.text = pubDate
tvActor.text = actor
tvDirector.text = director
}
}
}
fun add(items: List<Movie>) {
this.items.addAll(items)
notifyDataSetChanged()
}
fun setItemClickListener(callback: (Movie) -> Unit) {
this.callback = callback
}
fun clear() {
this.items.clear()
notifyDataSetChanged()
}
}
액티비티에서 아이템 클릭리스너 정의
//어댑터 아이템 클릭리스너
movieAdapter.setItemClickListener { movie ->
Intent(Intent.ACTION_VIEW, Uri.parse(movie.link)).takeIf {
it.resolveActivity(packageManager) != null
}?.run(this::startActivity)
}
그냥 startActivity를 하면 Intent.ACTION_VIEW 액션을 받는 액티비티가 없으면 예외가 발생할 수 있으므로 위와 같이 액션뷰 인텐트를 실행 하면 안전하다고 합니다.
액티비티 어댑터 세팅 (레이아웃 매니저는 xml에서 세팅해놨습니다.)
class MovieSearchActivity : AppCompatActivity() {
private lateinit var etInput: EditText
private lateinit var btnSearch: Button
private lateinit var rvMovies: RecyclerView
private lateinit var movieAdapter: MovieAdapter
private lateinit var movieCall: Call<MovieResponse>
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_movie_search)
initView()
initListener()
}
private fun initView() {
etInput = findViewById(R.id.et_input)
btnSearch = findViewById(R.id.btn_search)
rvMovies = findViewById(R.id.rv_movies)
movieAdapter = MovieAdapter()
rvMovies.adapter = movieAdapter
}
아이템 레이아웃
<?xml version="1.0" encoding="utf-8"?>
<androidx.appcompat.widget.LinearLayoutCompat 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:weightSum="5">
<ImageView
android:id="@+id/iv_poster"
android:layout_width="0dp"
android:layout_height="100dp"
android:layout_weight="1"
android:scaleType="fitXY" />
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:layout_weight="4"
android:orientation="vertical">
<TextView
android:id="@+id/tv_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="영화제목"
android:textSize="12sp"
android:textStyle="bold" />
<RatingBar
android:id="@+id/rb_rating"
style="@style/Widget.AppCompat.RatingBar.Small"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:isIndicator="true"
android:numStars="5"
android:max="10"
android:stepSize="0.1" />
<TextView
android:id="@+id/tv_release_date"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="2020" />
<TextView
android:id="@+id/tv_director"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="감독" />
<TextView
android:id="@+id/tv_actor"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="배우들" />
</LinearLayout>
</androidx.appcompat.widget.LinearLayoutCompat>
액티비티 레이아웃
<?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"
android:padding="8dp"
tools:context=".ui.MovieSearchActivity">
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintGuide_percent="0.08" />
<EditText
android:id="@+id/et_input"
android:layout_width="0dp"
android:layout_height="0dp"
android:hint="입력"
android:inputType="text"
app:layout_constraintBottom_toTopOf="@+id/guideline"
app:layout_constraintEnd_toStartOf="@+id/btn_search"
app:layout_constraintHorizontal_weight="8"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/btn_search"
android:layout_width="0dp"
android:layout_height="0dp"
android:text="검색"
app:layout_constraintBottom_toTopOf="@id/guideline"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_weight="2"
app:layout_constraintStart_toEndOf="@+id/et_input"
app:layout_constraintTop_toTopOf="parent" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_movies"
android:layout_width="0dp"
android:layout_height="0dp"
android:orientation="vertical"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
android:clipToPadding="false"
app:layout_constraintTop_toBottomOf="@id/guideline" />
</androidx.constraintlayout.widget.ConstraintLayout>
데이터바인딩 적용된 리사이클러뷰
package com.mtjin.androidarchitecturestudy.ui.search
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.databinding.DataBindingUtil
import androidx.recyclerview.widget.RecyclerView
import com.mtjin.androidarchitecturestudy.R
import com.mtjin.androidarchitecturestudy.data.search.Movie
import com.mtjin.androidarchitecturestudy.databinding.ItemMovieBinding
class MovieAdapter(private val itemClick: (Movie) -> Unit) :
RecyclerView.Adapter<MovieAdapter.ViewHolder>() {
private val items: ArrayList<Movie> = ArrayList()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val binding : ItemMovieBinding = DataBindingUtil.inflate(
LayoutInflater.from(parent.context),
R.layout.item_movie,
parent,
false
)
val viewHolder = ViewHolder(binding)
binding.root.setOnClickListener {
itemClick(items[viewHolder.adapterPosition])
}
return viewHolder
}
override fun getItemCount(): Int = items.size
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
items[position].let {
holder.bind(it)
}
}
class ViewHolder(private val binding: ItemMovieBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(movie: Movie) {
binding.movie = movie
binding.executePendingBindings()
}
}
fun addItems(items: List<Movie>) {
this.items.addAll(items)
notifyDataSetChanged()
}
fun clear() {
this.items.clear()
notifyDataSetChanged()
}
}
package com.mtjin.androidarchitecturestudy.utils
import android.widget.ImageView
import android.widget.RatingBar
import android.widget.TextView
import androidx.core.text.HtmlCompat
import androidx.databinding.BindingAdapter
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
import com.mtjin.androidarchitecturestudy.R
import com.mtjin.androidarchitecturestudy.data.search.Movie
import com.mtjin.androidarchitecturestudy.ui.search.EndlessRecyclerViewScrollListener
import com.mtjin.androidarchitecturestudy.ui.search.MovieAdapter
import com.mtjin.androidarchitecturestudy.ui.search.MovieSearchViewModel
@BindingAdapter("htmlText")
fun TextView.setHtmlText(html: String) {
text = HtmlCompat.fromHtml(html, HtmlCompat.FROM_HTML_MODE_COMPACT)
}
@BindingAdapter("urlImage")
fun ImageView.setUrlImage(url: String) {
Glide.with(this).load(url)
.placeholder(R.drawable.ic_default)
.into(this)
}
@BindingAdapter("movieRating")
fun RatingBar.setMovieRating(score: String) {
rating = (score.toFloatOrNull() ?: 0f) / 2
}
@BindingAdapter("setItems")
fun RecyclerView.setAdapterItems(items: List<Movie>?) {
with((adapter as MovieAdapter)) {
this.clear()
items?.let { this.addItems(it) }
}
}
@BindingAdapter("endlessScroll")
fun RecyclerView.setEndlessScroll(
viewModel: MovieSearchViewModel
) {
val scrollListener =
object : EndlessRecyclerViewScrollListener(layoutManager as LinearLayoutManager) {
override fun onLoadMore(page: Int, totalItemsCount: Int, view: RecyclerView?) {
viewModel.requestPagingMovie(totalItemsCount + 1)
}
}
this.addOnScrollListener(scrollListener)
}
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:bind="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="movie"
type="com.mtjin.androidarchitecturestudy.data.search.Movie" />
</data>
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="16dp"
android:weightSum="5">
<ImageView
android:id="@+id/iv_poster"
android:layout_width="0dp"
android:layout_height="100dp"
android:layout_weight="1"
android:scaleType="fitXY"
bind:urlImage="@{movie.image}" />
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:layout_weight="4"
android:orientation="vertical">
<TextView
android:id="@+id/tv_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="12sp"
android:textStyle="bold"
bind:htmlText="@{movie.title}" />
<RatingBar
android:id="@+id/rb_rating"
style="@style/Widget.AppCompat.RatingBar.Small"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:isIndicator="true"
android:max="10"
android:numStars="5"
android:stepSize="0.1"
bind:movieRating="@{movie.userRating}" />
<TextView
android:id="@+id/tv_release_date"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="@{movie.pubDate}" />
<TextView
android:id="@+id/tv_director"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="@{movie.director}" />
<TextView
android:id="@+id/tv_actor"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="@{movie.actor}" />
</LinearLayout>
</androidx.appcompat.widget.LinearLayoutCompat>
</layout>
혹은 다음과 같이 어댑터를 해도 됩니다.
package com.mtjin.cnunoticeapp.views.board_list
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.databinding.DataBindingUtil
import androidx.recyclerview.widget.RecyclerView
import com.mtjin.cnunoticeapp.R
import com.mtjin.cnunoticeapp.data.board_list.Board
import com.mtjin.cnunoticeapp.databinding.ItemBoardBinding
class BoardAdapter :
RecyclerView.Adapter<BoardAdapter.ViewHolder>() {
private val items = mutableListOf<Board>()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val binding: ItemBoardBinding = DataBindingUtil.inflate(
LayoutInflater.from(parent.context),
R.layout.item_board,
parent,
false
)
return ViewHolder(binding).apply {
binding.root.setOnClickListener { view ->
val position = adapterPosition.takeIf { it != RecyclerView.NO_POSITION }
?: return@setOnClickListener
}
}
}
override fun getItemCount(): Int = items.size
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
items[position].let {
holder.bind(it)
}
}
class ViewHolder(private val binding: ItemBoardBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(board: Board) {
binding.item = board
binding.executePendingBindings()
}
}
fun addItems(items: List<Board>) {
this.items.addAll(items)
notifyDataSetChanged()
}
fun addItem(item: Board) {
this.items.add(item)
notifyDataSetChanged()
}
fun clear() {
this.items.clear()
notifyDataSetChanged()
}
}
댓글과 공감은 큰 힘이 됩니다. 감사합니다. !!
728x90
'안드로이드 > 코틀린 & 아키텍처 & Recent' 카테고리의 다른 글
[안드로이드] 코틀린 lateinit var 초기화 되었는지 확인하는 방법 (2) | 2020.03.08 |
---|---|
[안드로이드] 코틀린 Retrofit2 OkHttP interceptor 헤더 사용법 (0) | 2020.03.07 |
[안드로이드] Kotlin Architecture Components (안드로이드 코틀린 아키텍처 구성요소) (1) | 2020.02.22 |
[안드로이드] Kotlin - Databinding, LiveData, viewModel (0) | 2020.02.16 |
[안드로이드] json to kotlin data class ( json을 코틀린 data class 로 변환해주는 플러그인) (2) | 2020.01.25 |
Comments