Shared Element Transitions - Part 4: RecyclerView

原文地址:http://mikescamell.com/shared-element-transitions-part-4-recyclerview/

在系列的第三节我们看到了如何在使用Picasso或Glide时使用共享元素过渡。

在第四节,我们准备通过使用应用程序中热门的共享元素过渡组件RecyclerView来实现这个效果。Google Play Music是我在第一节中提到过的例子,当然还有很多其他的例子。Pocket Casts便是另外一个不错的例子。

我们会做一个类似的效果,从动物相册进入到一个详细的介绍页面。这便是我们的两个页面:
Gallery View and Animal Detail Screen

我会举三个例子。其中两个使用Activity和Fragment来实现从RecyclerView进入简单的详情页。最后一个使用ViewPager中的RecyclerView来跳转。

这里有一个重点我想要先说明,以免你决定跳过本文的其他部分。这就是共享元素过渡需要一个唯一的过渡名称。当在RecyclerView中使用它们时,这一点很容易被遗忘。闲话少说,让我们进入正题。

公共代码

首先让我们看看一些例子中的公共代码。让我们从相册条目的布局开始。

动物相册条目布局

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="150dp"
    android:layout_marginBottom="16dp">

    <ImageView
        android:id="@+id/item_animal_square_image"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</RelativeLayout>

没什么特别的,但是需要注意的关键是ImageView并没有设置transitionName。

动物相册Adapter

public class AnimalGalleryAdapter extends RecyclerView.Adapter<AnimalGalleryAdapter.ImageViewHolder> {

    private final AnimalItemClickListener animalItemClickListener;
    private ArrayList<AnimalItem> animalItems;

    public AnimalGalleryAdapter(ArrayList<AnimalItem> animalItems, AnimalItemClickListener animalItemClickListener) {
        this.animalItems = animalItems;
        this.animalItemClickListener = animalItemClickListener;
    }

    ....

    @Override
    public void onBindViewHolder(final ImageViewHolder holder, int position) {
        final AnimalItem animalItem = animalItems.get(position);

        Picasso.with(holder.itemView.getContext())
                .load(animalItem.imageUrl)
                .into(holder.animalImageView);

        ViewCompat.setTransitionName(holder.animalImageView, animalItem.name);

        holder.itemView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                animalItemClickListener.onAnimalItemClick(holder.getAdapterPosition(), animalItem, holder.animalImageView);
            }
        });
    }

        holder.itemView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                animalItemClickListener.onAnimalItemClick(position, animalItem, holder.animalImageView);
            }
        });
    }
}

这是我们的AnimalGalleryAdapter。在所有例子中,它会被用来显示相册图片。

在第六行的构造函数中,我们需要一个AnimalItemClickListener用来处理有关Activity或Fragment启动动物详情页面的回调。

在onBindViewHolder方法中我们会设置transitionName。正如之前提到的,在布局层级中它必须是唯一的,所以我们使用动物的名称对我们来说应该是唯一的。最好的方式是如果你可以使用一些源自你的类型中的唯一标识符。如果我们在XML中设置transitionName,那么所有相册中的ImageView都会有相同的名称,这意味着党我们回调相册时,框架就不知道需要移动那个图片了。这样的结果会是图片移动很怪异。第21行我们通过ViewCompat来给ImageView设置transitionName。

在26行当图片被点击时会触发onAnimalItemClick。我们给它了位置,被点击的AnimalItem,ImageView。正如之前的例子所做的,我们可以使用ViewCompat来调用ImageView的getTransitionName。

动物条目

public class AnimalItem implements Parcelable {

    public String name;
    public String detail;
    public String imageUrl;

    ...
}

我们的AnimalItem非常简单。我们需要图片的URL来告诉Picasso(可以简单的替换为Glide)加载什么图片到我们的详情Activity或Fragment。因为是Parcelable,所以我们可以通过在Intent中添加额外的Bundle来传递它。

RecyclerView到Activity

这可能是最简单的实现。在我们的RecyclerViewActivity,我们只需要处理onAnimalItemClick并启动AnimalDetailActivity。

public class RecyclerViewActivity extends AppCompatActivity implements AnimalItemClickListener {

    //Code to setup the RecyclerView and Adapter

    @Override
    public void onAnimalItemClick(int pos, AnimalItem animalItem, ImageView sharedImageView) {
        Intent intent = new Intent(this, AnimalDetailActivity.class);
        intent.putExtra(EXTRA_ANIMAL_ITEM, animalItem);
        intent.putExtra(EXTRA_ANIMAL_IMAGE_TRANSITION_NAME, ViewCompat.getTransitionName(sharedImageView));

        ActivityOptionsCompat options = ActivityOptionsCompat.makeSceneTransitionAnimation(
                this,
                sharedImageView,
                ViewCompat.getTransitionName(sharedImageView));

        startActivity(intent, options.toBundle());
    }

}

第8行和第9行,我们把我们的AnimalItem设置到Bundle中并将其传递到AnimalDetailActivity中。第11至14行,我们做了和第一节一样的事情,通过AnimalGalleryAdapter中传递过来的ImageView和transitionName来创建ActivityOptions。当我们在第16行调用startActivity时,我们添加ActivityOptions

现在我们需要计划AnimalDetailActivity了。

