Переход общего элемента не завершается должным образом

У меня есть фрагмент, из которого я запускаю действие с переходом общего элемента, в котором есть просмотр пейджера, переход ввода работает нормально, но когда я прокручиваю пейджер просмотра и заканчиваю переход, общее изображение появляется с левой стороны, что нежелательно, оно должно изменить свое положение туда, где он был запущен, вот мой код:

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();

person blackHawk    schedule 05.05.2018    source источник


Ответы (2)


По сути, Android начинает переход с предопределенными View и transitionName и автоматически использует те же свойства для обратного перехода. Когда вы меняете сфокусированный вид в ViewPager, Android не знает об этом и сохраняет переход на предыдущий на обратном пути. Итак, вам нужно сообщить Android об изменениях:

  • Переназначьте свойства перехода: используйте setEnterSharedElementCallback, чтобы изменить transitionName и View на новый перед возвратом из Activity2.
  • Подождите, пока Activity1 завершит рендеринг addOnPreDrawListener.

Это немного сложно в окончательной реализации. Но вы можете посмотреть мой пример кода https://github.com/tamhuynhit/PhotoGallery. Я пытаюсь реализовать общий элемент-переход от многих простых разделов к сложным. Ваша проблема возникла с Level 3 и решена в Level 4.

Я пишу учебник об этом, но он не на английском языке, поэтому надеюсь, что код может помочь

ОБНОВЛЕНИЕ 1. Рабочий процесс

Вот как я реализую это в своем коде:

  • Переопределите finishAfterTransition в Activity2 и вызовите метод 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);
        }
    }
    
  • Переопределите onActivityReenter в Activity1, получите выбранный индекс из результата 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 используйте позицию как тег для каждого представления:

@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;
    }
}
person Tam Huynh    schedule 05.05.2018
comment
Вы правы, имя перехода не настроено должным образом, поэтому возникает проблема, я связываю ваш подход - person blackHawk; 05.05.2018
comment
где следует применять setEnterSharedElementCallback - person blackHawk; 05.05.2018
comment
@blackHawk это объясняется в той же статье что ты читал - person Suleyman; 06.05.2018
comment
@blackHawk Я добавил обновление выше в свой ответ, чтобы больше объяснить мой код. - person Tam Huynh; 06.05.2018
comment
@LieForBananas да, пытаюсь понять немного больше, - person blackHawk; 06.05.2018
comment
@TamHuynh, когда будет вызываться onMapSharedElements? на самом деле у меня есть фрагмент, и через фрагмент я запускаю активность с общим переходом элемента, в котором есть просмотр пейджера, - person blackHawk; 06.05.2018
comment
@LieForBananas Я не могу правильно установить имя перехода, потому что элемент создания экземпляра viewpager вызывается 3 раза, и имя перехода стало беспокоить - person blackHawk; 06.05.2018
comment
@blackHawk вызывается 3 раза, вероятно, потому, что ViewPager предварительно загружает страницы из соображений производительности. - person Suleyman; 06.05.2018
comment
где я использую onMapSharedElements, или мне не нужно использовать - person blackHawk; 06.05.2018
comment
@TamHuynh Я пытался, но я не могу получить доступ к представлению, чтобы получить его имя перехода, потому что его адаптер Viewpage не активен. - person blackHawk; 06.05.2018
comment
onMapSharedElements будет вызываться для обоих действий при входе/выходе перехода. Вот почему нам нужно установить обратный вызов на null сразу после завершения работы, чтобы он не испортил следующий переход. Общий элемент из Фрагмента в Активность такой же, как Активность в Активность, потому что фрагмент может получить доступ к своей Деятельности через getActivity(). Вам просто нужно передать вызов от onActivityReenter к фрагменту. Я обновлю ответ, чтобы включить текущий выбор представления ViewPager. - person Tam Huynh; 06.05.2018
comment
Раскомментируйте последнюю строку: setEnterSharedElementCallback(null) важно. И вы установили onActivityReenter в первом действии? Процесс переназначения должен быть выполнен с обеих сторон - person Tam Huynh; 06.05.2018
comment
где onActivityReenter? во фрагменте? - person blackHawk; 06.05.2018
comment
Он есть только в Activity, он вызывается, когда Activity2 завершается, чтобы вернуться к Activity1, но перед переходом напишите свой собственный onActivityReenter во фрагменте и вызовите его из Activity. Реализация представлена ​​выше - person Tam Huynh; 06.05.2018

