Two-Way Data Binding Conversions

I’ve seen a few questions regarding using Android Data Binding with EditText, but with a different type. For example, you may want the EditText to bind to an integer value:

<EditText android:text="@={user.age}" ../>

or float value:

<EditText android:text="@={user.favoriteRationalNumber}" ../>

or maybe even a custom object.

With Android Studio 2.2’s gradle plugin, you can use a simplified conversion form:

<EditText android:text="@={`` + user.age}" ../>

This converts to and from a String from any primitive type.

However, this doesn’t work on Android Studio 2.1, nor does it work for any custom type, so you must implement it yourself. Here’s how you can do it for a float value:

@BindingAdapter("android:text")
public static void setText(TextView view, float value) {
    boolean setValue = view.getText().length() == 0;
    try {
        if (!setValue) {
            setValue = Float.parseFloat(view.getText().toString()) != value;
        }
    } catch (NumberFormatException e) {
    }
    if (setValue) {
        view.setText(String.valueOf(value));
    }
}

@InverseBindingAdapter(attribute = "android:text")
public static float getText(TextView view) {
    try {
        return Float.parseFloat(view.getText().toString());
    } catch (NumberFormatException e) {
        return 0;
    }
}

In this, you’ll have erroneous float format for text return 0. You may want to keep track of the old value and return it if there is an error. If you do, remember to use a weak reference, such as a WeakHashMap so that you don’t end up leaking View references everywhere.

2-way Data Binding on Android!

Released with Android Studio 2.1 Preview 3, Android Data Binding now has 2-way data binding.

Data Binding Quick Recap

For a short recap on data binding, you can now add expressions to the layout files to reference variables in your data model. For example:

<layout ...>
  <data>
    <variable type="com.example.myapp.User" name="user"/>
  </data>
  <RelativeLayout ...>
    <TextView android:text="@{user.firstName}" .../>
  </RelativeLayout>
</layout>

The above binds the user’s first name to the TextView’s text field. The UI is updated whenever the user’s data has changed.

Two-Way Data Binding

Android isn’t immune to typical data entry and it is often important to reflect changes from the user’s input back into the model. For example, if the above data were in a contact form, it would be nice to have the edited text pushed back into the model without having to pull the data from the EditText. Here’s how you do it:

<layout ...>
  <data>
    <variable type="com.example.myapp.User" name="user"/>
  </data>
  <RelativeLayout ...>
    <EditText android:text="@={user.firstName}" .../>
  </RelativeLayout>
</layout>

Pretty nifty, eh? The only difference here is that the expression is marked with “@={}” instead of “@{}”. It is expected that most data binding will continue to be one-way and we don’t want to have all those listeners created and watching for changes that will never happen.

Implicit Attribute Listeners

You can also reference attributes on other Views:

<layout ...>
  <data>
    <import type="android.view.View"/>
  </data>
  <RelativeLayout ...>
    <CheckBox android:id="@+id/seeAds" .../>
    <ImageView android:visibility="@{seeAds.checked ? View.VISIBLE : View.GONE}" .../>
  </RelativeLayout>
</layout>

In the above, whenever the checked state of CheckBox changes, the ImageView’s visibility will change. No need to attach a listener on your own! This kind of expression only works with attributes that support 2-way data binding and those that have binding expressions.

Enabling Two-Way Data Binding

So, what else do you need? First, you must be sure to be using the 2.1-alpha3 or above version of the android gradle plugin in your project’s build.gradle:

classpath 'com.android.tools.build:gradle:2.1.0-alpha3'

And, of course, you need to enable data binding in your module’s android section of the build.gradle:

android {
    ...
    dataBinding.enabled = true
}

Catch?

So, what’s the catch? Well, the main one is that only a few attributes are supported. This is because there aren’t listeners to allow the data binding framework to know when something has changed. The good news is that these are probably the attributes you care most about:

  • AbsListView android:selectedItemPosition
  • CalendarView android:date
  • CompoundButton android:checked
  • DatePicker android:year, android:month, android:day (yes, these are synthetic, but we had a listener, so we thought you’d want to use them)
  • NumberPicker android:value
  • RadioGroup android:checkedButton
  • RatingBar android:rating
  • SeekBar android:progress
  • TabHost android:currentTab (you probably don’t care, but we had the listener)
  • TextView android:text
  • TimePicker android:hour, android:minute (again, synthetic, but we had the listener)

