Не работает переход SharedElement из фрагмента, содержащего recyclerview, в другой фрагмент с использованием нового компонента архитектуры навигации

#android #android-fragments #android-recyclerview #android-jetpack #android-architecture-navigation

#Android #android-фрагменты #android-recyclerview #android-jetpack #android-архитектура-навигация

Вопрос:

У меня есть приложение для Android, которое содержит 2 фрагмента, первый фрагмент содержит recyclerview, который показывает список контактов, вот код первого фрагмента

 public class NotesFragment extends Fragment implements ContactsAdapter.OnContactClicked {


    public NotesFragment() {
        // Required empty public constructor
    }


    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        return inflater.inflate(R.layout.fragment_notes, container, false);
    }

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);

        RecyclerView rvContacts = view.findViewById(R.id.rvContacts);
        rvContacts.setLayoutManager(new LinearLayoutManager(getContext(), RecyclerView.VERTICAL, false));

        List<Contact> contacts = new ArrayList<Contact>(){{
            add(new Contact(1,"Antoine El Murr"));
            add(new Contact(2,"Joelle Abou Chakra"));
            add(new Contact(3,"Michel Jebran"));
            add(new Contact(4,"Charlie Abou Moussa"));
            add(new Contact(5,"Edward Salameh"));
            add(new Contact(6,"Makram Merdass"));
            add(new Contact(7,"Sami Abou Jaoudeh"));
            add(new Contact(8,"Elie Nassif"));
            add(new Contact(9,"Samer Abou Samra"));
            add(new Contact(10,"Fady Ammoury"));
            add(new Contact(11,"Dany Fahed"));
            add(new Contact(12,"Mariana Haddad"));
            add(new Contact(13,"Mohamad Restom"));
        }};

        ContactsAdapter adapter = new ContactsAdapter(contacts);
        adapter.setOnContactClicked(this);
        rvContacts.setAdapter(adapter);

    }

    @Override
    public void onContactClicked(int position, Contact contact, View holderView) {
        NavController navController = Navigation.findNavController(holderView);
        Bundle arguments = new Bundle();
        arguments.putParcelable("contact", contact);
        FragmentNavigator.Extras extras = new FragmentNavigator.Extras.Builder()
                .addSharedElement(holderView.findViewById(R.id.tvIcon), ViewCompat.getTransitionName(holderView.findViewById(R.id.tvIcon)))
                .addSharedElement(holderView.findViewById(R.id.tvName), ViewCompat.getTransitionName(holderView.findViewById(R.id.tvName)))
                .build();
        navController.navigate(R.id.ContactToDetails, arguments, null, extras);
    }
}
  

Я использую навигационный компонент Android jetpack
В файле компоновки элементов recyclerview я устанавливаю имя перехода для каждого элемента, как показано ниже:

 <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="wrap_content">

    <TextView
        android:id="@ id/tvIcon"
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:layout_marginStart="8dp"
        android:layout_marginTop="8dp"
        android:layout_marginBottom="8dp"
        android:background="@drawable/circular_shape"
        android:gravity="center"
        android:textColor="@android:color/white"
        android:textSize="24sp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        android:transitionName="icon"
        tools:text="A" />

    <TextView
        android:id="@ id/tvName"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="24dp"
        android:layout_marginEnd="8dp"
        android:gravity="start"
        android:textSize="18sp"
        app:layout_constraintBottom_toBottomOf="@ id/tvIcon"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toEndOf="@ id/tvIcon"
        app:layout_constraintTop_toTopOf="@ id/tvIcon"
        android:transitionName="name"
        tools:text="Antoine El Murr" />

</androidx.constraintlayout.widget.ConstraintLayout>
  

И создал интерфейс в моем адаптере, который запускает функцию при нажатии контакта

Во втором фрагменте я установил имя перехода в моем XML-макете, как показано ниже:

 <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    tools:context=".ContactDetailsFragment">

    <TextView
        android:id="@ id/tvIcon"
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:layout_marginStart="8dp"
        android:layout_marginTop="8dp"
        android:background="@drawable/circular_shape"
        android:gravity="center"
        android:textColor="@android:color/white"
        android:textSize="24sp"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        tools:text="A"
        android:transitionName="icon"/>

    <TextView
        android:id="@ id/tvName"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="24dp"
        android:layout_marginEnd="8dp"
        android:gravity="start"
        android:textSize="18sp"
        app:layout_constraintBottom_toBottomOf="@ id/tvIcon"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toEndOf="@ id/tvIcon"
        app:layout_constraintTop_toTopOf="@ id/tvIcon"
        tools:text="Antoine El Murr"
        android:transitionName="name"/>

</androidx.constraintlayout.widget.ConstraintLayout>
  

И установите в onCreate переход enter, но он не работает:

 @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setSharedElementEnterTransition(TransitionInflater.from(getContext()).inflateTransition(android.R.transition.move));
    }
  

Пожалуйста, помогите, я искал решение в Интернете, но безуспешно, все примеры приведены в старой навигационной системе, а новые примеры — как использовать SharedElements из фрагмента в другой фрагмент, но не из recyclerview, а документация на веб-сайте Android скудная и небольшая и не охватывает все шаги для достижения этого перехода с помощью нового навигационного компонента

Ответ №1:

Я решил проблему: вместо того, чтобы помещать имя перехода внутри xml-элемента, поместите его в ViewHolder (или адаптер) и присвоите каждому элементу в представлении recycler уникальное имя перехода

Пример:
Элемент в позиции 0 -> имя перехода = «image_view_transition_0»
Элемент в позиции 1 -> имя перехода = «image_view_transition_1»

Итак, Android знает, какой элемент анимировать, если у всех элементов одинаковое имя перехода, он запутается, какой элемент будет анимироваться

Комментарии:

1. Эй, но вы также должны назвать переход в элементе другого фрагмента. Как вы это называете? Потому что в recyclerview у каждого элемента будет другое имя перехода

2. Вы должны передать имя перехода каждого элемента, который вы хотите перейти, через аргументы и присвоить переданные имена перехода соответствующим элементам

Ответ №2:

Убедитесь, что между вашими макетами есть соответствие transitionName(s) .

 android:transitionName="YOUR_TRANSITION_NAME"
  

Затем обязательно передайте экземпляр общего просмотра как (вы также можете использовать safeArgs):

 some_view.setOnClickListener{
  val extras = FragmentNavigatorExtras(
    yourSharedViewInstance to "YOUR_TRANSITION_NAME"
  )
  findNavController().navigate(R.id.your_destination_action, null, null, extras)
}
  

Затем в пункте назначения

 override fun onCreate(savedInstanceState: Bundle?) {
  super.onCreate(savedInstanceState)
  sharedElementEnterTransition = TransitionInflater.from(context).inflateTransition(android.R.transition.move /* or any other transition you want to use */)
}
  

И это должно быть так 🙂

Комментарии:

1. Как указано в моем вопросе, у меня есть соответствующее имя перехода в моих макетах, и я передаю экземпляр общего просмотра: FragmentNavigator.Extras extras = new FragmentNavigator.Extras.Builder() .addSharedElement(tvIcon, ViewCompat.getTransitionName(tvIcon)) .addSharedElement(tvName, ViewCompat.getTransitionName(tvName)) .build(); navController.navigate(R.id.ContactToDetails, arguments, null, extras); Но это не работает

2. Когда я попытался использовать внешний recyclerview, он работает нормально, но внутри recyclerview он не работает