私はかなり単純なケースがあります。 recyclerView
とfragment
のアイテム間で共有要素の遷移を実装したいと思います。アプリでAndroidナビゲーションコンポーネントを使用しています。
developer.Android の共有トランジションに関する記事と stackoverflow のトピックがありますが、このソリューションは、トランジションを開始するfragment
レイアウトにあるビューに対してのみ機能します。 RecyclerView
のアイテムでは機能しません。また、 github にライブラリがありますが、サードパーティのライブラリに依存して自分でやりたくありません。
これに対する解決策はありますか?多分それはうまくいくはずで、これはただのバグですか?しかし、私はそれについての情報を見つけていません。
コードサンプル:
移行開始
class TransitionStartFragment: Fragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_transition_start, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val testData = listOf("one", "two", "three")
val adapter = TestAdapter(testData, View.OnClickListener { transitionWithTextViewInRecyclerViewItem(it) })
val recyclerView = view.findViewById<RecyclerView>(R.id.test_list)
recyclerView.adapter = adapter
val button = view.findViewById<Button>(R.id.open_transition_end_fragment)
button.setOnClickListener { transitionWithTextViewInFragment() }
}
private fun transitionWithTextViewInFragment(){
val destination = TransitionStartFragmentDirections.openTransitionEndFragment()
val extras = FragmentNavigatorExtras(transition_start_text to "transitionTextEnd")
findNavController().navigate(destination, extras)
}
private fun transitionWithTextViewInRecyclerViewItem(view: View){
val destination = TransitionStartFragmentDirections.openTransitionEndFragment()
val extras = FragmentNavigatorExtras(view to "transitionTextEnd")
findNavController().navigate(destination, extras)
}
}
レイアウト
<androidx.constraintlayout.widget.ConstraintLayout xmlns:Android="http://schemas.Android.com/apk/res/Android"
xmlns:app="http://schemas.Android.com/apk/res-auto"
Android:layout_width="match_parent"
Android:layout_height="match_parent"
Android:orientation="vertical">
<TextView
Android:id="@+id/transition_start_text"
Android:layout_width="wrap_content"
Android:layout_height="wrap_content"
Android:text="transition"
Android:transitionName="transitionTextStart"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
Android:id="@+id/open_transition_end_fragment"
Android:layout_width="wrap_content"
Android:layout_height="wrap_content"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="@id/transition_start_text"
Android:text="open transition end fragment" />
<androidx.recyclerview.widget.RecyclerView
Android:id="@+id/test_list"
Android:layout_width="match_parent"
Android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@id/open_transition_end_fragment"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
recyclerView用アダプター
class TestAdapter(
private val items: List<String>,
private val onItemClickListener: View.OnClickListener
) : RecyclerView.Adapter<TestAdapter.ViewHodler>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHodler {
return ViewHodler(LayoutInflater.from(parent.context).inflate(R.layout.item_test, parent, false))
}
override fun getItemCount(): Int {
return items.size
}
override fun onBindViewHolder(holder: ViewHodler, position: Int) {
val item = items[position]
holder.transitionText.text = item
holder.itemView.setOnClickListener { onItemClickListener.onClick(holder.transitionText) }
}
class ViewHodler(itemView: View) : RecyclerView.ViewHolder(itemView) {
val transitionText = itemView.findViewById<TextView>(R.id.item_test_text)
}
}
onItemClickでtextViewフォームアイテムをrecyclerViewに渡して移行します
トランジションエンド
class TransitionEndFragment : Fragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
setUpTransition()
return inflater.inflate(R.layout.fragment_transition_end, container, false)
}
private fun setUpTransition(){
sharedElementEnterTransition = TransitionInflater.from(context).inflateTransition(Android.R.transition.move)
}
}
レイアウト
<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:orientation="vertical">
<TextView
Android:id="@+id/transition_end_text"
Android:layout_width="wrap_content"
Android:layout_height="wrap_content"
Android:text="transition"
Android:transitionName="transitionTextEnd"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
fun transitionWithTextViewInFragment()-遷移があります。
fun transitionWithTextViewInRecyclerViewItem(view:View)-遷移なし。
リターントランジションの問題を解決するには、リサイクラービューを初期化するソースフラグメント(リサイクラービューのフラグメント)にこの行を追加する必要があります。
// your recyclerView
recyclerView.apply {
...
adapter = myAdapter
postponeEnterTransition()
viewTreeObserver
.addOnPreDrawListener {
startPostponedEnterTransition()
true
}
}
これは、フラグメント共有遷移を持つRecyclerViewの例です。私のアダプターでは、位置に基づいてアイテムごとに異なる遷移名を設定しています(私の例ではImageViewです)。
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = items[position]
holder.itemView.txtView.text=item
ViewCompat.setTransitionName(holder.itemView.imgViewIcon, "Test_$position")
holder.setClickListener(object : ViewHolder.ClickListener {
override fun onClick(v: View, position: Int) {
when (v.id) {
R.id.linearLayout -> listener.onClick(item, holder.itemView.imgViewIcon, position)
}
}
})
}
そして、アイテムをクリックすると、ソースフラグメントに実装された私のインターフェース:
override fun onClick(text: String, img: ImageView, position: Int) {
val action = MainFragmentDirections.actionMainFragmentToSecondFragment(text, position)
val extras = FragmentNavigator.Extras.Builder()
.addSharedElement(img, ViewCompat.getTransitionName(img)!!)
.build()
NavHostFragment.findNavController(this@MainFragment).navigate(action, extras)
}
そして私の目的地の断片で:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
info("onCreate")
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Lollipop) {
sharedElementEnterTransition = TransitionInflater.from(context).inflateTransition(Android.R.transition.move)
}
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
info("onCreateView")
return inflater.inflate(R.layout.fragment_second, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
info("onViewCreated")
val name=SecondFragmentArgs.fromBundle(arguments).name
val position=SecondFragmentArgs.fromBundle(arguments).position
txtViewName.text=name
ViewCompat.setTransitionName(imgViewSecond, "Test_$position")
}
SOリターントランジションで多くの人と同じ問題に直面しましたが、私にとって問題の根本的な原因は、Navigation
が現在フラグメントトランザクションにreplace
のみを使用していることでしたスタートフラグメントのリサイクラーがヒットバックするたびにリロードする原因になりましたが、これはそれ自体が問題でした。
したがって、2番目の(ルート)問題を解決することにより、アニメーションを遅らせることなくリターントランジションが機能し始めました。ここで反撃するときに初期状態を維持しようとしている人のために、私がしたことです:
onCreateView
に簡単なチェックを追加するだけです
private lateinit var binding: FragmentSearchResultsBinding
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return if (::binding.isInitialized) {
binding.root
} else {
binding = DataBindingUtil.inflate(inflater, R.layout.fragment_search_results, container, false)
with(binding) {
//doing some stuff here
root
}
}
つまり、ここでトリプルウィン:リサイクラーは再描画されず、サーバーからの再フェッチも行われず、リターントランジションも期待どおりに機能します。