You’re also going to start getting warnings now if your variable names collide with the View id’s in your layout. How do we know whether you mean the View or the variable in your expressions?

Rolling Your Own

Let’s imagine that the attribute you care about isn’t one of those listed above. How do you go about making your own? Let’s imagine that you have a color picker View where you want to have two-way binding for the color choice.

public class ColorPicker extends View {
    public void setColor(int color) { /* ... */ }
    public int getColor() { /* ... */ }
    public void setOnColorChangeListener(OnColorChangeListener listener) { 
        /*...*/
    }
    public interface OnColorChangeListener {
        void onColorChange(ColorPicker view, int color);
    }
}

The important aspect of the above View is that it has a listener that the data binding framework can listen for. Now we need to tell data binding about the attribute. The simplest way is using an InverseBindingMethod attribute on any class:

@InverseBindingMethods({
  @InverseBindingMethod(type = ColorPicker.class, attribute = "color"),
})

In this case, the name of the getter matches the name of the attribute “getColor” for “app:color.” If the name is something different, you can supply a method attribute to correct that. This creates a synthetic attribute to be used when binding the event using the name of the attribute with the suffix “AttrChanged.” Let’s see how this is used to set up the listener:

@BindingAdapter("colorAttrChanged")
public static void setColorListener(ColorPicker view,
        final InverseBindingListener colorChange) {
    if (colorChange == null) {
        view.setOnColorChangeListener(null);
    } else {
        view.setOnColorChangeListener(new OnColorChangeListener() {
            @Override
            public void onColorChange(ColorView view, int color) {
                colorChange.onChange();
            }
        });
    }
}

That’s the simplest kind of BindingAdapter — it converts between the color change listener type and the one used by the data binding framework, InverseBindingListener. However, you’re often going to want to also support binding to the colorChange event as well, right? You’re going to have to combine listeners:

@BindingAdapter(value = {"onColorChange", "colorAttrChanged"}, 
                requireAll = false)
public static void setColorListener(ColorPicker view,
        final OnColorChangeListener listener,
        final InverseBindingListener colorChange) {
    if (colorChange == null) {
        view.setOnColorChangeListener(listener);
    } else {
        view.setOnColorChangeListener(new OnColorChangeListener() {
            @Override
            public void onColorChange(ColorView view, int color) {
                if (listener != null) {
                    listener.onColorChange(view, color);
                }
                colorChange.onChange();
            }
        });
    }
}

Now you can use two-way data binding and bind to the onColorChange event, even in the same View.

InverseBindingAdapters

Similar to BindingAdapters and setters, there are times when you have to do something other than just calling a getter to retrieve a value. For example, if our ColorPicker has an enumeration of only a few colors, we may need to convert to int:

@InverseBindingAdapter(attribute = "color")
public static int getColorInt(ColorPicker view) {
    return ConvertColorEnumToInt(view.getColor());
}

Preventing Loops

One problem we encounter with two-way data binding is a sort of infinite loop. When making a change to a value raises an event, the listener will set the value on the target object. This can raise another event and it may trigger a change to the View again and we’re off on our loop. This kind of loop is normally caught either in the View’s setter or the data value’s setter, but this can’t be guaranteed if you don’t have control over the View or the data value. We’ve decided to prevent loops through BindingAdapters. For our ColorPicker example:

@BindingAdapter("color")
public static void setColor(ColorPicker view, int color) {
    if (color != view.getColor()) {
        view.setColor(color);
    }
}

There! No more loop.

I hope you get some use out of two-way data binding and let me know if you have any issues.

Next time I’ll talk about event lambda expressions that we released as well.

 

DatePicker Data Binding

We’re very close to releasing the version 1.0 of data binding (we’re up to 1.0-rc4). There have been very few bug trickling in, so we know it is getting to be rock solid. I thought I’d talk about some of the features and how to implement some enhancements you might want to add to data binding for your application.

As you know, you can bind to almost all View attributes with an expression. For example, you can bind to a user’s name with the expression:

<TextView android:text="@{user.name}" .../>

You can even get callbacks for events you can set on the View. For example, if you want the user to be able to edit the name, you may use:

