Fragment中的RecycleView的轉(zhuǎn)場動畫與保存焦點(diǎn)

Fragment中的RecycleView的轉(zhuǎn)場動畫與保存焦點(diǎn)

這一周鄙渣都被一個需求困擾,這個需求就是給FAQ列表添加一個shareElement的轉(zhuǎn)場過度動畫,并且在切回來的時候保存焦點(diǎn)。其中FAQ列表是Fragment下的一個RecycleView,F(xiàn)AQ詳情頁是另一個Fragment。

實(shí)際上這個需求完成非常的簡單,貼一下代碼

fun showDetailFragment(position: Int, faqInfo: FaqInfo, view: View) {
    val faqContentFragment = FaqContentFragment().apply {
        faqTitle = faqInfo.title
        faqContent = faqInfo.content
        transitionName = "faqTitle$position"
    }
    //SDK21以上設(shè)置共享內(nèi)容的轉(zhuǎn)場動畫
    //21以下轉(zhuǎn)場動畫需要在這設(shè)置,不然無進(jìn)入的動畫效果
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
        fragmentManager?.apply {
            beginTransaction()
            .addSharedElement(view, ViewCompat.getTransitionName(view)
                              ?: "faqTitle$position")
            .replace(R.id.fl_faq_container, faqContentFragment)
            .addToBackStack(null)
            .commit()
        }
    } else {
        fragmentManager?.apply {
            beginTransaction()
            .setTransition((FragmentTransaction.TRANSIT_FRAGMENT_FADE))
            .addToBackStack(null)
            .commit()
        }
    }
}

接下來說一說在其中踩過的坑

使用supportFragmentManagerfragmentManager的效用不一致

這個問題鄙渣也只是知其然不知其所以然,只知道效果確實(shí)不一樣。

鄙渣使用的均是androidx.fragment.app下面的FragmentManager類,不存在一個是被deprecated的v4包中的getFragmentManager(),一個是support包下的getSuppotrFragmentManager()的的情況。

前者是在activity中獲得的supportFragmentManager,后者是在fragment中獲得的fragmentManager。

并且通過hashcode()進(jìn)行比對,兩個fragmentManager擁有一樣的hashCode。但是前者就是無法正確添加SharedElement。

如果有大佬看到這篇文章,麻煩告訴一下鄙渣具體原因。或者之后鄙渣研究出來了再貼結(jié)果。

保存RecycleView跳轉(zhuǎn)前的焦點(diǎn)

這個需求大多數(shù)開發(fā)者都用不上,只有鄙渣在的這個行當(dāng)需要,也簡單地說一下思路。一開始鄙渣想的是利用hideshow對第一個Fragment進(jìn)行操作,第二個Fragment手動addremove。并且覆寫onBackPressed(),根據(jù)Fragment的存在與否決定增刪改等操作。

這個思路并沒有完全解決焦點(diǎn)的問題,依舊需要通過存一個全局變量來保存焦點(diǎn),從而在view繪制后重新調(diào)用requestFocus()。

但這樣一來就完全可以使用replaceaddToBackStack操作,不需要進(jìn)行上述那么繁瑣的操作。

但是這個時候又出現(xiàn)了另外一個問題,即使是在Fragment的onStart回調(diào)中,RecycleView也并沒有繪制,其中獲取到的ChildView數(shù)量為0。查閱了網(wǎng)上的資料,有一種比較麻煩的方法進(jìn)行實(shí)現(xiàn),就是使用viewTreeObserver。給RecycleView綁定一個onDrawListner,即可在此時找到子view。

只是此處也是伴隨著一個棘手的問題:何時解綁onDrawListner。如果不進(jìn)行解綁,每一次滑動RecycleView都會進(jìn)行一次繪制,那么焦點(diǎn)就被固定死了,無法移動;規(guī)范要求也不能在onDrawListner內(nèi)部進(jìn)行解綁操作;如果進(jìn)行delay后再解綁,那我也一樣可以delay后再找到焦點(diǎn),一點(diǎn)也不優(yōu)雅精致。最后我放棄了這種方法。

接下來是最終使用的方法,在viewHolder內(nèi)部進(jìn)行操作,貼一下代碼

