5

I will start off with a gif showing what is happening.

gif of what is happening

The list item is supposed to animate up to be the header item. As you can see, the header item animates back down to become the list item when the back button is pressed. There is another issue where the Activity parent for these fragments flashes behind the fade transition as well, so if you have any ideas about that I would like to know :)

Now, a little explanation of my code before I paste the snippets. This app navigates a tree structure of HTML documents. The small RecyclerView list items are the titles of documents. When clicking on a document, it anchors the title on top with a WebView that displays the HTML. There is only one fragment, and that fragment creates another instance of itself when a new fragment is created. The structure is a little weird, but the back button and stack seem to work as they should. This code is in C# because I am using Xamarin.Android, but most of it is very similar, aside from click handling.

I can start with my XML files. Layouts are still a WIP so I know they are messy.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:id="@+id/linear_layout_main"
    android:background="#ffffff">
  <LinearLayout
    android:id="@+id/progressLayout"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:visibility="gone"
    android:gravity="center">
    <ProgressBar
        android:id="@+id/progressbar"
        style="?android:attr/progressBarStyleLarge"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
  </LinearLayout>
  <LinearLayout
      android:id="@+id/subroutines_layout"
      android:layout_width="match_parent"
      android:layout_height="match_parent"
      android:gravity="center"
      android:orientation="vertical"
      android:background="#ffffff">
    <TextView
        android:id="@+id/routine_title"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center_vertical"
        android:padding="5dp"
        android:textSize="24sp"
        android:visibility="gone"/>
    <FrameLayout
       android:id="@+id/preview_frame"
       android:layout_width ="match_parent"
       android:layout_height="150dp"
       android:visibility="gone">
      <WebView
        android:id="@+id/routine_preview"
        android:layout_width="match_parent"
        android:layout_height="150dp"
        android:gravity="center_vertical"
        android:padding="5dp"
        android:transitionName="webview_preview_transition"
        android:scrollbars="none"
        android:visibility="gone"/>
      <TextView
        android:id="@+id/routine_preview_clickable"
        android:layout_width="match_parent"
        android:layout_height="150dp"
        android:gravity="center_vertical"
        android:visibility="gone"/>
    </FrameLayout>
    <TextView
        android:id="@+id/preview_separator"
        android:layout_width="match_parent"
        android:layout_height="1dp"
        android:gravity="center_vertical"
        android:layout_marginLeft="15dp"
        android:layout_marginRight="15dp"
        android:background="@color/material_grey_100"
        android:visibility="gone"/>
    <android.support.v7.widget.RecyclerView
        android:id="@+id/routines_rv"
        android:layout_width="match_parent"
        android:layout_height="match_parent" >
    </android.support.v7.widget.RecyclerView>
  </LinearLayout>
</LinearLayout>

This is the main layout for the fragment. The WebView is inside of a frame, covered by an invisible TextView because I need to be able to click it to expand the preview.

<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/routines_rv_item"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:minHeight="48dp"
    android:gravity="center_vertical"
    android:textSize="16sp"
    android:paddingLeft="16dp"
    android:paddingRight="16dp"
    android:background="@android:color/transparent"/>

This is the RecyclerView list item.

namespace Routines.Views.Routines
{
    class RoutineAdapter : RecyclerViewAdapter<PublishedDocumentFragmentDTO>
    {
        public RoutineAdapter(List<PublishedDocumentFragmentDTO> items,
                                int layout,
                                Func<View, Action<int>, HolderBase> viewHolderInitializer)
            : base(items, layout, viewHolderInitializer) { }

        public override void OnBindViewHolder(RecyclerView.ViewHolder holder, int position)
        {
            var vh = (RoutineHolder)holder;
            vh.Text.Text = Items[position].Title;
            vh.Text.TransitionName = "preview" + Items[position].Title;
        }

        public void UpdateData(List<PublishedDocumentFragmentDTO> routines)
        {
            Items = routines;
        }
    }

    public class RoutineHolder : HolderBase
    {
        public TextView Text { get; private set; }

        public RoutineHolder(View itemView, Action<int> action) : base(itemView, action)
        {
            Text = itemView.FindViewById<TextView>(Resource.Id.routines_rv_item);
        }
    }
}

The RecyclerViewAdapater. Here, I set the TransitionName for each item. I have checked to make sure that transition names match when starting the fragment, and in OnCreateView, so I do not think those are the problem.

