共享元素转换未正确退出

时间:2018-05-05 12:12:02

标签: java android android-animation android-transitions shared-element-transition

我有片段,我正在启动具有viewpager的共享元素转换的活动,输入转换工作正常但是当我在视图寻呼机中滚动并完成转换时,共享图像来自左侧,这是不希望的应该将自己重新定位到它的启动位置,这是我的代码:

Intent myIntent = new Intent(getActivity(), EnlargeActivity.class);

            ActivityOptionsCompat options = ActivityOptionsCompat.
                    makeSceneTransitionAnimation(getActivity(),
                            imageView,
                            ViewCompat.getTransitionName(imageView));
            startActivity(myIntent, options.toBundle());

我在完成活动时在包含viewpager的活动中更新视图及其名称,但它会闪烁:

public void finishAfterTransition() {
    setEnterSharedElementCallback(new SharedElementCallback() {
        @Override
        public void onMapSharedElements(List<String> names, Map<String, View> sharedElements) {
            // Clear all current shared views and names
            names.clear();
            sharedElements.clear();

            ViewGroup viewGroup = (ViewGroup) viewPagerDetail.getAdapter()
                    .instantiateItem(viewPagerDetail, viewPagerDetail.getCurrentItem());

            if (viewGroup == null) {
                return;
            }

            // Map the first shared element name to the child ImageView.
            sharedElements.put(viewGroup.findViewById(R.id.img).getTransitionName(), viewGroup.findViewById(R.id.img));

           // setExitSharedElementCallback((SharedElementCallback) this);
        }
    });

    super.finishAfterTransition();

2 个答案:

答案 0 :(得分:0)

这实际上是一种默认行为,我在很多方面都在努力争取SharedElementTransitions,但我已经嵌套了片段。我从一篇文章(最近的文章)中得到了我的解决方案,它显示了一个RecyclerView的实现,我猜你有。简而言之,解决方案是覆盖onLayoutChange

recyclerView.addOnLayoutChangeListener(
new OnLayoutChangeListener() {
  @Override
  public void onLayoutChange(View view,
            int left, 
            int top, 
            int right, 
            int bottom, 
            int oldLeft, 
            int oldTop, 
            int oldRight, 
            int oldBottom) {
     recyclerView.removeOnLayoutChangeListener(this);
     final RecyclerView.LayoutManager layoutManager =
        recyclerView.getLayoutManager();
     View viewAtPosition = 
        layoutManager.findViewByPosition(MainActivity.currentPosition);
     // Scroll to position if the view for the current position is null (not   
     // currently part of layout manager children), or it's not completely
     // visible.
     if (viewAtPosition == null 
         || layoutManager.isViewPartiallyVisible(viewAtPosition, false, true)){
        recyclerView.post(() 
           -> layoutManager.scrollToPosition(MainActivity.currentPosition));
     }
 }
});

以下是article,您还可以在GitHub找到该项目。

答案 1 :(得分:0)

基本上,Android会使用预定义的ViewtransitionName启动转换,并自动使用相同的属性进行返回转换。当您在ViewPager中更改焦点视图时,Android不知道这一点,并在返回途中保持对前一个的转换。因此,您需要告知Android有关更改的信息:

  • 重新映射过渡属性:在从setEnterSharedElementCallback返回之前,使用transitionNameViewActivity2更改为新属。
  • 等待Activity1完成呈现addOnPreDrawListener

最终实施中有点复杂。但是你可以查看我的示例代码https://github.com/tamhuynhit/PhotoGallery。我尝试实现从许多简单到复杂部分的共享元素转换。 您的问题出现在Level 3,并在Level 4中解决了。

我正在编写一个关于此的教程,但它不是英文的,所以希望代码可以提供帮助

更新1:工作流程

以下是我在代码中实现的方法:

  • 覆盖Activity2中的finishAfterTransition并调用setEnterSharedElementCallback方法重新映射ViewPager中当前选定的项目。此外,请致电setResult将新选定的索引传回此处的上一个活动。

    @Override 
    @TargetApi(Build.VERSION_CODES.LOLLIPOP) 
    public void finishAfterTransition() {
        setEnterSharedElementCallback(new SharedElementCallback() {
            @Override
            public void onMapSharedElements(List<String> names, Map<String, View> sharedElements) {
                View selectedView = getSelectedView();
                if (selectedView == null)
                    return;
    
                // Clear all current shared views and names
                names.clear();
                sharedElements.clear();
    
                // Store new selected view and name
                String transitionName = ViewCompat.getTransitionName(selectedView);
                names.add(transitionName);
                sharedElements.put(transitionName, selectedView);
    
                setExitSharedElementCallback((SharedElementCallback) null);
            }
        });
    
        Intent intent = new Intent();
        intent.putExtra(PHOTO_FOCUSED_INDEX, mCurrentIndex);
        setResult(RESULT_PHOTO_CLOSED, intent);
    
        super.finishAfterTransition();
    }
    
  • 编写自定义ShareElementCallback,以便我可以在知道将要使用哪个View之前设置回调。

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    private static class CustomSharedElementCallback extends SharedElementCallback {
        private View mView;
    
        /**
         * Set the transtion View to the callback, this should be called before starting the transition so the View is not null
         */
        public void setView(View view) {
            mView = view;
        }
    
        @Override
        public void onMapSharedElements(List<String> names, Map<String, View> sharedElements) {
            // Clear all current shared views and names
            names.clear();
            sharedElements.clear();
    
            // Store new selected view and name
            String transitionName = ViewCompat.getTransitionName(mView);
            names.add(transitionName);
            sharedElements.put(transitionName, mView);
        }
    }
    
  • 在Activity1中覆盖onActivityReenter,从结果Intent中获取所选索引。设置setExitSharedElementCallback以在转换开始时重新映射新选定的View。请supportPostponeEnterTransition稍稍延迟,因为此时可能无法呈现新的View。使用getViewTreeObserver().addOnPreDrawListener来监听布局更改,找到所选索引的正确View并继续转换supportStartPostponedEnterTransition

    @Override
    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    public void onActivityReenter(int resultCode, Intent data) {
        if (resultCode != LevelFourFullPhotoActivity.RESULT_PHOTO_CLOSED || data == null)
            return;
    
        final int selectedIndex = data.getIntExtra(LevelFourFullPhotoActivity.PHOTO_FOCUSED_INDEX, -1);
        if (selectedIndex == -1)
            return;
    
        // Scroll to the new selected view in case it's not currently visible on the screen
        mPhotoList.scrollToPosition(selectedIndex);
    
        final CustomSharedElementCallback callback = new CustomSharedElementCallback();
        getActivity().setExitSharedElementCallback(callback);
    
        // Listen for the transition end and clear all registered callback
        getActivity().getWindow().getSharedElementExitTransition().addListener(new Transition.TransitionListener() {
            @Override
            public void onTransitionStart(Transition transition) {}
    
            @Override
            public void onTransitionPause(Transition transition) {}
    
            @Override
            public void onTransitionResume(Transition transition) {}
    
            @Override
            public void onTransitionEnd(Transition transition) {
                removeCallback();
            }
    
            @Override
            public void onTransitionCancel(Transition transition) {
                removeCallback();
            }
    
            private void removeCallback() {
                if (getActivity() != null) {
                    getActivity().getWindow().getSharedElementExitTransition().removeListener(this);
                    getActivity().setExitSharedElementCallback((SharedElementCallback) null);
                }
            }
        });
    
        // Pause transition until the selected view is fully drawn
        getActivity().supportPostponeEnterTransition();
    
        // Listen for the RecyclerView pre draw to make sure the selected view is visible,
        //  and findViewHolderForAdapterPosition will return a non null ViewHolder
        mPhotoList.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
            @Override
            public boolean onPreDraw() {
                mPhotoList.getViewTreeObserver().removeOnPreDrawListener(this);
    
                RecyclerView.ViewHolder holder = mPhotoList.findViewHolderForAdapterPosition(selectedIndex);
                if (holder instanceof ViewHolder) {
                    callback.setView(((ViewHolder) holder).mPhotoImg);
                }
    
                // Continue the transition
                getActivity().supportStartPostponedEnterTransition();
    
                return true;
            }
        });
    }
    

更新2:getSelectedItem

要从ViewPager获取选定的视图,请不要使用getChildAt,否则您的视图会显示错误,请改用findViewWithTag

PagerAdapter.instantiateItem中,使用position作为每个视图的标记:

@Override
public View instantiateItem(ViewGroup container, int position) {
    // Create the View
    view.setTag(position)

    // ...
}

收听onPageSelected事件以获取所选索引:

mViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
    @Override
    public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {

    }

    @Override
    public void onPageSelected(int position) {
        mSelectedIndex = position;
    }

    @Override
    public void onPageScrollStateChanged(int state) {

    }
});

调用getSelectedView以获取所选索引的当前视图

private View getSelectedView() {
    try {
        return mPhotoViewPager.findViewWithTag(mSelectedIndex);
    } catch (IndexOutOfBoundsException | NullPointerException ex) {
        return null;
    }
}