<EditText android:text="@{user.name}"
          android:afterTextChanged="@{handlers.nameChanged}" .../>

That said, DatePicker doesn’t have anything special associated with it. There’s no android:date or android:year property. You don’t even get them for free as automatic setters because there isn’t a setDate() or setYear() method, just an updateDate() method. Likewise, because there is no setter for the OnDateChanged listener, there is no android:onDateChanged support. It sure would be nice to have a date picker support this functionality.

Ideally, we could implement it in a single method:

@BindingAdapter({"android:year","android:month", "android:day",
                 "android:onDateChanged"})
public static void setDate(DatePicker view, int year, int month,
                           int day, OnDateChanged listener) {
    view.init(year, month, day, listener);
}

The above BindingAdapter will work when all of the attributes are supplied. If you want to have a BindingAdapter work when only some of the attributes are supplied, you should supply requireAll = false for the BindingAdapter annotation. When you do that, and the attribute doesn’t exist, the default value for type is supplied (0 for int, null for Objects, etc):

@BindingAdapter(value = {"android:year", "android:month",
                         "android:day", "android:onDateChanged"},
                required = false)
public static void setDate(DatePicker view, int year, int month,
                           int day, OnDateChanged listener) {
    if (year == 0) year = view.getYear();
    if (month == 0) month = view.getMonth();
    if (day == 0) day = view.getDayOfMonth();
    view.init(year, month, day, listener);
}

That doesn’t look hard! But wait a second, month = 0 is January! This won’t work at all. If the month expression is set January, the month will use the current value. We’re fortunate that year = 0 is not legal (in Gregorian Calendar, we traverse from 1BC to 1AD) and day = 0 is not legal as we count from the first day of the month.

At this point, I wish that I had implemented “these attributes are required, but not these” for data binding. But I didn’t, so we have to have several BindingAdapters to cover all cases:

  • year, month, day, onDateChanged
  • year, month, day
  • year, month, onDateChanged
  • year, month
  • month, day, onDateChanged
  • month, day
  • year, day, onDateChanged; requireAll = false

The final one is special. Since it doesn’t have a month, we can compare the values to the default values (0, null) and know whether to set them or use the default.

Take a look and see how DatePicker can be data bound with this BindingAdapter. Perhaps it will inspire you to add some cool BindingAdapters. I hope we’ll get lots of requests for BindingAdapters like these for the attributes that you use and would like to be built by default.

https://github.com/georgemount/DatePicker

Postponing Fragment Transitions

Last week at Google I/O, I finally got to talk about what I have been working on for the last 6 months. You should try out data binding:

http://developer.android.com/tools/data-binding/guide.html

It is a super-fast code-generating data binding system that optimized to work well with Android. We’re looking for feedback, so try it out and let us know what you think.

That said, I’ve had a couple of posts in the back of my mind that I haven’t been able to get to. One bit of feedback that I had heard about Fragment Transitions is that we don’t get the postponeEnterTransition() that we get with Activity Transitions. That is especially important when one or both fragments have a RecyclerView.

When I wrote Fragment Transitions, I ran across the problem that FragmentTransaction is a Transaction; one fragment should be removed and the other should be added in an atomic step. This left the developer to handle the postponeEnterTransition.

In the situation where Fragment A is replaced by B, my recommendation was this:

  1. Transaction 1
    • add B,
    • hide B
  2. Transition 2: when B is ready
    • show B
    • remove A
    • add shared elements
    • add to back stack

That works great when B needs to be postponed. If, for example, A has a recycler view, the popBackStack() will immediate remove B and add A. Since A has a recycler view, the contents aren’t ready and the shared elements won’t be ready; you’ll lose the shared element transition.

This can be alleviated by only hiding fragment A.

  1. Transaction 1:
    • add B
    • hide B
    • add to back stack
  2. Transaction 2: when B is ready
    • show B
    • hide A
    • add shared elements
    • add to back stack

In this scheme, there is an intermediate state that you add at which B is added and hidden, When you pop the back stack, you will end up in the intermediate state, so you’ll have to automatically pop the back stack to remove B when you get there. If you don’t want that intermediate state, you might be able to avoid it. I didn’t try it, but you would have to detect when you’ve popped back from Transaction 2 and just remove B instead of popping the back stack.

