您好,欢迎来到伴沃教育。
搜索
您的当前位置:首页Android 共享元素的基本使用以及一些需要注意的坑

Android 共享元素的基本使用以及一些需要注意的坑

来源:伴沃教育

先说fragment 到fragment如何实现共享元素。

假设我们recycleview(fragment中)的某一个item中点击图片,去到另外一个item中(可以理解为微信朋友圈查看照片)。

首先我们要在adapter中的每一个图片imageview(或者是其他的)设置transitionName,防止重复

关键代码

@Override
    public void onBindViewHolder(final MyViewHolder holder, final int position) {
        //省略
        
        ViewCompat.setTransitionName(holder.getImageView(), url);
        //省略
        
    }

使用ViewCompat.setTransitionName为每一个image绑定他自己的照片url,防止重复(前提是你的照片url不重复)

我们将adapter的图片的fragment定义为界面1 显示大图的fragment定义为界面2

此时我们的界面1的每一个imageView 都绑定了transitionName,然后我们需要界面2的照片也设置一个transiotionName

这是界面2的xml设置

<ImageView
        android:id="@+id/detail_image"
        android:layout_width="240dp"
        android:layout_height="240dp"
        android:transitionName="TransitionName"
        tools:ignore="UnusedAttribute"/>

trasitionName可以静态xml设置 也可以动态设置

两边都设置后 就可以进行跳转了

//界面2的fragment
DetailFragment detailFragment = DetailFragment.newInstance(position);

            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                detailFragment.setSharedElementEnterTransition(new DetailTransition());
                setExitTransition(new Fade());
                detailFragment.setEnterTransition(new Fade());
                detailFragment.setSharedElementReturnTransition(new DetailTransition());
            }

            getActivity().getSupportFragmentManager().beginTransaction()
                    .addSharedElement(holder.getImageView(), "TransitionName")
                    .replace(R.id.main_cl_container, detailFragment)
                    .addToBackStack(null)
                    

其中需要注意的是 addSharedElement这里 第一个参数是界面一的imageview 参数2是界面2的imageview的transitionName。

image

以上就是fragment跳转fragment的关键代码。
那么有哪些呢?

不能用add只能用replace

fragment跳转的时候 不能用add 只能用replace。我在这里卡住一段时间 最后发现不能用add

加载网络图片会有问题

因为如果下载图片时间比transition共享的动画慢的话 动画会有问题

怎么解决呢

  1. 我在StackOverflow找到的 但是我没试验成功()
There's no direct equivalent in Fragment Transitions because Fragments use FragmentTransaction and we can't really postpone something that is supposed to happen in a transaction.

To get the equivalent, you can add a Fragment and hide it in a transaction, then when the Fragment is ready, remove the old Fragment and show the new Fragment in a transaction.

getFragmentManager().beginTransaction()
    .add(R.id.container, fragment2)
    .hide(fragment2)
    

Later, when fragment2 is ready:

getFragmentManager().beginTransaction()
    .addSharedElement(sharedElement, "name")
    .remove(fragment1)
    .show(fragment2)
    
  1. 使用activity 而不使用fragment。
    我强烈建议使用方法二(因为方法1没成功,只能用2)

使用activty的话 要提到两个关键字
postponeEnterTransition()和startPostponedEnterTransition()
第一个是暂停共享元素动画,第二个是开始共享元素动画。这两个方法只能在activity中使用,因此建议使用activity

好,上代码
首先照旧,在界面1的adapter中 绑定每一个imageview的transitionName

ViewCompat.setTransitionName(holder.getImageView(), url);

界面1可以是activity也可以是fragment 都不影响
然后跳转方式要换成activity的跳转方式

ActivityOptionsCompat activityOptions = ActivityOptionsCompat.makeSceneTransitionAnimation(
                            getActivity(),
                            new Pair<View, String>(view,
                                    o.getText())
                    );
                    Intent intent = new Intent(getActivity(), PhotoActivity.class);
                    intent.putExtra("url", o.getText());
                    ActivityCompat.startActivity(getActivity(), intent, activityOptions.toBundle());

其中new Pair<View, String>(view,o.getText()) 第一个参数就是要进行共享元素的view(在这里是imageView) 第二个参数就是界面2的view的transitionName。界面1和界面2的view的transitionName要一致,因此我传递了url过去界面2

界面2:
在界面2的activty中 直接套上之前的fragment就行
然后设置界面2的imageview的transitionName,这里我们动态进行设置

getActivity().postponeEnterTransition();//先停止
        ViewCompat.setTransitionName(photoView, url);//然后设置transitionName, photoview为界面2要进行共享元素的view

然后使用glide4.0加载网络图片(如果是glide之前版本 写法不一样 不可照搬)

Glide
                .with(App.getContext())
                .load(url)
                .apply(options)
                .listener(new RequestListener<Drawable>() {
                    @Override
                    public boolean onLoadFailed(@Nullable GlideException e, Object model, Target<Drawable> target, boolean isFirstResource) {
                        scheduleStartPostponedTransition(photoView);
                        return true;
                    }

                    @Override
                    public boolean onResourceReady(Drawable resource, Object model, Target<Drawable> target, DataSource dataSource, boolean isFirstResource) {
                        photoView.setImageDrawable(resource);
                        scheduleStartPostponedTransition(photoView);
                        return true;
                    }
                })
                .into(photoView);

重点是listener里面的scheduleStartPostponedTransition()方法

if (sharedElement == null) return;
        sharedElement.getViewTreeObserver().addOnPreDrawListener(
                new ViewTreeObserver.OnPreDrawListener() {
                    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
                    @Override
                    public boolean onPreDraw() {
                        if (sharedElement == null) return false;
                        sharedElement.getViewTreeObserver().removeOnPreDrawListener(this);
                        getActivity().startPostponedEnterTransition();//开启动画
                        return true;
                    }
                });

通过activity 就能使用这两个方法进行延时了。

还需要注意的是
如果发现动画能运行 但是定位到图片上的时候有偏差 那可能是你设置了centerCrop之类的属性 查看一下 去掉 就行了

fragment多次调用onCreateView导致动画部分失效

如果还是不行得话 看看有没有可能是fragment多次调用onCreateView,打一下log

解决方法就是判断是否是第一次加载root view

private View rootView;

//oncreateview
if (null != rootView) {
//不是第一次
            ViewGroup parent = (ViewGroup) rootView.getParent();
            if (parent != null) {
                parent.removeView(rootView);
            }
            unbinder = ButterKnife.bind(this, rootView);////这一句是因为用了ButterKnife注入 没有用得忽视就行
        } else {
//第一次
            rootView = inflater.inflate(R.layout.fragment_gallery, container, false);
            unbinder = ButterKnife.bind(this, rootView);//这一句是因为用了ButterKnife注入 没有用得忽视就行

            EventBus.getDefault().register(this);
            initRv();
            initToolbar();
            initData();
            initEvent();
        }
        return rootView;

Copyright © 2019- bangwoyixia.com 版权所有 湘ICP备2023022004号-2

违法及侵权请联系:TEL:199 1889 7713 E-MAIL:2724546146@qq.com

本站由北京市万商天勤律师事务所王兴未律师提供法律服务