RecyclerView到ViewPager共享元素过渡仅首次点击闪屏问题排查
我看到你在实现RecyclerView到ViewPager的Fragment间共享元素过渡时,遇到了仅首次启动ViewPager闪屏,后续点击过渡正常的问题。结合你提供的代码,我来梳理下可能的原因和对应的解决办法:
可能的原因分析
首次闪屏通常是因为首次启动时ViewPager的Fragment初始化、视图加载与共享元素过渡的时机不匹配,导致过渡过程中出现短暂的视图空白或错位,进而表现为闪屏。而后续操作时,ViewPager和Fragment已经完成初始化,过渡流程就能正常执行。
具体解决办法
1. 避免依赖静态变量传递position,改为通过Fragment参数传递
你当前用MainActivity.position来设置TransitionName,这种静态变量的方式在首次启动时可能存在值传递不及时或者上下文不一致的问题,导致共享元素的TransitionName不匹配,引发闪屏。
修改步骤:
- 在RecyclerView点击事件中,把position通过Bundle传递给
ImagePagerFragment:
holder.iv.setTransitionName(position + "ZoomImage"); ImagePagerFragment productDetailFragment = new ImagePagerFragment(); Bundle args = new Bundle(); args.putInt("current_position", position); productDetailFragment.setArguments(args); getActive.getSupportFragmentManager() .beginTransaction() .setReorderingAllowed(true) .addSharedElement(holder.iv, holder.iv.getTransitionName()) .replace(R.id.fragment, productDetailFragment) .addToBackStack(null) .commit();
- 在
ImagePagerFragment中获取这个position:
private int currentPosition; @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (getArguments() != null) { currentPosition = getArguments().getInt("current_position"); } }
- 在ImageFragment中使用传递过来的position设置TransitionName(可以通过ImagePagerFragment传递给Adapter,再传给ImageFragment)。
2. 确保ViewPager布局完成后再处理共享元素过渡
首次启动时,ViewPager可能还没完成布局,导致instantiateItem获取的Fragment视图为空,共享元素映射失败,引发闪屏。我们可以等待ViewPager布局完成后再调用prepareSharedElementTransition。
修改ImagePagerFragment的代码:
@Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); postponeEnterTransition(); // 等待ViewPager布局完成 viewPager.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { viewPager.getViewTreeObserver().removeOnGlobalLayoutListener(this); prepareSharedElementTransition(); } }); }
3. 优化SharedElementCallback中的共享元素映射
在onMapSharedElements中,先清空原有共享元素映射,再添加新的,避免旧的无效映射干扰过渡:
@Override public void onMapSharedElements(List<String> names, Map<String, View> sharedElements) { // 先清空原有映射 sharedElements.clear(); Fragment currentFragment = (Fragment) viewPager.getAdapter() .instantiateItem(viewPager, viewPager.getCurrentItem()); View view = currentFragment.getView(); if (view == null) { return; } // 使用传递的currentPosition设置正确的TransitionName String targetTransitionName = currentPosition + "ZoomImage"; View targetView = view.findViewById(R.id.image1); targetView.setTransitionName(targetTransitionName); sharedElements.put(targetTransitionName, targetView); }
4. 确保Picasso加载完成后再启动过渡的逻辑严谨性
虽然你已经在Picasso的回调中调用了startPostponedEnterTransition,但首次启动时可能存在多个ImageFragment同时加载的情况,需要确保当前显示的Fragment加载完成后再启动过渡。
修改ImageFragment的代码:
Picasso.with(getActivity()) .load(url) .into(ivZoom, new Callback() { @Override public void onSuccess() { // 确保当前Fragment是ViewPager中显示的那个才启动过渡 ImagePagerFragment parentFragment = (ImagePagerFragment) getParentFragment(); if (parentFragment != null && parentFragment.viewPager.getCurrentItem() == getArguments().getInt("fragment_position")) { ivZoom.setTransitionName(parentFragment.currentPosition + "ZoomImage"); parentFragment.startPostponedEnterTransition(); } } @Override public void onError() { // 错误情况同样处理 ImagePagerFragment parentFragment = (ImagePagerFragment) getParentFragment(); if (parentFragment != null) { ivZoom.setTransitionName(parentFragment.currentPosition + "ZoomImage"); parentFragment.startPostponedEnterTransition(); } } });
这里需要给每个ImageFragment传递它在ViewPager中的位置(fragment_position),用来判断是否是当前显示的Fragment。
总结
这些修改主要围绕统一TransitionName的传递、确保视图初始化完成后再执行过渡、严谨控制过渡启动时机这几个核心点,应该能解决你首次启动时的闪屏问题。你可以先尝试前两个修改,看看是否能解决问题,如果还有问题再逐步添加后面的优化。
内容的提问来源于stack exchange,提问作者blackHawk

