我正在使用RecyclerView创建抽认卡应用程序。
每个项目都是扩展ConstraintsLayout的自定义视图,并且是可以翻转的卡片。当卡片从屏幕上刷下时,下一张卡片将从RecyclerView中移入。
我已经为自定义视图实现了onSavedInstanceState和onRestoreInstanceState:
class FlashcardView : ConstraintLayout {
override fun onSaveInstanceState(): Parcelable {
Log.d("FlashcardView", "saving instance state")
val savedState = SavedState(super.onSaveInstanceState())
savedState.isFlipped = isFlipped
savedState.side = side
return savedState
}
override fun onRestoreInstanceState(state: Parcelable?) {
Log.d("FlashcardView", "restoring instance state")
if (state is SavedState) {
Log.d("FlashcardView", "is flipped: ${state.isFlipped}, " +
"side: ${state.side}")
super.onRestoreInstanceState(state)
isFlipped = state.isFlipped
if(state.side != side)
flipCard()
}else{
super.onRestoreInstanceState(state)
}
}
internal class SavedState : View.BaseSavedState {
var isFlipped: Boolean = false
var side: Int = 0
constructor(source: Parcel): super(source){
isFlipped = source.readByte().toInt() != 0
side = source.readByte().toInt()
}
constructor(superState: Parcelable) : super(superState)
override fun writeToParcel(destination: Parcel, flags: Int) {
super.writeToParcel(destination, flags)
destination.writeByte((if (isFlipped) 1 else 0).toByte())
destination.writeByte(side.toByte())
}
companion object {
@JvmField
val CREATOR: Parcelable.Creator<SavedState> = object : Parcelable.Creator<SavedState> {
override fun createFromParcel(source: Parcel): SavedState {
return SavedState(source)
}
override fun newArray(size: Int): Array<SavedState?> {
return arrayOfNulls(size)
}
}
}
}
}
我希望自定义视图处理其自身的状态,但是在配置上进行更改时,RecyclerView.Adapter在onCreateViewHolder中创建该自定义视图的新实例(而不是将先前的实例与onBindViewHolder一起使用)。我假设发生这种情况是因为适配器被销毁或具有已创建的Viewholder的池被销毁了。如何保持recyclerview适配器的状态?
CardFlipFragment.kt
class CardFlipFragment: Fragment() {
@Inject
lateinit var viewModelFactory: ViewModelProvider.Factory
private lateinit var viewModel: CardViewModel
@Inject
lateinit var disposable: CompositeDisposable
private val cards = ArrayList<Card>()
private val cardsAdapter = CardsAdapter(cards)
private val DEBUGTAG = "CardFlipFragment"
companion object {
const val deckNameKey = "deckName"
}
override fun onAttach(context: Context?) {
super.onAttach(context)
AndroidSupportInjection.inject(this)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
viewModel = ViewModelProviders.of(activity!!, viewModelFactory).get(CardViewModel::class.java)
setupRecyclerView()
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.activity_card_flip, container, false)
}
override fun onStart() {
super.onStart()
val deckName = arguments!!.getString(deckNameKey)
fetchDeckCards(deckName)
}
override fun onStop() {
super.onStop()
disposable.clear()
}
private fun setupRecyclerView() {
cardRecyclerView.adapter = cardsAdapter
cardRecyclerView.layoutManager = object : LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false) {
override fun smoothScrollToPosition(recyclerView: RecyclerView?, state: RecyclerView.State?, position: Int) {
val smoothScroller = object: LinearSmoothScroller(context){
//return a vector pointing to the next position -> to the card to the right
override fun computeScrollVectorForPosition(targetPosition: Int): PointF? {
return PointF(1F, 0F)
}
}
smoothScroller.targetPosition = position
startSmoothScroll(smoothScroller)
}
}
cardRecyclerView.addOnItemTouchListener(CardRecyclerTouchListener(context!!, cardRecyclerView, object : CardRecyclerTouchListener.ClickListener {
override fun onClick(view: FlashcardView) {
Log.d(DEBUGTAG, "click")
view.flipCard()
}
override fun onLongClick(view: FlashcardView?) {
Log.d(DEBUGTAG, "longclick")
val pos = view!!.tag as Int
val card = cards[pos]
val side = view.side
Log.d(DEBUGTAG, "view pos :" + pos + "card uid: " + card.uid)
//start edit card fragment
val bundle = Bundle()
bundle.putInt("cardUid", card.uid)
bundle.putInt("cardSide", side)
Navigation.findNavController(view).navigate(R.id.action_cardFlipFragment_to_editCardFragment, bundle)
}
override fun onSwipe(view: FlashcardView, swipe_type: Int) {
Log.d(DEBUGTAG, "fling cards: ${cards.size}")
if (view.isFlipped) {
if (swipe_type == CardRecyclerTouchListener.SWIPE_IN) {
animateIn(view)
} else {
animateOut(view) {
val nextCardPos = view.tag as Int + 1
cardRecyclerView.smoothScrollToPosition(nextCardPos)
if (nextCardPos == cards.size)
Navigation.findNavController(view).popBackStack()
}
}
}
}
}))
}
private fun fetchDeckCards(deckName: String) {
disposable.add(
viewModel.getCards(deckName)
.subscribe({ deck ->
if (deck.isEmpty())
startEditCardFragment(deckName)
cards.clear()
cards.addAll(deck)
cardsAdapter.notifyDataSetChanged()
}, { throwable -> Log.e(DEBUGTAG, "Unable to fetch cards", throwable) }))
}
}
CardsAdapter.kt
class CardsAdapter (private val cards: List<Card>) : RecyclerView.Adapter<CardsAdapter.ViewHolder>(){
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
internal val textFront: TextView = itemView.cardFrontTextView
internal val textBack: TextView = itemView.cardBackTextView
internal val imageFront: ImageView = itemView.imageViewFront
internal val imageBack: ImageView = itemView.imageViewBack
internal val cardLayout: FlashcardView = itemView as FlashcardView
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CardsAdapter.ViewHolder {
Log.d("CardsAdapter", "flashcard viewholder created")
val flashcardView = FlashcardView(parent.context)
return ViewHolder(flashcardView)
}
override fun getItemCount(): Int {
return cards.size
}
override fun onBindViewHolder(holder: CardsAdapter.ViewHolder, position: Int) {
val card = cards[position]
holder.cardLayout.tag = position
var tv = holder.textFront
tv.text = card.textFront
tv = holder.textBack
tv.text = card.textBack
}
//reset card so it is ready for reuse
override fun onViewRecycled(holder: ViewHolder) {
super.onViewRecycled(holder)
holder.cardLayout.reset()
}
}