Now the hidden fragment, A, will not lose its view hierarchy when it is hidden. Thus, the view is ready when going back. However, if you do an orientation change, the view hierarchy will be rebuilt. This is fine if your application is ready, even when the view is gone, but if your view needs layout (like recycler view), this isn’t enough. You may be able to trigger the system into laying out the hidden fragment A, but I don’t know the solution to that right off. The other issue is, of course, memory usage. While A is hidden and won’t take part in layout, it does consume resources. If your back stack is particularly large, this could become a noticeable pressure on the heap.

So, to support getting fragment A ready when going back, we need another stage in the transactions:

  1. Transaction 1:
    • add B
    • hide B
    • add to back stack
  2. Transaction 2: when B is ready
    • show B
    • hide A
    • add shared elements
    • add to back stack
  3. Transaction 3:
    • show A
    • remove A
    • add to back stack

In this scheme, there are two intermediate states. The first one has Fragment B hidden and Fragment A showing. Fragment B can hang out until is ready to show. On the way back, that intermediate state is not really necessary and should be popped automatically. The second intermediate state shows B and hides A. When pushing up the stack, it can immediately proceed to the final state, but when popping, it needs to pause there until Fragment A is ready to show.

I made a utility to handle the 3 transitions as a unit and watch the back stack to detect when we’ve arrived in an intermediate state and proceed when the fragment is ready to transition (both ways). In my utility, I’ve made an interface for the Fragment to implement:

public interface DelayedTransitionFragment {
    boolean isTransitionDelayed();
}

If the fragment does not implement the interface, it won’t support delayed transitions. If isTransitionDelayed() returns true, then the fragment will be responsible for calling transitionReady() on the utility.

In my example, I had each fragment contain a RecyclerView that needed to be laid out prior to the transition continuing. Instead of being hidden (Visibility.GONE), the fragment View needed to be invisible. Once it was laid out, I continued the transition:

final RecyclerView recyclerView = (RecyclerView) getView();
recyclerView.setVisibility(View.INVISIBLE); // force a layout
recyclerView.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
    @Override
    public boolean onPreDraw() {
        recyclerView.getViewTreeObserver()
            .removeOnPreDrawListener(this);
        FragmentTransitionUtil.getInstance(getFragmentManager())
            .transitionReady();
        return false;
    }
});

In trying this out, I found a few bugs in Fragment Transitions that I needed to work around. Obviously, I’ll fix those, but you’ll need to work around them until the bugs are fixed. In the fragment, I need to reset the enter and exit transitions onHiddenChanged and explicitly exclude the shared elements. My utility only provides for a single shared element, but you can expand it to support multiple. Again, this is only because of a bug; future versions shouldn’t require this hack.

@Override
public void onHiddenChanged(boolean hidden) {
    super.onHiddenChanged(hidden);
    resetTransitions();
}

private void resetTransitions() {
    // Work around bug in fragment transitions in 22.2.0
    // We need to reset the transition after the view is created
    String sharedElementName =
            FragmentTransitionUtil.getInstance(getFragmentManager())
                .getTransitionName();
    Slide enter = new Slide(Gravity.LEFT);
    Slide exit = new Slide(Gravity.RIGHT);
    if (sharedElementName != null) {
        enter.excludeTarget(sharedElementName, true);
        exit.excludeTarget(sharedElementName, true);
    }
    setEnterTransition(enter);
    setExitTransition(exit);
}

My sample project uses minimal data binding, so if you aren’t trying out the beta Android Studio, you’ll have to do just a little jiggering to get the sample to work.

Take a look at this project and let me know what you think:

https://drive.google.com/file/d/0BzzMYfCAYzn4bHpqWGhKbV81cU0/view?usp=sharing

Reveal Challenge

I saw an interesting take on the RevealTransition from Johannes Homeier. The circular reveal was to shrink and cross-fade to a circle, then move, then grow to the final position. At first I thought that this would be a fairly easy thing to do and the more I thought about it, the more interesting it was, so I thought I’d give it a go.

Fortunately for me, Johannes provided me a nice mockup so I could picture it:

reveal_move_transition

How would you make this transition work? I’m sure there are several ways to do this, but I decided to make a complete transition in the called Activity that replaces ChangeBounds. To accommodate the cross-faded Views, I needed to create Views and add them to the overlay and do all of the animation in the overlay.