public class AnimalDetailActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_animal_detail);
        supportPostponeEnterTransition();

        Bundle extras = getIntent().getExtras();
        AnimalItem animalItem = extras.getParcelable(RecyclerViewActivity.EXTRA_ANIMAL_ITEM);

        ImageView imageView = (ImageView) findViewById(R.id.animal_detail_image_view);
        TextView textView = (TextView) findViewById(R.id.animal_detail_text);
        textView.setText(animalItem.detail);

        String imageUrl = animalItem.imageUrl;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            String imageTransitionName = extras.getString(RecyclerViewActivity.EXTRA_ANIMAL_IMAGE_TRANSITION_NAME);
            imageView.setTransitionName(imageTransitionName);
        }

        Picasso.with(this)
                .load(imageUrl)
                .noFade()
                .into(imageView, new Callback() {
                    @Override
                    public void onSuccess() {
                        supportStartPostponedEnterTransition();
                    }

                    @Override
                    public void onError() {
                        supportStartPostponedEnterTransition();
                    }
                });
    }
}

我们需要在Picasso通过图片链接完成图片加载前停止AnimalDetailActivity的加载。所以第7行我们调用了supportPostponeEnterTransition来告诉它等待。第19行我们从extras中得到了transitionName并将其设置给AnimalDetailActivity的ImageView。这很重要,我们不在AnimalDetailActivity的布局XML中设置transitionName。尽管这不是必需的,如果你想要在布局中设置ImageView的transitionName也是可以的,但是你的确需要在创建RecyclerViewActivity的ActivityOptionsCompat时设置它。第23至36行我们使用Picasso来加载图片并在onSuccess和onError方法中设置回调supportStartPostponedEnterTransition来告诉AnimalDetailActivity它可以启动了。在第三节已经详细讲解过。一切都应该正常运行。

RecyclerView到Fragment

与上面Activity例子中的代码相比,使用Fragment并没有很大的区别。所以我们只需要了解关键的不同点。这里我们的RecyclerViewFragment通过Activity持有的Fragments加载而来。

public class RecyclerViewFragment extends Fragment implements AnimalItemClickListener {

    //Code to setup RecyclerView and Adapter

    @Override
    public void onAnimalItemClick(int pos, AnimalItem animalItem, ImageView sharedImageView, String transitionName) {
        Fragment animalDetailFragment = AnimalDetailFragment.newInstance(animalItem, transitionName);
        getFragmentManager()
                .beginTransaction()
                .addSharedElement(sharedImageView, ViewCompat.getTransitionName(sharedImageView))
                .addToBackStack(TAG)
                .replace(R.id.content, animalDetailFragment)
                .commit();
    }
}

在onAnimalItemClick方法中我们创建了我们的FragmentTransaction。在第10行我们添加了从AnimalGalleryAdapter传递而来的ImageView。我们可以使用它来获得transitionName。就像Activity例子中的一样,我们需要将我们的AnimalItem和transitionName传递下去,正如我们在第7行创建我们的Fragment中所做的。

public class AnimalDetailFragment extends Fragment {

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        postponeEnterTransition();
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            setSharedElementEnterTransition(TransitionInflater.from(getContext()).inflateTransition(android.R.transition.move));
        }
    }

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

        AnimalItem animalItem = getArguments().getParcelable(EXTRA_ANIMAL_ITEM);
        String transitionName = getArguments().getString(EXTRA_TRANSITION_NAME);

        TextView detailTextView = (TextView) view.findViewById(R.id.animal_detail_text);
        detailTextView.setText(animalItem.detail);

        ImageView imageView = (ImageView) view.findViewById(R.id.animal_detail_image_view);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            imageView.setTransitionName(transitionName);
        }

        Picasso.with(getContext())
                .load(animalItem.imageUrl)
                .noFade()
                .into(imageView, new Callback() {
                    @Override
                    public void onSuccess() {
                        startPostponedEnterTransition();
                    }

                    @Override
                    public void onError() {
                        startPostponedEnterTransition();
                    }
                });
    }
}

又一次和Activity的例子一样,我们需要告诉Fragment来等待加载,直到我们在第7行通过postponeEnterTransition()告诉它何时加载。第9行我们调用了setSharedElementTransition(),否则我们不能如愿,我们仅通过在Android资源文件中告诉它我们需要何种过渡效果来初始化过渡运动。

第25行,我们在ImageView上设置了transitionName。和Activity的例子一样,它不是必须的,我们可以通过XML来设置一个transitionName并在这使用它。我们通过Picasso来加载图片(28-41),并且确认我们在回调的onSuccess() 和OnError()方法中调用startPostponedEnterTransition(),这样确保Fragment实际加载。

真如你所见的,除了Fragment的特殊调用,Activity和Fragment的例子非常的相近。

RecyclerView到ViewPager

上周我被问到了这个场景,所以我决定在这将它抛出来。这仅仅是这个例子的预告。正如你所了解的那样ViewPager的特性是可以滑动改变页面。你可能滑动了ViewPager,不再持有第一次你进行共享元素过渡的View了。如果你这样做了你可能在毗邻的页面,所以当你按返回键时第一张图片过渡回来而不是你希望的。

使用ViewPager几乎和上面Fragment的例子没有什么不同,这是部分片段:

public class AnimalViewPagerFragment extends Fragment {

    ///Other setup code

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        postponeEnterTransition();
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            setSharedElementEnterTransition(TransitionInflater.from(getContext()).inflateTransition(android.R.transition.move));
        }
        setSharedElementReturnTransition(null);
    }
}

在第12行,我们调用了setSharedElementReturnTransition()并设置它为空,所以它不会返回共享元素过渡。这样,当你点击返回键时,不会再有奇怪的过渡了。如果你知道解决办法我很乐意倾听,我有几个自己的想法,但我本周没有时间去尝试它们。注意我在将要创建的Fragment中调用它,而不是加载它的那个。

源代码可以在这里的recycler_view下找到。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值