public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
        {
            var v = inflater.Inflate(Resource.Layout.routines_rv_fragment, container, false);

            _subRoutinesLayout = v.FindViewById<LinearLayout>(Resource.Id.subroutines_layout);
            _subRoutinesView = v.FindViewById<RecyclerView>(Resource.Id.routines_rv);
            _progressLayout = v.FindViewById<LinearLayout>(Resource.Id.progressLayout);
            _selectedTitleView = v.FindViewById<TextView>(Resource.Id.routine_title);
            _selectedPreview = v.FindViewById<WebView>(Resource.Id.routine_preview);
            _separator = v.FindViewById<TextView>(Resource.Id.preview_separator);
            _previewFrame = v.FindViewById<FrameLayout>(Resource.Id.preview_frame);
            _selectedPreviewClickable = v.FindViewById<TextView>(Resource.Id.routine_preview_clickable);

            _subRoutinesView.HasFixedSize = true;
            _subRoutinesView.SetLayoutManager(new LinearLayoutManager(Activity));
            _subRoutinesView.AddItemDecoration(new MaterialDesignDivider(Activity, Resource.Drawable.md_divider));

            _selectedPreview.TransitionGroup = true;

            if (!_topLevel)
            {

                _selectedTitleView.Visibility = ViewStates.Visible;
                _selectedPreview.Visibility = ViewStates.Visible;
                _separator.Visibility = ViewStates.Visible;
                _previewFrame.Visibility = ViewStates.Visible;
                _selectedPreviewClickable.Visibility = ViewStates.Visible;

                _selectedTitleView.TransitionName = "preview" + _selectedTitle;
                _selectedTitleView.Text = _selectedTitle;

                IRoutinesService _routinesService;
                var routinesContainer = AutoFac.Container;
                _routinesService = routinesContainer.Resolve<IRoutinesService>();

                var blobIdGuid = new Guid(_selectedBlobId);
                Tuple<bool, string> tuple = _routinesService.GetFragment(blobIdGuid);

                if (tuple.Item1)
                {
                    string mimeType = "text/html";
                    string encoding = "UTF-8";
                    string html = File.ReadAllText(tuple.Item2);

                    _selectedPreview.LoadDataWithBaseURL("", transformedHtml, mimeType, encoding, "");
                }

                _selectedPreviewClickable.Click += PreviewClicked;
            }

            return v;
        }

This is the whole OnCreateView method, for good measure. But I think the only part that will be relevant to my transition is

        _selectedTitleView.TransitionName = "preview" + _selectedTitle;
        _selectedTitleView.Text = _selectedTitle;

_selectedTitleView is the TextView that is anchored at the top. _selectedTitle is passed in to the fragment as the title of the RecyclerView item that was selected. _topLevel is a bool that is set to true when this fragment is created from itself. This is because the first instance of this fragment does not have the anchored file at the top, so those views are not visible.

private void SubRoutineSelected(int position)
        {    
            PublishedDocumentFragmentDTO selectedRoutine;

            IRoutinesService _routinesService;
            var container = AutoFac.Container;
            _routinesService = container.Resolve<IRoutinesService>();

            if (_topLevel)
            {
                selectedRoutine = _viewModel.SelectedRoutineTree[position];
            }
            else
            {
                var childList = _viewModel.RetrieveSelectedChildren(_fragId, _viewModel.SelectedRoutineTree);
                selectedRoutine = childList[position];
            }

            var newFragId = selectedRoutine.FragmentId;
            var newBlobId = selectedRoutine.BlobId;
            var title = selectedRoutine.Title;

            if (selectedRoutine.Children.Any())
            {
                SubroutinesFragment selectedRoutineFragment = new SubroutinesFragment(_viewModel, newFragId, false, title, newBlobId);

                if (Build.VERSION.SdkInt >= BuildVersionCodes.Lollipop)
                {
                    SharedElementReturnTransition = new PreviewTransition();
                    ExitTransition = new Fade();

                    selectedRoutineFragment.SharedElementEnterTransition = new PreviewTransition();
                    selectedRoutineFragment.EnterTransition = new Fade();

                }

                var test = (RoutineHolder)_subRoutinesView.FindViewHolderForAdapterPosition(position);

                FragmentManager.BeginTransaction().
                    Replace(Resource.Id.main_frame, selectedRoutineFragment).
                    AddToBackStack(null).
                    AddSharedElement(test.Text, test.Text.TransitionName).
                    Commit();
            }
            else
            {
                var intent = new Intent(Activity, typeof(RoutineActivity));
                intent.PutExtra(RoutineBundleKeys.BLOB_ID, newBlobId);
                intent.PutExtra(RoutineBundleKeys.ROUTINE_TITLE, title);
                StartActivity(intent);
            }
        }

This is the code that is executed when a list item is clicked.

public class PreviewTransition : TransitionSet
{
    public PreviewTransition()
    {
        SetOrdering(TransitionOrdering.Together);
        AddTransition(new ChangeTransform()).
            AddTransition(new ChangeBounds());
    }
}

And finally, this is the class for the animation. Very simple.

So I hope SOMEBODY has an idea of what direction to look in. I really cannot figure out why the return animation works but not the start animation.

Just to clarify, this is a transition between two different fragments. The SubRoutineSelected method can be considered the "First Fragment", and then OnCreateView is the "Second Fragment".

Jason Toms
  • 850
  • 2
  • 10
  • 23

0 Answers0