I started with the project I used for the Reveal Activity Transition. First, let’s look at the view hierarchy for the called Activity:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin">

  <ImageView
      android:transitionName="hello"
      android:id="@+id/planter"
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:adjustViewBounds="true"
      android:layout_alignParentTop="true"
      android:src="@drawable/planter"
      />
</RelativeLayout>

This is much simpler than we had in the Reveal Activity Transitions project. Because I’m replacing the entire shared element transition, including the ChangeBounds transition, I can just do a normal shared element transition. No funny stuff required.

Now, my shared element transition is very simple:

<transition
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    class="com.sample.revealactivitytransition.CircleTransition"
    app:color="@android:color/holo_green_dark"/>

Here, I’m using a custom Transition with a custom tag “color” in the transition XML. Very nice.

Now, what do we do in the CircleTransition?

  1. I need a bitmap image of the start state so that I can do a cross-fade.
  2. Reveal in reverse the shared element start state and a uniform color.
  3. Cross-fade in the color over the start state image.
  4. Move a circle View from the start position to the final position.
  5. Reveal the end state view and the solid color
  6. Cross-fade the solid color

That’s not so bad. First, I need to get a bitmap of the starting state. I could share this with my transition in a more efficient way, but I want my Transition to be generic and reusable in other situations. I just had it take a snapshot of the View when capturing the start state:

@Override
public void captureStartValues(TransitionValues transitionValues) {
    final View view = transitionValues.view;
        if (view.getWidth() <= 0 || view.getHeight() <= 0) {
        return;
    }
    captureValues(transitionValues);
    Bitmap bitmap = Bitmap.createBitmap(view.getWidth(),
                                        view.getHeight(),
                                        Bitmap.Config.ARGB_8888);
    Canvas canvas = new Canvas(bitmap);
    view.draw(canvas);
    transitionValues.values.put(PROPERTY_IMAGE, bitmap);
}

I don’t need the bitmap of the end state because the View will exist in the end state. I can already cross-fade to that View.

When creating the Animator, I need to add the bitmap into the overlay as well as the solid-color Views that are cross-faded in and out. Then, it is just a matter of using the ObjectAnimator and the circular reveal Animator. There is one trick: the circular reveal Animator runs on the render thread and after the animator runs, the view is completely revealed. If I tie the removal of view to the onAnimatorEnd, then there may be a frame in which the animator completes and the view is revealed, showing a blink. I needed to hide the view one frame early to make sure that View doesn’t blink exposed.

That crossed-out section is wrong! I learned from John Reck that the RevealAnimator guarantees that the onAnimatorEnd will be received prior to the next UI draw call. If we set view Visibility, it will register in the correct frame.

 shrinkingAnimator.addListener(new AnimatorListenerAdapter() {
     @Override
     public void onAnimationEnd(Animator animation) {
         shrinkingView.setVisibility(View.INVISIBLE);
         startView.setVisibility(View.INVISIBLE);
         circleView.setVisibility(View.VISIBLE);
     }
 });

Because we’re doing reveal and fade simultaneously on the solid Views, we really should make the system work a little more efficiently. Typically, we would want any fading View to be on its own hardware layer. However, we really shouldn’t do that here — the reveal would cause the layer to be redrawn on every frame (bad!). Having hasOverlappingRendering() return false will make it more efficient. ImageViews do that normally, but I didn’t think of that before making the project.

There’s still some work to do in the Activity. We need to have the start state show the Hello World button instead of a shrunken planter image, so just like in the previous project, we need to move the snapshot into the layout. I don’t want to use the snapshot View because I want the shared element to do the entire Transition.

@Override
public void onSharedElementStart(List sharedElementNames,
                                 List sharedElements,
                                 List sharedElementSnapshots) {
    ImageView sharedElement = (ImageView) findViewById(R.id.planter);
    for (int i = 0; i < sharedElements.size(); i++) {
        if (sharedElements.get(i) == sharedElement) {
            View snapshot = sharedElementSnapshots.get(i);
            Drawable snapshotDrawable = snapshot.getBackground();
            sharedElement.setBackground(snapshotDrawable);
            sharedElement.setImageAlpha(0);
            forceSharedElementLayout();
            break;
        }
    }
}

