-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[2주차 과제] 안드로이드 UI 구현 심화 #4
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
배우고 갑니다 ㅎㅎ
private fun initRecyclerView(dataSet: ArrayList<MultiViewItem>) { | ||
binding.rvHome.apply { | ||
adapter = MultiViewAdapter(dataSet) | ||
layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
xml recycler view 속성에 layoutManager 이용하셔도 될거 같아요!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
만약 XML에서 사용하실 경우 orientation도 반드시 지정해주셔야 한다는 점 !
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
다들 코멘트 감사합니다!!
import org.android.go.sopt.databinding.ItemTextBinding | ||
|
||
/** sealed class는 추상 클래스로 자식 클래스의 종류를 제한할 수 있다. */ | ||
sealed class MultiViewHolder<E : MultiViewItem>( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sealed class 를 이용해서 하는 방법도 있군요 배워갑니다 ㅎ
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
정말 코드들하고 네이밍이 너무 좋아서 많이 배웠습니다ㅎㅎ
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
저도 이번에 구글링으로 처음 배운 코드들이 대부분인 거 같아요!! 온전히 제가 짠 코드는 아니라는 점,, 참고로 알아주세요!
initBnvItemReselectedListener() | ||
} | ||
|
||
private fun initBnvItemReselectedListener() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
세미나때 하신 말처럼 네이밍이 길어져도 훨씬 좋은 이름이네요 ! 네이밍하는 방법 배우겠습니다 ㅎ
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
컴포넌트의 어떤 리스너를 초기화하는 것인지까지 명시해주니까 훨씬 직관적이네요! 센스 있는 네이밍 방식 배워갑니다 🤭
import androidx.databinding.ViewDataBinding | ||
import androidx.fragment.app.Fragment | ||
|
||
abstract class BindingFragment<T : ViewDataBinding>( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
바인딩 프래그먼트까지 ,,,,, 👍
<variable | ||
name="name" | ||
type="String" /> | ||
|
||
<variable | ||
name="author" | ||
type="String" /> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Repo data class 사용하셔도 될거 같습니다 !
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
코멘트 참고해서 item_github_repo.xml은 삭제하고 item_image.xml 파일만 남겨뒀어요!
import org.android.go.sopt.databinding.ItemTextBinding | ||
|
||
/** sealed class는 추상 클래스로 자식 클래스의 종류를 제한할 수 있다. */ | ||
sealed class MultiViewHolder<E : MultiViewItem>( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
정말 코드들하고 네이밍이 너무 좋아서 많이 배웠습니다ㅎㅎ
override fun onDestroyView() { | ||
super.onDestroyView() | ||
_binding = null | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
큰 문제는 없겠지만, 부모의 onDestroyView가 뷰를 파괴하는 역할이므로, 그 위에 _binding 에 null을 넣어 해제해주는건 어떨까요?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
공식문서 코드를 다시 봤는데 아래처럼 작성되어 있더라구요..!
프래그먼트 뷰가 종료되고 나서도 프래그먼트 객체는 살아있기 때문에, 바인딩으로 인한 메모리 누수를 막으려면 _binding = null
을 설정해줘야 한다는 것으로 알고 있습니다. 그래서 원래대로 작성해도 괜찮지 않을까 하는 게 제 개인적인 의견입니다..!
private var _binding: ResultProfileBinding? = null
// This property is only valid between onCreateView and
// onDestroyView.
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = ResultProfileBinding.inflate(inflater, container, false)
val view = binding.root
return view
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
when(supportFragmentManager.findFragmentById(R.id.fcv_main)){ | ||
is HomeFragment -> { | ||
val recyclerView = findViewById<RecyclerView>(R.id.rv_home) | ||
recyclerView.scrollToPosition(0) | ||
} | ||
is GalleryFragment -> { | ||
val recyclerView = findViewById<RecyclerView>(R.id.rv_gallery) | ||
recyclerView.scrollToPosition(0) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
요 부분에서 findViewById를 사용하지 않고도 가능할까요?
private fun initBnvItemReselectedListener() { | ||
binding.bnvMain.setOnItemReselectedListener { | ||
when(supportFragmentManager.findFragmentById(R.id.fcv_main)){ | ||
is HomeFragment -> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
여기서 item의 id로 프래그먼트를 접근하면 findById를 쓰지않을수있어서 코드양이 적어질 수 있을것같아요 !
import androidx.databinding.ViewDataBinding | ||
import org.android.go.sopt.R | ||
|
||
class MultiViewHolderFactory { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
저는 sealed class로 해놓고 이 코드를 adapter에 전부 넣었는데 viewholderfactory를 이렇게 활용할 수 있군요 ! 저도 참고해서 리팩토링 해보겠습니다 !
<item | ||
android:id="@+id/home_menu" | ||
android:icon="@drawable/ic_home" | ||
android:title="Home" /> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
여기도 string resource 추출하면 더 좋을것같습니다 !
import org.android.go.sopt.ui.main.gallery.adapter.MyItemTouchHelperCallback | ||
import org.android.go.sopt.ui.main.data.DataSources | ||
|
||
/** DiffUtil + ListAdapter 사용해서 리사이클러뷰 성능 개선하기 */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
viewHolder: RecyclerView.ViewHolder, | ||
target: RecyclerView.ViewHolder | ||
): Boolean { | ||
(recyclerView.adapter as MyListAdapter).moveItem( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
위에서 말씀드린 findByViewId 없이 scroll to top 기능을 구현하는것의 힌트는 요것입니다 !
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
고수시군요,, 고생하셨습니다 !
binding: ViewDataBinding | ||
) : RecyclerView.ViewHolder(binding.root) { | ||
|
||
data class TextViewHolder( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이 클래스를 data class로 만들어주신 이유가 무엇인가요?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
구글링해서 나온 코드를 거의 그대로 사용한 거여서,, 저도 무의식적으로 따라서 친 거 같아요😅 뷰홀더는 일반 클래스로 선언하는 게 좋을 거 같아요! 피드백 감사합니다👍👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
직관적이고 효율적인 코드를 작성하려고 노력하시는 게 많이 느껴지네요 👍
이번 주도 과제 하시느라 고생하셨습니다 ~ 👏👏
initBnvItemReselectedListener() | ||
} | ||
|
||
private fun initBnvItemReselectedListener() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
컴포넌트의 어떤 리스너를 초기화하는 것인지까지 명시해주니까 훨씬 직관적이네요! 센스 있는 네이밍 방식 배워갑니다 🤭
if (currentFragment == null) { | ||
supportFragmentManager.beginTransaction() | ||
.add(R.id.fcv_main, HomeFragment()) | ||
.commit() | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if (currentFragment == null) { | |
supportFragmentManager.beginTransaction() | |
.add(R.id.fcv_main, HomeFragment()) | |
.commit() | |
} | |
if (currentFragment == null) changeFragment(HomeFragment()) |
여기서도 changeFragment
함수를 활용할 수 있을 것 같습니다!
private fun changeFragment(fragment: Fragment) { | ||
supportFragmentManager | ||
.beginTransaction() | ||
.replace(R.id.fcv_main, fragment) | ||
.commit() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Android KTX에서 제공되는 Fragment KTX를 사용하면 코드를 경량화하고 불필요한 프래그먼트 객체 생성을 줄일 수 있습니다!
공식문서의 FragmentTransaction 예시에서도 ktx를 활용하고 있습니다. 한번 참고해보시면 좋을 것 같아요 :)
class MyDiffCallback : DiffUtil.ItemCallback<Repo>() { | ||
override fun areItemsTheSame(oldItem: Repo, newItem: Repo): Boolean { | ||
return oldItem === newItem | ||
} | ||
|
||
override fun areContentsTheSame(oldItem: Repo, newItem: Repo): Boolean { | ||
return oldItem == newItem | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
제너릭을 활용하여 다른 ListAdapter를 구현할 때에도 재사용할 수 있도록 구현해봐도 좋을 것 같습니다!
|
||
import androidx.recyclerview.widget.ItemTouchHelper | ||
import androidx.recyclerview.widget.RecyclerView | ||
import org.android.go.sopt.ui.main.gallery.adapter.MyListAdapter |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Optimize imports 단축키와 Reformat code 단축키를 습관화하여 사용하지 않는 import문은 제거하고 코드 가독성을 높이는 것을 추천 드립니다!
Window 기준 Optimize import는 Ctrl + Alt + O
, Reformat code는 Ctrl + Alt + L
입니다.
override fun onMove( | ||
recyclerView: RecyclerView, | ||
viewHolder: RecyclerView.ViewHolder, | ||
target: RecyclerView.ViewHolder | ||
): Boolean { | ||
(recyclerView.adapter as MyListAdapter).moveItem( | ||
viewHolder.adapterPosition, | ||
target.adapterPosition | ||
) | ||
return true | ||
} | ||
|
||
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) { | ||
(recyclerView.adapter as MyListAdapter).removeItem(viewHolder.layoutPosition) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
recyclerView.adapter as MyListAdapter
처럼 as를 활용한 강제 형변환은 예기치 못한 예외가 발생할 수 있기 때문에 지양하는 것이 좋다고 생각합니다. 대체할 수 있는 방법이 뭐가 있을지 찾아보시는 것을 추천 드립니다 ㅎㅎ
) : RecyclerView.Adapter<MultiViewHolder<MultiViewItem>>() { | ||
private val multiViewHolderFactory = MultiViewHolderFactory() | ||
|
||
@Suppress("UNCHECKED_CAST") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이 어노테이션을 추가하신 이유가 뭘까요?
@Suppress
로 컴파일 오류를 무시하기보다는 안전성을 보장하는 다른 방법을 찾아보는 것이 좋다고 생각합니다!
fun getViewHolder(parent: ViewGroup, viewType: MultiViewType): MultiViewHolder<MultiViewItem> { | ||
return when (viewType) { | ||
MultiViewType.TEXT -> | ||
MultiViewHolder.TextViewHolder(viewBind(parent, R.layout.item_text)) | ||
MultiViewType.IMAGE -> | ||
MultiViewHolder.ImageViewHolder(viewBind(parent, R.layout.item_image)) | ||
} as MultiViewHolder<MultiViewItem> | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fun getViewHolder(parent: ViewGroup, viewType: MultiViewType): MultiViewHolder<MultiViewItem> { | |
return when (viewType) { | |
MultiViewType.TEXT -> | |
MultiViewHolder.TextViewHolder(viewBind(parent, R.layout.item_text)) | |
MultiViewType.IMAGE -> | |
MultiViewHolder.ImageViewHolder(viewBind(parent, R.layout.item_image)) | |
} as MultiViewHolder<MultiViewItem> | |
} | |
import org.android.go.sopt.ui.main.home.adapter.MultiViewType.IMAGE | |
import org.android.go.sopt.ui.main.home.adapter.MultiViewType.TEXT | |
fun getViewHolder(parent: ViewGroup, viewType: MultiViewType): MultiViewHolder<MultiViewItem> { | |
return when (viewType) { | |
TEXT -> | |
MultiViewHolder.TextViewHolder(viewBind(parent, R.layout.item_text)) | |
IMAGE -> | |
MultiViewHolder.ImageViewHolder(viewBind(parent, R.layout.item_image)) | |
} as MultiViewHolder<MultiViewItem> | |
} |
사람마다 코드 스타일이 다르지만 저는 상수값 임포트를 통해 코드를 간략화하는 편입니다!
private fun initBnvItemSelectedListener() { | ||
binding.bnvMain.setOnItemSelectedListener { item -> | ||
when (item.itemId) { | ||
R.id.home_menu -> { | ||
changeFragment(HomeFragment()) | ||
true | ||
} | ||
R.id.gallery_menu -> { | ||
changeFragment(GalleryFragment()) | ||
true | ||
} | ||
R.id.search_menu -> { | ||
changeFragment(SearchFragment()) | ||
true | ||
} | ||
else -> false | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
private fun initBnvItemSelectedListener() { | |
binding.bnvMain.setOnItemSelectedListener { item -> | |
when (item.itemId) { | |
R.id.home_menu -> { | |
changeFragment(HomeFragment()) | |
true | |
} | |
R.id.gallery_menu -> { | |
changeFragment(GalleryFragment()) | |
true | |
} | |
R.id.search_menu -> { | |
changeFragment(SearchFragment()) | |
true | |
} | |
else -> false | |
} | |
} | |
} | |
private fun initBnvItemSelectedListener() { | |
binding.bnvMain.setOnItemSelectedListener { item -> | |
when (item.itemId) { | |
R.id.home_menu -> changeFragment(HomeFragment()) | |
R.id.gallery_menu -> changeFragment(GalleryFragment()) | |
R.id.search_menu -> changeFragment(SearchFragment()) | |
else -> return@setOnItemSelectedListener false | |
} | |
true | |
} | |
} |
이렇게 쓸 수도 있을 것 같아요!
아니면 changeFragment를 true를 반환하도록 구현해도 될 것 같네요
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM 👍
} | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이 방법이 더 좋아보여 저도 scroll to top 기능 수정했어요 ㅎㅎ
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
엇 참고가 되었다니 다행이네요!! 제 코드에 관심 가져주셔서 감사합니다🙂
필수 과제
심화 과제
도전 과제
실행 결과
Screen_Recording_20230421_103731_GO.SOPT.Android.mp4