首页 > 解决方案 > RecyclerView 项目在片段共享元素动画中奇怪地动画

问题描述

我研究了 Android Fragment 共享元素动画。我认为几乎所有工作都是为片段共享元素动画完成的。然而结果太奇怪了。我预计 recyclerview 中的触摸项目将动画到 DetailFragment 中的主要中心图像。动画已启动,但起始位置错误。这个项目有什么问题?

结果动画在这里 https://i.stack.imgur.com/vbxJ5.gif

MainActivity 是这样的

class BodyCheckDemoActivity : AppCompatActivity() , SimpleNavigate{
    lateinit var binding: ActivityBodyCheckDemoBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = DataBindingUtil.setContentView<ActivityBodyCheckDemoBinding>(this, R.layout.activity_body_check_demo);
    }

    override fun navigate( fragment : Class<*>, shareElement : List<View>?) {
        val fragment : Fragment = supportFragmentManager.fragmentFactory.instantiate(
            fragment.classLoader!!,
            fragment.name
        )

        val beginTransaction = supportFragmentManager.beginTransaction()
        beginTransaction.addToBackStack(null);
        beginTransaction.replace(R.id.fragment_container, fragment )
        shareElement?.forEach{ v-> beginTransaction.addSharedElement(v, v.transitionName)}
        beginTransaction.commit()
    }

    override fun onBackPressed() {
        super.onBackPressed()
    }

    private val map = HashMap<String,Any>();
    override fun getBundle(): MutableMap<String, Any> = map
}

fun Fragment.onBackPressed() = (activity as? SimpleNavigate)?.onBackPressed()
fun Fragment.navigate( fragment : Class<*> , shareElement : List<View>? = null ) = (activity as? SimpleNavigate)?.navigate(fragment,shareElement)
fun Fragment.getShareMap() = (activity as? SimpleNavigate)?.getBundle()
fun Fragment.getTransitionView( transitionName : String , root : View? = this.view ) : View? {
    return root?.let {
        if( it.transitionName == transitionName) it
        else (it as? ViewGroup)
            ?.children?.map{ child-> getTransitionView(transitionName, child) }?.find { childView-> childView != null }
    }
}

interface SimpleNavigate{
    fun onBackPressed()
    fun getBundle() : MutableMap<String,Any>
    fun navigate( fragment : Class<*>, shareElement : List<View>?)
}

第一个片段是这样的


class BodyCheckMainFragment : Fragment() {

    companion object {
        fun newInstance() = BodyCheckMainFragment()
    }

    private lateinit var binding: BodyCheckMainFragmentBinding
    val viewModel: BodyCheckMainViewModel by viewModels<BodyCheckMainViewModel>()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        initSubscription()
    }

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        Log.e( "MotionLayout", "onCreateView")
        binding = DataBindingUtil.inflate<BodyCheckMainFragmentBinding>(inflater,R.layout.body_check_main_fragment, container, false)
        with(binding.recyclerview){
            adapter = MyAdapter(viewModel);
            layoutManager = GridLayoutManager(context, 2);
            setHasFixedSize(true)
        }
        return binding.root
    }

    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)
        binding.vm = viewModel

    }

    private fun initSubscription() {
        viewModel.toastMsg.observe( this, Observer {
        })
        viewModel.toDetail.observe( this, Observer {
            getShareMap()!!["resourceId"] = it;
            val list = getTransitionView( it.toString() )?.let {  arrayListOf( it ) }
            getTransitionView( "bg$it" )?.let{ list?.add(it)}
            navigate( BodyCheckDetailFragment::class.java , list )
        })
    }

    class BodyCheckMainViewModel : ViewModel() {
        private val _toast = MutableLiveData<String>();
        private val _detailIndex = MutableLiveData<Int>();
        val toastMsg : LiveData<String> = _toast
        val toDetail : LiveData<Int> = _detailIndex

        fun onSymptopmpsClick( resourceId : Int){
            _detailIndex.value = resourceId;
        }

        fun onRequirementClick( index : Int){
            when(index){
                0->{ _toast.value = "Mask" }
                1->{ _toast.value = "Gloves" }
                2->{ _toast.value = "Alcohol" }
                3->{ _toast.value = "Soap" }
            }
        }
    }
}

