Android shared elements transition

Fragment Transitions with Shared Elements using Android Navigation

Feb 24, 2020 · 4 min read

Anyone that isn’t using the newest Android Studio tools should definitely change that, especially if that means they aren’t using Android Jetpack’s Navigation component. This component makes maneuvering through the screens of your app a breeze and its chock full of features that can be used to make the navigation itself appear elegant. In this article I’ll be explaining how you can use the navigation component to create transitions with shared elements.

Shared Elements

What are shared elements? In the singular sense, a shared element is a pair of views that are present in two different fragments or activities. These views display the same information, such as a string or an image, but they may have different sizes or locations on screen. To give the the user the illusion that the view is being preserved during navigation, a transition is used to move and reshape the first view so that it “becomes” the second view. Since shared elements actually consist of two different views, they are technically not shared between destinations.

Setup

We’ll be creating an app that performs a simple transition from one fragment to another with multiple shared elements. In Android Studio, create a new project using the “Bottom Navigation Activity” project template.

File -> New -> New Project -> Bottom Navigation Activity

This template gets us up and running pretty quick since there is already a navigation action between HomeFragment and HomeSecondFragment.

In addition to the basic project setup, you should also verify the following items are checked off:

  • Increase the minSdkVersion in build.grade(Module:app) to 21
  • Implement navigation component in build.gradle (Module:app):
  • Enable window content transitions in style.xml
  • Create a new transition folder in app/res.

Right-click on the res folder -> New -> Android Resource Director

resource type = transition

  • Create a new transition resource in the transition folder (change_bounds.xml):

ChangeBounds Transition — Single View

Now that everything’s set up, let’s work on executing a simple shared element transition with one TextView. Add one TextView to HomeFragment and one TextView to HomeSecondFragment. Move them to different locations on screen, constrain them each appropriately, and make them say the same thing (ex. “Example”).

If we navigate between these two fragments as is, there is no way to know that the first TextView is associated with the second TextView. For this reason, we need to add the android:transitionName property to both TextViews. All shared elements require unique transitionNames. Note that the transitionName property will not be available if windowActivityTransitions are not enable in your theme.

Now, in the onClickListener in HomeFragment, create a new val extras that maps the view in the starting fragment to the view in the destination fragment (textView to “exampleText”). Then, pass the extras argument to the navigate method of the navController.

The last step is to handle the incoming shared element in the destination fragment. To do this, we set sharedElementEnterTransition to the change_bounds.xml transition we created earlier. Add the same thing for sharedElementReturnTransition so that the transition works in the reverse direction.

With that, BAM! Transition works like a charm. You can move the TextViews anywhere on screen and it will still work fine.

Although not totally related to this tutorial, you need to add this line to MainActivity.kt to make the back button in the upper left corner work properly:

ChangeBounds Adjustments

You can also apply a ChangeBounds transition using the following code:

Using this method, you can adjust specific aspects of the transition.

  • Duration (duration = ## in ms)
  • Interpolator
  • Propogation (CircularPropogation or SidePropogation)

ChangeBounds Transition-Nested View

A shared element nested inside another view (such as a CardView or a ConstraintLayout) will not transition properly if the parent view is not also treated as a shared element. For example, if our TextView is nested inside a ConstraintLayout in fragment 1 and we do not map the ConstraintLayout to the destination fragment, we get something super choppy like this:

To fix this, the parent View also needs to be transitioned. Using the same ConstraintLayout example, the proper way to make the transition is to map the ConstraintLayout from the first fragment to the root ConstraintLayout in the second fragment like this:

Читайте также:  Dsp процессор для android

In conclusion, Shared Elements are pretty simple to use but there are a few gotchas that can make creating transitions a troubleshooting marathon. Code carefully!

Источник

Как я боролся с Shared Element Transition и написал свою первую opensource библиотeку

Нет повести печальнее на свете,
чем повесть о ViewPager’e и SET’e

Хочется предупредить, что автор ‒ новичок андроид, поэтому статья содержит столько технических неточностей, что вас, скорее, нужно предупредить о том, что в статье могут встретиться технически достоверные утверждения.

Куда приводит бекенд

Всю жизнь я пилил бекенд. Начало 2019 года, за плечами уже один очень амбициозный, но недоделанный проект. Бесплодная поездка в Цюрих на собеседование в одну поисковую компанию. Зима, грязь, настроение никакое. Сил и желания тянуть проект дальше нету.

Хотелось навсегда забыть этот страшный бекенд. К счастью, судьба подкинула мне идею – это было мобильное приложение. Основной его фишкой должно было стать нестандартное использование камеры. Работа закипела. Прошло немного времени, и вот прототип готов. Релиз проекта близился и все было хорошо и стройно, пока я не решил сделать пользователю “удобно”.

ViewPager и Shared Element Transition. Ищем пути примирения

Часы изучения: десятки тем на форумах и вопросов на StackOverflow без ответа. Что бы я ни открывал, мне предлагали совершить transition из RecyclerView во ViewPager или “приложить подорожник Fragment.postponeEnterTransition() ”.

Народные средства не помогли, и я решил заняться примирением ViewPager и Shared Element Transition самостоятельно.

ViewPager: Первая кровь

Я начал размышлять: “Проблема появляется в тот момент, когда пользователь переходит с одной страницы на другую. ”. И тут меня осенило: “У тебя не будет проблем с SET во время смены страницы, если не менять страницы”.

Мы можем делать transition на одной и той же странице, а потом просто подменять в ViewPager текущую страницу на целевую.

Попробуем поменять фрагмент в текущей странице на что-то еще.

Запускаем приложение и… плавно переходим на пустой экран. В чем причина?

Оказывается, контейнером каждой из страниц является сам ViewPager , без всяких посредников вида Page1Container , Page2Container . Поэтому просто так поменять одну страницу на другую не получится, заменится весь pager .

Хорошо, чтобы изменять контент каждой страницы в отдельности, создаем несколько фрагментов-контейнеров для каждой страницы.

Что-то опять не заводится.

java.lang.IllegalStateException: Can’t change container ID of fragment BigPictureFragment<. >: was 2131165289 now 2131165290

Мы не можем прицепить фрагмент второй страницы ( BigPictureFragment ) к первой, потому что он уже прицеплен к контейнеру второй страницы.

Стиснув зубы добавляем еще фрагментов-дублеров.

Заработало! Код transition, который я когда-то скопировал с просторов GitHub, уже содержал анимации fade in и fade out. Поэтому перед transition все статические элементы с первого фрагмента исчезали, потом перемещались снимки, и только затем появлялись элементы второй страницы. Для пользователя это выглядит как настоящее перемещение между страницами.

Все анимации прошли, но есть одна проблема. Пользователь до сих пор на первой странице, а должен быть на второй.

Чтобы это исправить мы аккуратно подменяем видимую страницу ViewPager на вторую. А потом восстанавливаем содержимое первой страницы в начальное состояние.

Проект можно посмотреть на GitHub.

Подведем итоги. Код начал выглядеть гораздо солиднее: вместо 2 изначальных фрагментов у меня получилось аж 6, в нем появились инструкции, которые управляют спектаклем, подменяя фрагменты в нужное время. И это только в демо.

В настоящем же проекте в коде один за другим, начали появляться подпорки в самых неожиданных местах. Они не давали приложению развалиться, когда пользователь нажимал на кнопки с “неправильных” страниц или сдерживали фоновую работу дублирующих фрагментов.

Оказалось, что в андроиде нет коллбеков на завершение transition, а время его исполнения весьма произвольное и зависит от множества факторов (например, как быстро загрузится RecyclerView в результирующем фрагменте). Это приводило к тому, что подмена фрагментов в handler.postDelayed() часто исполнялась слишком рано или слишком поздно, что только усугубляло предыдущую проблему.

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

Такое положение дел меня не устроило и я, полный праведного гнева, начал поиски другого решения.

Как сделать Shared Element Transition в Viewpager правильно

Пробуем PageTransformer

В интернете ответов по-прежнему не было и я задумался: как же еще можно провернуть этот transition. Что-то на подкорке сознания шептало мне: “Используй PageTransformer , Люк”. Идея показалась мне многообещающей и я решил прислушаться.

Суть идеи в том, чтобы сделать PageTransformer , который, в отличии от Android SET, не будет требовать многократных повторений setTransitionName(transitionName) и FragmentTransaction.addSharedElement(sharedElement,name) с обеих сторон перехода. Будет перемещать элементы вслед за свайпом и иметь простой интерфейс вида:

Приступим к разработке. Данные из метода addSharedTransition(fromId, toId) я сохраню в Set из Pair и достану их в методе PageTransfomer

Внутри я пройду по всем сохраненным парам View , между которыми нужно сделать анимацию. И попытаюсь отфильтровать их, чтобы анимировались только видимые элементы.

Для начала проверим, успели ли создаться элементы, которые нужно анимировать. Мы не привередливые, и, если View не было создано до начала анимации, мы не будем ломать анимацию целиком (как Shared Element Transition), а подхватим ее, когда элемент будет создан.

Нахожу страницы, между которыми происходит перемещение (как я определяю номер страницы расскажу ниже).

Если обе страницы уже созданы, то я ищу на них пару View , которую нужно анимировать.

На данном этапе мы выбрали View, которые лежат на страницах, между которыми скролит пользователь.

Настало время завести много переменных. Вычисляю опорные точки:

В прошлом сниппете я задал slideToTheRight , и уже в этом он мне пригодится. От него зависит знак в translation , который определит полетит View на свое место или куда-то за пределы экрана.

Интересно, что формулы смещения по X и Y у обоих View , на стартовой и результирующей странице получились одинаковыми, несмотря на разные изначальные смещения.

Но с масштабом, к сожалению, такой трюк не пройдет – нужно учитывать является ли данное View начальной или конечной точкой анимации.

Для кого-то может стать неожиданностью, но transformPage(@NonNull View page, float position) вызывается много раз: для каждой кэшированной страницы (размер кэша настраивается). И, чтобы не перерисовывать анимированное View по несколько раз, на каждый вызов transformPage() , мы изменяем только те, которые находятся на текущей page .

Выбираем страницы для рисования анимации

ViewPager не спешит делиться сведениями между какими страницами идет скроллинг. Как я и обещал, сейчас расскажу как мы получаем эту информацию. В нашем PageTransformer мы реализуем еще один интерфейс ViewPager.OnPageChangeListener . Поизучав выход onPageScrolled() через System.out.println() я пришел к следующей формуле:

Вот и все. Мы сделали это! Анимация следит за жестами пользователя. Зачем выбирать между свайпами и Shared Element Transition, когда можно оставить все.

Во время написания статьи я добавил эффект исчезновения статичных элементов — он пока очень сырой, поэтому в библиотеку не добавлен.

Источник

Shared element transition in fragments Android

Hey people! Welcome to my blog on shared element transition using fragments with navigation component.

Before we begin with the implementation it is important to understand the difference between element transition and other such as container transforms.
Container transforms opens a View from a ViewGroup into a fragment or activity as a whole. Whereas, element or shared element transition only moves the views from one place to other.

1. Add dependencies to project

Start with new android studio project choosing Empty Activity.
Give project and package name and select language Kotlin.
Minimum SDK as API 21: Lollipop and finish

gradle.build(Module: app)

2. Create destination fragments

We need two fragments for shared element transition to take place. First fragment has RecyclerView list data and Second fragment has detail for each item from the list.

So create two blank fragments with names as follows and leave them empty for now:
ListFragment .kt with fragment_list.xml
DetailFragment.kt with fragment_detail.xml
If you are unsure of this, take this as reference. BlankFragment and fragment_blank.xml

3. Make navigation graph and attach to activity

Let’s create a graph to connect all destinations in our app with actions. In our case we connect previously created empty fragments.

Right click on res —> New —> Android Resource File
In the dialog section:
File name —> nav_graph
Resource type —> Navigation and then click Ok.

Attach graph to Activity :
Let’s make our MainActivity class as default navigation host for all fragments by attaching our navigation graph.

activity_main.xml:

Let’s set NavController with ActionBar using NavigationUI to the activity class

MainActivity.kt:

4. Connect destinations with actions

Open nav_graph.xml file that you have created earlier, in that file from the toolbar go to Design section.
Click the New Destination icon to see available destinations to add.

Locate fragment_list and then click to insert, similarly add fragment_detail to the graph.
Now arrange the layouts properly. Select listFragment and drag the connection to detailFragment.

Here’s the xml layout for what we did.

nav_graph.xml:

Now run the app and you will be able to see blank screen with no views to make sure everything is working.

5. Create classes to show RecyclerView and data

We need data such as text string resources to show in layout, let’s copy the strings for showing titles and other data, colors, drawables and styles for the app.
Get all the strings and paste into your strings.xml file .
Get all the colors and paste into your colors.xml file .
Get all the styles and paste into your styles.xml file .
Get all drawables and paste into your drawables folder.

Now let’s create a data model class Sports.kt which helps in making a list.

Sports.kt:

Create item_sports layout file to show each item in list.

item_sports.xml:

Finally create an RecyclerView adapter for fragment to show a list.

SportsAdapter.kt:

6. With SafeArgs pass data class as argument

We have to pass our data class Sports.kt as argument from ListFragment to DetailFragment, and we will do this from nav_graph file.

In nav_graph.xml file go to design section from toolbar.
Select detailFragment, to the right side Attributes section, click plus(+) button to add new arguments to that fragment.

In Add Arguments section dialog :
Name —> sportsArgs
Type —> Custom Parcelable —> Sports.kt
Continue by clicking OK and then again select Add to finish.
Now Rebuild the project for android studio to auto generate the classes required for this.

Note: If build is not successful or android studio cannot build at this point, try cleaning the project and Rebuilding it from
Build —> Clean Project.

7. Show list and detail data in Fragments

Now we have data and arguments ready to be passed let’s populate the data inside fragments.
Add RecyclerView to ListFragment.kt and layout. Replace the code inside the layout with below one.

fragment_list.xml:

ListFragment.kt:

Our ListFragment is ready to show the RecyclerView, let’s complete DetailFragment to show each list item which user is selected.

fragment_detail.xml:

FragmentDetail.kt:

Now run the app, ListFragment will be able to show list of Cards and when you click single item you will be navigated to DetailFragment with no errors.
Now let’s see how we can add shared element transitions between these fragments.

8. Make SharedElementTransition between fragments

To perform any transition between views first we need to identity those views from both layouts. So in our case we are performing transition between two layouts which are item_sports.xml to fragment_detail.xml. See the image below.

Since our item views are created by the adapter let’s make changes to those. Open SportsAdapter.kt file and scroll down below to class which implements OnClickListener, this takes single argument which is data class but now change that to take two more arguments which are ImageView(banner) and TextView(title).
These are the changes you need to make:

Now for views which we are passing needs unique transition names while transition occurs, if all views have same transition name then transition won’t take place.
So the best way to do this is by giving each view transition name from it’s data class itself which must be of the type String.
We have access to both banner, title view with data in SportsViewHolder class.
These are the changes you need to make:

Inside SportsViewHolder

Since ListFragment.kt implements OnClickListener it takes extra parameters after changes which needs ImageView and TextView, so we will pass that which shown below.
Let’s use FragmentNavigatorExtras which builds pair of view with string value. We can pass as many as pairs we want for transition to take place. In our case we are going to pass two pairs a ImageView for banner and TextView for title.
After with an assigned value we pass that inside navigate along with directions and extras.
After all the required changes this is how your code should look like.

ListFragment.kt:

We have to specify what kind of transition should take place from the leaving fragment and for that we have inflate a transition type using TransitionInflater in our fragment OnCreateView before returning.
So let’s inflate the transition with type move effect, see those changes below.

onCreateView ListFragment

With this we will be able to perform moving transition but when user clicks back button we must be able to perform return transition, so we will this changes inside OnViewCreated after all views have been ready.
This gives fragment the ability to delay fragment animations until all data is being loaded.
With these changes you can get backward transition taking place.

onViewCreated at the end

But enter transition on any view should happen while it’s been drawn. So kotlin has a way of doing this by calling doOnPreDraw for view for transition.
In our case we have to call this on RecyclerView while being drawn or laid out.

onViewCreated after enter transition

If you get an error at doOnPreDraw which means you have to specify proper jvm target version. So here’s the fix for it.

build.gradle(Module:app) after build types

Now Sync the project to fix this.
With all the changes being made to ListFragment. This is how it should look like.

ListFragment.kt:

One last thing remaining to do is to apply these transition names in DetailFragment for transition to occur correctly.

Just like ListFragment one same thing we have to do is to inflate transition same way.
Optionally you can delay the transition from leaving fragment at certain duration and let the transition take place.

onCreateView

DetailsFragment.kt:

Now run the app check how transitions takes place between them. For your better understanding experiment by removing code where transition changes or doesn’t work at any point.

You will find this example wrote completely with BindingAdapters in my GitHub repository in different branch, so make sure to check that too.

Источник

Оцените статью