level 2
2-1
1.리사이클러뷰 항목마다 이벤트 리스너 달아주기
각 항목마다 클릭 이벤트 리스너 달아서 이벤트를 처리해보자
ViewHolder 혹은 onBindViewHolder() 함수 두곳에서 이벤트 처리 하는 방법이 있는데
우선 viewholder에서 처리해주는것부터 부터 봐볼것이다.
1-1ViewHolder에서 처리
우리가 viewbinding 을 이용해서 viewHolder를 만들었기에 ViewHolder의 생성자로 binding객체를 꽂아줬다.
그래서 이 binding객체의 root가 리사이클러뷰가 표현하는 항목하나 즉 item의 레이아웃에 접근할수있다
그래서 init함수를 만들어 root에 onClicklistener를 추가한다.
1-2 onBindViewHolder() 에서 처리
이 함수 안에서도 item에 대한 클릭 리스너를 정의할수있다. 하지만 난 앞전의 방법을 사용했다
결과적으로 Holder 클래스 내부에서 사용하는것과 같다.
2.리사이클러 뷰 어댑터에서 startActivity해보기
리사이클러 뷰에서 아이템을 클릭해서 이벤트를 발생까지는 시켰는데 원하는 이벤트가 새로운 액티비티 실행일 경우
어댑터에서 이벤트를 실행시켜주고있기에 기존에 액티비티에서 Intent에 넣어줬던 인자들을 그대로 넣어주면 안된다.
일단 clickListener안에 intent추가하고 첫번째 인자로 binding.root에 context가 있기에 그걸 사용한다.
그래서 컨텍스트를 맞춰서 넣어주고 두번째 인자로 이동하려는 액티비티 넣고
startActivity함수에 첫번째 인자로 binding.root의 context, 두번째 인자로 intent, 세번째로 별다른 옵션이없다면 null을 입력하면된다.
3. activity에서 activity로 데이터 옮기기
데이터 보내기
첫번째 방법
// 제일 단순하고 쉬운 방법
val intent = Intent(this,옮겨갈 액티비티::class.java)
intent.putExtra("num1",1) //데이터 넣기
intent.putExtra("num2",2) //데이터 넣기
startActivity(intent)
두번째 방법
val intent = Intent(this@Intent1,Intent2::class.java).apply {
this.putExtra("num1",1) // 데이터 넣기
this.putExtra("num2",2) // 데이터 넣기
}
startActivity(intent)
//코틀린의 유용한 기능!🤩 apply
//한눈에 모아서 볼 수 있어서 유용한 듯
데이터 받기
val number1 = intent.getIntExtra("num1", 0)
val number2 = intent.getIntExtra("num2", 0)
4. Activity에서 Fragment로 데이터 주고 받기
데이터 보내기
var fragment2 = Fragment2()
var bundle = Bundle()
bundle.putInt("num1",1)
bundle.putInt("num2",2)
fragment2.arguments = bundle //fragment의 arguments에 데이터를 담은 bundle을 넘겨줌
activity?.supportFragmentManager!!.beginTransaction()
.replace(R.id.view_main, fragment2)
.commit()
데이터 받기
val num1 = arguments?.getInt("num1")
val num2 = arguments?.getInt("num2")
5.텍스트 줄간격
안드로이드 TextView 줄간격
TextView내의 줄간격을 android:lineSpacingExtra 으로 조절이 가능함.
<TextView
….
android:lineSpacingExtra=”20dp”
/>
코드로는 textView.setLineSpacing(); 구현.
2-2
itemDecoration 활용해서 구분선과 간격주기
xml파일에서 margin이나 구분선을 어느정도 만들수있지만 이거는 정확히말하자면 상하단 끝쪽에 margin은 한번만 들어가고 나머지 중간부분은 두번씩 들어가는문제
구분선은 xml내에서 view를 추가해서 넣으면 레이아웃에 불필요한 뷰를 추가함으로써 레이아웃 계층이 증가하고 그에따라 성능에 안좋은 영향을 미치며
좌우로 스와이프 하는 애니메이션이있다면 구분선이 같이 움직인다.
그래서 itemDecoration을 사용하는데 itemDecoration 클래스는 Recyclerview 내부에 있는 추상 클래스이다.
이름처럼 RectclerView의 아이템들을 꾸미는 역할을 한다.
사실 커스텀 하는대로 많은 기능들을 구현할 수 있으므로 하고싶은게 있으면 구글링해서 사용해야겠다 근데 왜 죄다 예제코드가 자바냐고!!!!!!!!!!!!!
대표적으로 구분선이나 여백을 넣는데 많이들 사용한다.
내부 함수가 3가지가 있는데
1.onDraw
아이템이 그려지기 전에 호출됨으로 아이템(viewholder)보다 아래에 위치하게된다 아이템과 onDraw가 그리는 것이 겹친다면 아이템이 덮어씌워서 onDraw가 그리는 것이 안보인다.
2.onDrawOver
아이템이 그려지고 난다음에 호출되는 함수로 이거는 겹친다면 아이템을 가릴수있다.
3.getItemOffsets
각 항목을 배치할때 호출한다 -> margin을 줄때 사용
outRect에 원하는 크기에 간격을 (왼쪽, 위쪽, 오른쪽, 아래쪽) 의 4개 필드에 설정해준다.
그래서 일단 만들어보자.
구글링하면 예제 겁나게 많다 복잡한거는 더더욱많다. 그리고 뭔소리인지 이해하기 좀 난해하다
그냥 쉬운 것들로 적용해보며 필요할때 마다 만들어서 사용해 나가고 난이도를 조금씩 높여야겠다.
가장쉬운 margin만들기
package changhwan.experiment.sopthomework
import android.graphics.Rect
import android.view.View
import androidx.recyclerview.widget.RecyclerView
class CustomMarginDecoration(private val padding: Int) : RecyclerView.ItemDecoration() {
override fun getItemOffsets(
outRect: Rect,
view: View,
parent: RecyclerView,
state: RecyclerView.State
) {
super.getItemOffsets(outRect, view, parent, state)
outRect.top = padding
outRect.bottom = padding
outRect.left = padding
outRect.right = padding
}
}
따로 class파일을 하나파서 itemDecoration을 상속받은후
생성자로 띄울 값을 받아서
getItemOffsets를 오버라이딩 할때 사방 top,bottom,left,right에 생성자로 받은 값을 넣어서 margin을 확보했다.
적용은 적용시킬 리사이클러뷰에다
binding.followerRecycle.addItemDecoration(CustomMarginDecoration(50))
이런식으로 addItemDecoration으로 넣어주면된다.(생명주기상 화면이 그려지고 난후에 실행시켜야하는것 같다)
가장쉬운 구분선 만들기
package changhwan.experiment.sopthomework
import android.graphics.Canvas
import android.graphics.Paint
import androidx.annotation.ColorInt
import androidx.recyclerview.widget.RecyclerView
class CustomDividerDecoration(private val height: Float,private val padding: Float, @ColorInt private val color: Int,private val margin : Int):RecyclerView.ItemDecoration() {
private val paint = Paint()
init{
paint.color = color
}
override fun onDrawOver(c: Canvas, parent: RecyclerView, state: RecyclerView.State) {
val left = parent.paddingStart + padding
val right = parent.width - parent.paddingEnd - padding
for (i in 0 until parent.childCount) {
val child = parent.getChildAt(i)
val params = child.layoutParams as RecyclerView.LayoutParams
val top = ( child.bottom.toFloat() + margin)
val bottom = child.bottom.toFloat() + height + margin
c.drawRect(left, top, right, bottom, paint)
}
}
}
구분선이 좀빡세다
for문에 들어가는것들이 뭔소리인가 싶은데 좀 공통적으로 예제마다 거의 겹친다.
몇가지 예제들의 부분부분을 따서 섞어서 사용했다
생성자에 들어가는 height에 따라 구분선의 굵기가 달라지고
padding에 따라 좌우에 여백이생기며
color은 색깔 설정이고
margin은 얼마나 띄울지 거리설정이다.
근데 이런것도 내가 뭐넣을지 알아서 결정이다 어짜피.
2-3
recyclerview의 drag&drop swipe to Dismiss 구현
ItemTouchHelper는 RecyclerView.ItemDecoration의 서브 클래스이다. RecyclerView 및 Callback 클래스와 함께 작동하며, 사용자가 이러한 액션을 수행할 때 이벤트를 수신한다. 우리는 지원하는 기능에 따라 메서드를 재정의해서 사용하면 된다.
ItemTouchHelper.Callback은 추상 클래스로 추상 메서드인 getMovementFlags(), onMove(), onSwiped()를 필수로 재정의해야 한다. 아니면 Wrapper 클래스인 ItemTouchHelper.SimpleCallback을 이용해도 된다.
이제 순서대로 구현하는것을 쫒아가보자
1.ItemDragListener.kt 만들기
interface ItemDragListener {
fun onStartDrag(viewHolder :RecyclerView.ViewHolder)
}
사용자가 Drag 액션을 시작할 때 itemTouchHelper에 이벤트를 전달한다.
2.ItemActionListener.kt 만들기
interface ItemActionListener {
fun onItemMoved(from: Int, to: Int)
fun onItemSwiped(position: Int)
}
아이템이 Drag & Drop 됐거나 Swiped 됐을 때 어댑터에 이벤트를 전달한다.
3.adapter에서 ItemActionListener 인터페이스를 구현
class FollowerAdapter(private val listener: ItemDragListener) :
RecyclerView.Adapter<FollowerAdapter.FollowerViewHolder>(), ItemActionListener {
//...
override fun onItemMoved(from: Int, to: Int) {
if (from == to) {
return
}
val fromItem = followerData.removeAt(from)
followerData.add(to, fromItem)
notifyItemMoved(from, to)
}
override fun onItemSwiped(position: Int) {
followerData.removeAt(position)
notifyItemRemoved(position)
}
}
어댑터에서는 ItemActionListener 인터페이스를 구현한다. onItemMoved(), onItemSwiped()을 재정의하여 아이템 이동과 제거 코드를 작성한다. 이때 어댑터가 아이템 변경 사항을 인식할 수 있도록 notifyItemMoved(), notifyItemRemoved()를 호출해야 한다.
4.viewAdapter에서 OnTouchListener달아주기
inner class FollowerViewHolder(
private val binding: FollowerItemBinding,
listener: ItemDragListener
) : RecyclerView.ViewHolder(binding.root) {
init {
binding.root.setOnTouchListener { v, event ->
if (event.action == MotionEvent.ACTION_DOWN) {
listener.onStartDrag(this)
}
false
}
}
}
어댑터 생성자의 파라미터로 받은 ItemDragListener는 뷰홀더에서 사용된다. 여기서는 드래그 핸들을 통한 아이템 이동을 구현하고자 하기 때문에, 드래그 핸들 뷰에 터치 리스너를 달아준다. 그리고 사용자가 ACTION_DOWN 액션을 취했을 때 listener.onStartDrag()를 호출한다.
5.ItemTouchHelperCallback.kt 작성
package changhwan.experiment.sopthomework
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView
class ItemTouchHelperCallback(val listener: ItemActionListener) : ItemTouchHelper.Callback() {
override fun getMovementFlags(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder
): Int {
val dragFlags = ItemTouchHelper.DOWN or ItemTouchHelper.UP
val swipeFlags = ItemTouchHelper.START or ItemTouchHelper.END
return makeMovementFlags(dragFlags,swipeFlags)
}
override fun onMove(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder,
target: RecyclerView.ViewHolder
): Boolean {
listener.onItemMoved(viewHolder!!.adapterPosition, target!!.adapterPosition)
return true
}
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
listener.onItemSwiped(viewHolder!!.adapterPosition)
}
override fun isLongPressDragEnabled(): Boolean = true
}
ItemTouchHelper.Callback을 상속받는 ItemTouchHelperCallback 클래스를 구현한다. 생성자의 파라미터로 ItemActionListener를 받는다.
5-1 우선 getMovementFlags()를 재정의해 Drag 및 Swipe 이벤트의 방향을 지정한다.
5-2 아이템이 Drag 되면 ItemTouchHelper는 onMove()를 호출한다. 이때 ItemActionListener로 어댑터에
fromPosition과 toPosition을 파라미터와 함께 콜백을 전달한다.
5-3 아이템이 Swipe 되면 ItemTouchHelper는 범위를 벗어날 때까지 애니메이션을 적용한 후 onSwiped()를 호출한다.
이때 ItemActionListener로 어댑터에 제거할 아이템의 position을 파라미터와 함께 콜백을 전달한다.
5-4 isLongPressDragEnabled()은 아이템을 길게 누르면 Drag & Drop 작업을 시작해야 하는지를 반환한다. 디폴트는
true이다
6.사용처(activity 혹은 fragment) 에서 액티비티에서는 ItemDragListener 인터페이스를 구현
class FollowerFragment : Fragment(), ItemDragListener {
private lateinit var itemTouchHelper : ItemTouchHelper
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
itemTouchHelper = ItemTouchHelper(ItemTouchHelperCallback(followerAdapter))
itemTouchHelper.attachToRecyclerView(binding.followerRecycle)
}
override fun onStartDrag(viewHolder: RecyclerView.ViewHolder) {
//이부분 참고한 블로그와 다르게 아무것도 없어야지만 돌아간다 이해안됨 이부분은 추가적인 공부해야겠다
}
}
이렇게하면 원했던 기능들을 구현할수있다.
Level3
3-1
보일러 플레이트 코드 어떻게 잡을것인가?
딱히 당장 많이 개선할부분이 없는데 복잡해서 방법만 알아두고 가야겠다.
어노테이션 프로세서 같은걸 사용해서 자동화 작업을 하는것이 좋다지만 너무 복잡하다 당장 이거하다 머리터진다.
그래서 복잡하지 않은 방법을 봤다
base코드 즉 BaseActivity, BaseFragment같은 코드들을 만들어놓고 상속해서 사용하는것이다.
장단점을 살펴보면
장점
-데이터 바인딩뿐만 아니라 Activity들이 공통적으로 수행해야 하는 코드가 있다면 BaseActivity를 이용할 수 있다.따라서
-Bolierplate 코드가 줄어든다.
단점
-AppCompatActivity가 아니라 특정한 Activity를 상속해야만 하는 경우가 있다.
=> 어쩔 수 없이 BaseActivity를 쓰지 못한다.
-BaseActivity가 변경되면 이를 상속한 모든 Activity들이 변경되는 것이므로 부담이 크다. (Side Effect)
-공동작업을 할 때 다른 사람이 코드를 이해하기 어렵다.
이제 basecode예시 잘 들어놓은 블로그 링크를 걸어놓겠다 나중에 시도해봐야겠다 ㅎㅎ
3-2
notifyDataSetChanged의 문제점!!
리스트 업데이트 하는데 5가지 방법이있다
리스트를 업데이트 하는방법중 가장 큰범위인 리스트의 크기와 아이템이 둘다 변경되는 경우에 사용하면 되는 것인
notifyDataSetChanged를 무지성으로 쓰면 모든경우에 다 적용이야 되겠지만 비효율적으로 움직일것이다.
그러므로 나머지 4가지 방법을 적재적소에 이용해서 자원을 아끼자
관련 방법들을 잘정리해 놓은 블로그가있어 링크로 남겨놓겠다
자근데 이방법 말고 더 참신한거 써보자
바로 DiffUtil 이다.
https://velog.io/@deepblue/RecyclerView%EC%9D%98-notifyDataSetChanged
이블로그에 있는거 그대로 구현해봤는데 뭔가 틀린부분이 분명있을거같다 급하게해서 돌아는가는데
어쨋든 추후에 다른자료들과 비교해보면서 체크해봐야겠다
출처:https://kumgo1d.tistory.com/44
https://dudmy.net/android/2018/05/02/drag-and-swipe-recyclerview/
https://seunghyun.in/android/1/
https://youngest-programming.tistory.com/285
https://todaycode.tistory.com/55
https://velog.io/@deepblue/RecyclerView%EC%9D%98-notifyDataSetChanged
'sopt 세미나 정리 > 과제' 카테고리의 다른 글
4주차 과제중 배운것들 (0) | 2021.11.12 |
---|---|
3주차 과제중 배운것들 (0) | 2021.10.30 |
1주차 과제중 배운것들 (0) | 2021.10.10 |