带有相关的 xml 视图

<androidx.constraintlayout.widget.ConstraintLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    tools:context=".bodycheck.BodyCheckDemoActivity"
    android:background="@color/white">

    ...
    some views

    ...

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recyclerview"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom"
        app:layout_constraintTop_toBottomOf="@+id/title_symp"
        app:layout_constraintBottom_toBottomOf="parent"/>

</androidx.constraintlayout.widget.ConstraintLayout>

和适配器


class BodyState(
    val title : String,
    val resourceId: Int
)

class BodyStateHolder(
    val view: WidgetBodycheckSymptompsBinding,
    val itemClick : (BodyState) -> Unit
) : RecyclerView.ViewHolder(view.root){
    lateinit var item : BodyState;
    fun binding( item : BodyState ){
        this.item = item
        view.imgSrc = item.resourceId
        view.title = item.title
        view.bg.transitionName = "bg${item.resourceId}"
        view.image.transitionName = "${item.resourceId}"
        view.root.setOnClickListener { itemClick(item) }
    }
}

class MyAdapter(val viewModel: BodyCheckMainFragment.BodyCheckMainViewModel) : RecyclerView.Adapter<BodyStateHolder>() {
    val itemList = arrayListOf(
        BodyState( "Fever" , R.drawable.img_face_fever ),
        BodyState( "Cough" , R.drawable.img_face_cough ),
        BodyState( "Sore troath" , R.drawable.img_face_sore_troath ),
        BodyState( "Tiredness" , R.drawable.img_face_headache ),
        BodyState( "Aches" , R.drawable.img_face_sore_troath_middle ),
        BodyState( "Shortness breath" , R.drawable.img_face_comportable )
    )
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BodyStateHolder {
        return BodyStateHolder(
            DataBindingUtil.inflate(
                LayoutInflater.from(parent.context),
                R.layout.widget_bodycheck_symptomps, parent, false )
        ) {
            viewModel.onSymptopmpsClick( it.resourceId )
        }
    }

    override fun getItemCount() = itemList.size;
    override fun onBindViewHolder(holder: BodyStateHolder, position: Int) = holder.binding(itemList[position]);
}

第二个fragent在这里

class BodyCheckDetailFragment : Fragment() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        postponeEnterTransition()
        sharedElementEnterTransition = AutoTransition()
        sharedElementReturnTransition = AutoTransition()

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        binding = DataBindingUtil.inflate<BodyCheckDetailFragmentBinding>(inflater, R.layout.body_check_detail_fragment, container, false)
        binding.srcImg = getShareMap()!!["resourceId"] as Int
        binding.faceImage.viewTreeObserver.addOnPreDrawListener(object:ViewTreeObserver.OnPreDrawListener{
            override fun onPreDraw(): Boolean {
                startPostponedEnterTransition()
                binding.faceImage.viewTreeObserver.removeOnPreDrawListener(this)
                return true;
            }
        })
        return binding.root
    }
}

只有相关的视图 xml

<data>
    <variable
        name="srcImg"
        type="int" />
</data>
<androidx.constraintlayout.motion.widget.MotionLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/motion_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/bodyCheckPrimaryColor"
    app:layoutDescription="@xml/body_check_detail_fragment_scene">

    ... some views...
    
    <androidx.constraintlayout.widget.ConstraintLayout
        android:id="@+id/face_image"
        android:layout_width="260dp"
        android:layout_height="240dp"
        android:layout_marginTop="80dp"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        android:transitionName="@{`bg`+srcImg}">

        <ImageView
            android:layout_width="0dp"
            android:layout_height="0dp"
            tools:src="@drawable/img_face_fever"
            android:srcGlide="@{srcImg}"
            android:transitionName="@{srcImg+``}"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintEnd_toEndOf="parent"/>
    </androidx.constraintlayout.widget.ConstraintLayout>

</androidx.constraintlayout.motion.widget.MotionLayout>

标签: androidanimationandroid-recyclerviewfragment

解决方案


推荐阅读