На самом деле это поведение по умолчанию, я много боролся с 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));
     }
 }
});

Вот статья, и вы также найдите проект на GitHub.

person Suleyman    schedule 05.05.2018
comment
Я использовал ту же статью и многому научился из нее, но здесь другая ситуация, я открываю действие с общим элементом из фрагмента, когда я заканчиваю это действие, оно работает нормально, но когда я добавляю viewpager в действие, а затем завершаю действие, его ведет себя так, как будто имя перехода установлено неправильно, оно идет с правой стороны, где, как и при отсутствии пейджера, изображение появляется прямо, как увеличение, и возвращается назад, как уменьшение, что желательно - person blackHawk; 05.05.2018
comment
можете ли вы понять, что происходит, когда я добавляю просмотр пейджера в действие и заканчиваю действие - person blackHawk; 05.05.2018
comment
@blackHawk Итак, если у вас есть просмотрщик, откройте действие и закройте его, не проводя просмотрщик, он по-прежнему делает то же самое? - person Suleyman; 05.05.2018
comment
добавление просмотра пейджера в действие, вызывающее разницу, независимо от того, поменяюсь я или нет - person blackHawk; 05.05.2018
comment
@blackHawk, как вы сказали, это может быть связано с именами переходов, что содержит ваш просмотрщик? Вы уверены, что имена уникальны? У меня возникла проблема с конфликтующими именами переходов, я использовал табличку с фрагментами которые отображали похожие данные, поэтому мне пришлось добавить номера фрагментов к именам переходов, чтобы различать их. - person Suleyman; 05.05.2018
comment
в экземпляре viewpager я делаю так: img.setTransitionName(simple_activity_transition); - person blackHawk; 05.05.2018
comment
@blackHawk Вы используете FragmentStatePagerAdapter? Вероятно, это связано с тем, что ваше имя перехода одинаково для всех страниц просмотра, попробуйте добавить позицию, когда вы устанавливаете имя перехода, например img.setTransitionName("simple_activity_transition" + position) - person Suleyman; 05.05.2018
comment
Я заметил, предположим, что у меня есть 5 в getCount для просмотра, когда я прокручиваю до 5-го элемента, а затем выхожу из активности, он работает нормально, а когда я прокручиваю до 3 или 4, он не работает - person blackHawk; 05.05.2018
comment
img.setTransitionName(simple_activity_transition + position) работает, но он приходит или уходит с миганием - person blackHawk; 05.05.2018
comment
@blackHawk хорошо, что работает :) все изображение мигает или строка состояния и панель действий? - person Suleyman; 05.05.2018
comment
все изображение мигает - person blackHawk; 05.05.2018
comment
@blackHawk Я считаю, что это распространенная проблема, проверьте этот ответ, он должен помочь, он связан с анимацией действий. . - person Suleyman; 05.05.2018
comment
Я думаю, что проблема все еще заключается в имени перехода, я не могу назначить уникальное имя для каждого изображения. - person blackHawk; 05.05.2018
comment
@blackHawk Я думал, ты сказал, что это работает, когда ты добавляешь позицию? Если нет, то вам нужно подумать о создании уникальных имен, в моем случае я использовал пути к изображениям в качестве имен переходов, возможно, вы можете сделать то же самое, возможно, передать путь к изображению конструктору и добавить его в качестве имени перехода. - person Suleyman; 05.05.2018
comment
Я сделал то же самое для своей предыдущей работы :) для этого, к сожалению (пока), у меня есть только один URL-адрес, как насчет подключения лично? Мне очень нравится с тобой общаться, ты лучше понимаешь :D - person blackHawk; 05.05.2018
comment
Давайте продолжим обсуждение в чате. - person Suleyman; 05.05.2018
comment
Я считаю, что этот фрагмент кода просто прокручивает recyclerview, чтобы убедиться, что текущее отображаемое представление из viewpager видно, когда пользователь решит вернуться к recyclerview. К переходникам отношения не имеет - person Rafsanjani; 22.07.2019