class FaqAdapter(val data: List<FaqInfo>, private val faqItemClickListener: FaqItemClickListener) : RecyclerView.Adapter<FaqViewHolder>() {

    var thisRecyclerView: RecyclerView? = null

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FaqViewHolder {
        val itemView: View = LayoutInflater.from(parent.context).inflate(R.layout.faq_item, parent, false)
        return FaqViewHolder(itemView)
    }

    override fun getItemCount(): Int {
        return data.size
    }

    override fun onBindViewHolder(holder: FaqViewHolder, position: Int) {
        holder.setText(R.id.tv_faq_item_title, data[position].title)
        val faqItemTitle = holder.getView<TextView>(R.id.tv_faq_item_title)
        val faqItemContainer = holder.itemView.findViewById<ConstraintLayout>(R.id.cl_faq_item_container)

        /*
        * 設(shè)置轉(zhuǎn)場動畫所需
        * */
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            faqItemTitle.transitionName = "faqTitle$position"
        }
        /*
        * 點(diǎn)擊事件的回調(diào)
        * */
        faqItemContainer.setOnClickListener {
            faqItemClickListener.onFaqItemClickListener(position, data[position], faqItemTitle)
        }
        /*
        * 回來recycleView找到上次的焦點(diǎn)
        * */
        if (position == latestPosition) {
            thisRecyclerView?.smoothScrollToPosition(latestPosition)
            faqItemContainer.requestFocus()
        }
        /*
        * 記錄焦點(diǎn)變換的位置
        * */
        faqItemContainer.setOnFocusChangeListener { _, hasFocus ->
            if (hasFocus) {
                latestPosition = position
            }
        }
    }

    override fun onAttachedToRecyclerView(recyclerView: RecyclerView) {
        super.onAttachedToRecyclerView(recyclerView)
        thisRecyclerView = recyclerView
    }

    override fun onDetachedFromRecyclerView(recyclerView: RecyclerView) {
        super.onDetachedFromRecyclerView(recyclerView)
        thisRecyclerView = null
    }

    companion object {
        var latestPosition = 0
    }

}

其實(shí)代碼已經(jīng)很清晰明朗了。首先在attach的時候獲取此時的RecycleView(detach的時候同樣要記得施放),然后在綁定ViewHolder的時候進(jìn)行判斷,如果是記錄中的位置,那么就滑動到該位置并獲取焦點(diǎn)。并且附帶一個焦點(diǎn)變化監(jiān)聽器,如果某個位置的view獲得了焦點(diǎn),就記錄該控件的位置。這樣一來代碼就十分簡潔優(yōu)雅了。

動畫的進(jìn)入效果消失

這個其實(shí)是一個版本問題。由于往上要兼容到8.0,向下最低兼容到4.4,而sharedElement是21(5.0)后才有,所以之前需要統(tǒng)一設(shè)置成淡入淡出的效果。而此時卻有一個莫名其妙的問題:在Fragment中的設(shè)置對進(jìn)入動畫不生效。貼一下設(shè)置代碼。

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
        sharedElementEnterTransition = DetailTransition()
        sharedElementReturnTransition = DetailTransition()
    }
    allowEnterTransitionOverlap = true
    allowReturnTransitionOverlap = true
    enterTransition = Fade()
    returnTransition = Fade()
    exitTransition = Fade()
}

實(shí)際上這個的解決方法并不是難題,低版本有自己對應(yīng)的設(shè)置方法。鄙渣疑惑的地方是,退出動畫生效,那么為什么進(jìn)入動畫會不生效?這個問題鄙渣也沒有研究出結(jié)果,希望有大佬能夠提點(diǎn)一下,順帶貼一下現(xiàn)在的解決設(shè)置。

fragmentManager?.apply {
    beginTransaction()
    .setTransition((FragmentTransaction.TRANSIT_FRAGMENT_FADE))
    .addToBackStack(null)
    .commit()
}

其中setTransition((FragmentTransaction.TRANSIT_FRAGMENT_FADE))就是設(shè)置動畫效果的語句。

最后貼一下示意圖

transition.gif
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容