@Override
public void onSharedElementEnd(List sharedElementNames,
                               List sharedElements,
                               List sharedElementSnapshots) {
    ImageView sharedElement = (ImageView) findViewById(R.id.planter);
    sharedElement.setBackground(null);
    sharedElement.setImageAlpha(255);
}

I’m just setting the background of the ImageView to the snapshot and hiding the planter picture with setImageAlpha(0). The captureStartValues will capture a bitmap of the View with the shared element snapshot. Now, we don’t have any guarantees about the snapshot View — it could be any class and the image may not be in the background. Therefore, I need to create the snapshot View myself:

@Override
public View onCreateSnapshotView(Context context,
                                 Parcelable snapshot) {
    View view = new View(context);
    view.setBackground(new BitmapDrawable((Bitmap) snapshot));
    return view;
}

and in the calling Activity, I have to provide the right Parcelable:

@Override
public Parcelable onCaptureSharedElementSnapshot(View sharedElement, Matrix viewToGlobalMatrix, RectF screenBounds) {
    int bitmapWidth = Math.round(screenBounds.width());
    int bitmapHeight = Math.round(screenBounds.height());
    Bitmap bitmap = null;
    if (bitmapWidth > 0 && bitmapHeight > 0) {
        Matrix matrix = new Matrix();
        matrix.set(viewToGlobalMatrix);
        matrix.postTranslate(-screenBounds.left, -screenBounds.top);
        bitmap = Bitmap.createBitmap(bitmapWidth, bitmapHeight,
                                     Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(bitmap);
        canvas.concat(matrix);
        sharedElement.draw(canvas);
    }
    return bitmap;
}

This is stolen right from the support library code (and simplified a bit). Take a look at the results:

You can download the project here. Enjoy!

What are all these dang Transitions?

I’ve seen a few questions about the Transitions used in Activity Transitions and Fragment Transitions and I thought I’d clear up their meanings.

In Activity Transitions, some Transitions are executed in the calling Activity and some are executed in the called Activity. Let’s see the simplest case and describe what is going on.

I’ve slowed down all animations on my device by 5x so that you can see what’s going on easier. There are two Activities, one with “Hello world” and one showing “Goodbye, cruel world!” One of the “Hello world” TextViews is shared to the Goodbye Activity. This app uses the default Material Theme — what is going on?

In the calling Activity, no Transition is being run. What?!? That can’t be. The Views from hello are clearly being faded out. What is happening is that the goodbye Activity is being brought on top of the hello Activity. Its background is being faded in on top of hello Activity and it appears as if the hello Activity is being faded out. You can adjust the duration of this fade by calling:

getWindow().setTransitionBackgroundFadeDuration(millis);

or in the window style:

<item name="windowTransitionBackgroundFadeDuration">500</item>

These should be called on the called (goodbye) Activity’s Window.

In the called Activity (goodbye), the “Goodbye, cruel world!” Views are faded in. This Transition is the Enter Transition and Material defaults this to Fade.

The shared element TextView “Hello, world!” is being moved using the shared element enter transition. In Material themes, this defaults to a combination of ChangeBounds, ChangeTransform, ChangeImageTransform, and ChangeClipBounds. These are generally a good starting point for most shared elements, moving and resizing the shared element.

Now, when going back, we want to play the transitions backwards. The enter transition is replaced with the return transition. The shared element enter transition is replaced with the shared element return transition. By default, these will just use the enter versions. In many cases, this is fine, but you may need to adjust the timing between entering and returning — either offsetting start delays or changing the interpolator to accelerate instead of decelerate or similar.

When going back, the background of the goodbye Activity fades out while the “Goodbye, cruel world!” Views Fade, following the return transition. The “Hello, world!” View moves to the position in the hello Activity following the shared element return transition.

So far, we’ve covered:

  • shared element enter
  • enter
  • shared element return
  • return

Let’s work on exit/reenter Transitions next:

Here, you can see that the “Hello, world” TextViews as well as the Action Bar move out of the hello Activity with an Explode Transition. The epicenter of the explosion is the shared element, the top-left “Hello, world” text. On the way back, I chose to use a different transition, Slide, to bring the Views back into the Activity. In my theme, I made these two additions:

<style name="AppTheme" parent="android:Theme.Material.Light">
  <item name="android:windowExitTransition">@android:transition/explode</item>
  <item name="android:windowReenterTransition">@android:transition/slide_bottom</item>
</style>

The exit transition is used to transition Views out of the calling Activity during the transition. The return transition is used to transition Views back into the Activity. These are used in the calling Activity.

The final transitions, shared element exit/return, are a little different. These are used in the calling Activity to execute a Transition before the shared element is transferred. Let’s make the TextView lift up off the hello Activity, before moving to the new goodbye Activity. Then the goodbye Activity will drop it when it reaches its destination.

I’ve made the background of the shared element “Hello, world” white so that you can see it a little better and distinguish the shadow. Setting a background on a View means that I get a shadow when I change the translation Z.

I’ve updated my transition definitions in the theme to use some custom transitions:

<style name="AppTheme" parent="android:Theme.Material.Light">
 <item name="android:windowExitTransition">@android:transition/explode</item>
 <item name="android:windowSharedElementExitTransition">@transition/shared_element_exit</item>
 <item name="android:windowSharedElementEnterTransition">@transition/shared_element_enter</item>
 <item name="android:windowSharedElementReenterTransition">@transition/shared_element_enter</item>
 <item name="android:windowSharedElementReturnTransition">@transition/shared_element_exit</item>
 <item name="android:windowReenterTransition">@android:transition/slide_bottom</item>
</style>

And I now have two new transitions, shared_element_enter:

<?xml version="1.0" encoding="utf-8"?>
<transitionSet
   xmlns:android="http://schemas.android.com/apk/res/android"
   android:transitionOrdering="sequential">
 <changeBounds/>
 <transition class="com.sample.simpletransitions.ShadowTransition"
             android:duration="150"/>
</transitionSet>

and shared_element_exit:

<?xml version="1.0" encoding="utf-8"?>
<transitionSet
   xmlns:android="http://schemas.android.com/apk/res/android"
   android:transitionOrdering="sequential">
 <transition class="com.sample.simpletransitions.ShadowTransition"
             android:duration="150"/>
 <changeBounds/>
</transitionSet>

Note the timing. I want the lift to happen first, then move, then drop.

I have a very simple custom Transition for translation z. You’ll see it in the project.

Now, when I launch the goodbye Activity, I have to call setTranslationZ to trigger the shared element exit Transition:

startActivity(intent, options.toBundle());
hello.setTranslationZ(16);

Now, on the way back, there is nothing telling the system to put the shared element back to zero elevation, so I need to force that. I chose to use the SharedElementCallback. You could also use a TransitionListener on the share element reenter Transition.

setExitSharedElementCallback(new SharedElementCallback() {
  @Override
  public void onSharedElementEnd(List<String> sharedElementNames,
      List<View> sharedElements, List<View> sharedElementSnapshots) {
    findViewById(R.id.hello).setTranslationZ(0);
  }
});

Now we have an active shared element exit transition.

In summary, these are called in the calling Activity (e.g. hello):

  • exit transition – removes Views from the calling Activity (e.g. explode)
  • reenter transition – moves Views back into the calling Activity when returning (e.g. slide). Defaults to exit transition.
  • shared element exit transition – additional Transition to execute before transferring the shared element to the called Activity. Does not have to be on the shared element! In my example, this lifts the shared element off the hello Activity.
  • shared element reenter transition – when coming back, this is used after the shared element has been transferred back to the calling Activity. In my example, this drops the shared element back into the hello Activity. Defaults to shared element exit transition.

In the called Activity (e.g. goodbye):

  • enter transition – moves Views into the called Activity (e.g. fade)
  • return transition – moves Views out of the called Activity when going back. Defaults to the enter transition.
  • shared element enter transition – moves shared elements from the location/size in the calling Activity to the final location/size.
  • shared element return transition – when going back, moves the shared elements from the location/size in the called Activity to the location/size in the calling Activity. Defaults to shared element enter transition.

All of these transitions are the same in Fragment Transitions except that there are no shared element exit and shared element return transitions. Fragment Transitions work using the FragmentTransaction. You remove a Fragment, then add a Fragment, and the Transitions are activated. If you remove a Fragment, it sure is difficult to do some manipulation on it like you would in the Activity Transition. Instead, you’re going to have to do your manipulations before starting the FragmentTransaction.

Here is the code I used in the examples.