Android - Как создать переход от элемента в списке к целому действию?

Что я хочу, так это то, что когда пользователь щелкает элемент списка в ListView, он преобразуется в целое действие (как вы можете видеть в следующем примере), но я не смог найти учебник, объясняющий это, и, на самом деле, я не знаю, как это движение называется.

Другими словами, чего я хочу добиться, так это:

  1. Увеличьте высоту элемента списка при нажатии (как вы можете видеть на правом GIF-файле)

  2. Развернуть и преобразовать элемент списка в следующий макет фрагмента/действия, который содержит подробную информацию о выбранном элементе.

введите описание изображения здесь

Я пробовал много переходов, но безуспешно. Может ли кто-нибудь помочь мне в этом?


person Antonio    schedule 28.10.2015    source источник
comment
Рассматривали ли вы возможность использования ActivityOptions.makeSceneTransitionAnimation?   -  person Sir4ur0n    schedule 28.10.2015
comment
На самом деле я использую следующий код: ActivityOptions options = ActivityOptions.makeSceneTransitionAnimation(this, view, getString(R.string.transition_name));   -  person Antonio    schedule 28.10.2015
comment
comment
Привет, @Antonio, если ты сделал это, не мог бы ты поделиться своим кодом или любым примером для достижения этого.   -  person Manu    schedule 29.04.2019


Ответы (4)


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

Однако переходы на предоставленных гифках немного отличаются. Переход в gif слева переводит элемент списка в область содержимого второго действия (панель инструментов остается на месте). На гифке справа переход превращает элемент списка в полный экран второго действия. Следующий код обеспечивает эффект в левом gif. Однако должна быть возможность адаптировать решение с небольшими изменениями, чтобы добиться перехода в правильный gif.

Обратите внимание, что это работает только на Lollipop. Однако на старых устройствах можно сымитировать другой эффект. Кроме того, единственная цель предоставленного кода — показать, как это можно сделать. Не используйте это непосредственно в своем приложении.

Основная деятельность:

public class MainActivity extends AppCompatActivity {

    MyAdapter myAdapter;

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

        setSupportActionBar((Toolbar) findViewById(R.id.toolbar));
        ListView listView = (ListView) findViewById(R.id.list_view);

        myAdapter = new MyAdapter(this, 0, DataSet.get());

        listView.setAdapter(myAdapter);

        listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, final View view, final int position, long id) {
                startTransition(view, myAdapter.getItem(position));
            }
        });
    }

    private void startTransition(View view, Element element) {
        Intent i = new Intent(MainActivity.this, DetailActivity.class);
        i.putExtra("ITEM_ID", element.getId());

        Pair<View, String>[] transitionPairs = new Pair[4];
        transitionPairs[0] = Pair.create(findViewById(R.id.toolbar), "toolbar"); // Transition the Toolbar
        transitionPairs[1] = Pair.create(view, "content_area"); // Transition the content_area (This will be the content area on the detail screen)

        // We also want to transition the status and navigation bar barckground. Otherwise they will flicker
        transitionPairs[2] = Pair.create(findViewById(android.R.id.statusBarBackground), Window.STATUS_BAR_BACKGROUND_TRANSITION_NAME);
        transitionPairs[3] = Pair.create(findViewById(android.R.id.navigationBarBackground), Window.NAVIGATION_BAR_BACKGROUND_TRANSITION_NAME);
        Bundle b = ActivityOptionsCompat.makeSceneTransitionAnimation(MainActivity.this, transitionPairs).toBundle();

        ActivityCompat.startActivity(MainActivity.this, i, b);
    }
}

Activity_main.xml:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <android.support.v7.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        android:background="@color/colorPrimary"
        android:transitionName="toolbar" />

    <ListView
        android:id="@+id/list_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</LinearLayout>

Подробная активность:

public class DetailActivity extends AppCompatActivity {

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

        setSupportActionBar((Toolbar) findViewById(R.id.toolbar));

        long elementId = getIntent().getLongExtra("ITEM_ID", -1);
        Element element = DataSet.find(elementId);


        ((TextView) findViewById(R.id.title)).setText(element.getTitle());
        ((TextView) findViewById(R.id.description)).setText(element.getDescription());

        // if we transition the status and navigation bar we have to wait till everything is available
        TransitionHelper.fixSharedElementTransitionForStatusAndNavigationBar(this);
        // set a custom shared element enter transition
        TransitionHelper.setSharedElementEnterTransition(this, R.transition.detail_activity_shared_element_enter_transition);
    }
}

Activity_detail.xml:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <android.support.v7.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        android:background="@color/colorPrimary"
        android:transitionName="toolbar" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="#abc"
        android:orientation="vertical"
        android:paddingBottom="200dp"
        android:transitionName="content_area"
        android:elevation="10dp">

        <TextView
            android:id="@+id/title"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />

        <TextView
            android:id="@+id/description"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />
    </LinearLayout>
</LinearLayout>

detail_activity_shared_element_enter_transition.xml (/res/transition/):

<?xml version="1.0" encoding="utf-8"?>
<transitionSet xmlns:android="http://schemas.android.com/apk/res/android"
    android:transitionOrdering="together">
    <changeBounds/>
    <changeTransform/>
    <changeClipBounds/>
    <changeImageTransform/>
    <transition class="my.application.transitions.ElevationTransition"/>
</transitionSet>

my.application.transitions.ElevationTransition:

@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public class ElevationTransition extends Transition {

    private static final String PROPNAME_ELEVATION = "my.elevation:transition:elevation";

    public ElevationTransition() {
    }

    public ElevationTransition(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    public void captureStartValues(TransitionValues transitionValues) {
        captureValues(transitionValues);
    }

    @Override
    public void captureEndValues(TransitionValues transitionValues) {
        captureValues(transitionValues);
    }

    private void captureValues(TransitionValues transitionValues) {
        Float elevation = transitionValues.view.getElevation();
        transitionValues.values.put(PROPNAME_ELEVATION, elevation);
    }

    @Override
    public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues, TransitionValues endValues) {
        if (startValues == null || endValues == null) {
            return null;
        }

        Float startVal = (Float) startValues.values.get(PROPNAME_ELEVATION);
        Float endVal = (Float) endValues.values.get(PROPNAME_ELEVATION);
        if (startVal == null || endVal == null || startVal.floatValue() == endVal.floatValue()) {
            return null;
        }

        final View view = endValues.view;
        ValueAnimator a = ValueAnimator.ofFloat(startVal, endVal);
        a.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                view.setElevation((float)animation.getAnimatedValue());
            }
        });

        return a;
    }
}

Помощник по переходу:

public class TransitionHelper {

    public static void fixSharedElementTransitionForStatusAndNavigationBar(final Activity activity) {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP)
            return;

        final View decor = activity.getWindow().getDecorView();
        if (decor == null)
            return;
        activity.postponeEnterTransition();
        decor.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
            @TargetApi(Build.VERSION_CODES.LOLLIPOP)
            @Override
            public boolean onPreDraw() {
                decor.getViewTreeObserver().removeOnPreDrawListener(this);
                activity.startPostponedEnterTransition();
                return true;
            }
        });
    }

    public static void setSharedElementEnterTransition(final Activity activity, int transition) {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP)
            return;
        activity.getWindow().setSharedElementEnterTransition(TransitionInflater.from(activity).inflateTransition(transition));
    }
}

Итак, какие здесь разные части: У нас есть два вида деятельности. Во время перехода между действиями происходит переход между четырьмя представлениями.

  • Панель инструментов: как и на левой картинке, панель инструментов не перемещается вместе с остальным содержимым.

  • Элемент ListView View -> становится представлением содержимого DetailActivity

  • Фон StatusBar и NavigationBar: если мы не добавим эти представления в набор переходных представлений, они будут исчезать и снова появляться во время перехода. Однако для этого требуется задержать переход ввода (см.: TransitionHelper.fixSharedElementTransitionForStatusAndNavigationBar)

В MainActivity переходные представления добавляются в пакет, который используется для запуска DetailActivity. Кроме того, переходные представления должны быть названы (transitionName) в обоих действиях. Это можно сделать как в макете xml, так и программно.

Набор переходов по умолчанию, который используется во время перехода общего элемента, влияет на различные аспекты представления (например: границы представления — см. 2). Однако различия в высоте вида не анимируются. Вот почему в представленном решении используется пользовательский ElevationTransition.

person Andreas Wenger    schedule 06.11.2015
comment
Удивительный ответ. Вы заслужили награду! Поздравляю. Я просто хочу добавить комментарий: у меня были некоторые проблемы с использованием findViewById(android.R.id.navigationBarBackground), потому что он был нулевым. Как я это решаю: добавление следующего кода в переход xml <targets> <target android:excludeId="@android:id/statusBarBackground"/> <target android:excludeId="@android:id/navigationBarBackground"/> </targets> - person Antonio; 06.11.2015
comment
Рад слышать, что ответ оказался полезным. Не вызывает ли это мерцания в статусе и навигационной панели при переходе? Другим решением может быть добавление этих представлений в пакет только в том случае, если они не равны нулю. Я мог бы представить, что navigationBarBackground становится нулевым, потому что он доступен не на всех устройствах? - person Andreas Wenger; 06.11.2015
comment
На самом деле, ты прав. Я не заметил, что панель навигации мерцает во время перехода. Я попытаюсь выяснить, почему это происходит. - person Antonio; 06.11.2015
comment
Решено, @Andreas Wenger! Это было связано с другим переходом, который я выполнял. Теперь это работает как шарм! Спасибо - person Antonio; 06.11.2015
comment
что нужно было импортировать для Element используемого в коде? - person Sourav Roy; 02.11.2016
comment
@SouravRoy Element — это просто DTO для разных элементов в списке. В этом примере у каждого элемента есть идентификатор, заголовок и описание. Статический DataSet предоставляет доступный Elements. Однако для реального приложения DataSet будет, например, базой данных, а Element будет строками в этой базе данных. - person Andreas Wenger; 02.11.2016

попробуйте это.. Material-Animations

blueIconImageView.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        Intent i = new Intent(MainActivity.this, SharedElementActivity.class);

        View sharedView = blueIconImageView;
        String transitionName = getString(R.string.blue_name);

        ActivityOptions transitionActivityOptions = ActivityOptions.makeSceneTransitionAnimation(MainActivity.this, sharedView, transitionName);
        startActivity(i, transitionActivityOptions.toBundle());
    }
});

Общие элементы

person Carlos.V    schedule 28.10.2015
comment
Спасибо за ответ, Карлос. Но то, что я хочу сделать, это именно переход из примера, когда элемент списка становится действием. Я попытался установить одно и то же имя перехода для моего элемента списка и моего самого внешнего родителя, но не повезло. - person Antonio; 28.10.2015
comment
Вы должны увидеть пример исходного кода и увидеть, что отличается. /Material-Animations/archive/master.zip - person Carlos.V; 28.10.2015
comment
Я добавил пример gif, который показывает именно то, что я хочу. Спасибо за ваш ответ. Это действительно интересно, но я не вижу, что я хочу сделать - person Antonio; 28.10.2015

Нужная вам анимация называется Activity Transitions между общими элементами. Исследованиями я обнаружил, что вы должны:

  1. Поместите представление ListView в относительный макет
  2. OnClick, раздуйте копию вашего рендерера
  3. Найдите глобальные координаты того, где средство визуализации находится по отношению к родителю ListView.
  4. Добавьте скопированный рендерер в RelativeLayout (родительский элемент ListView)
  5. Анимировать списокПросмотреть
  6. В конце этой анимации анимируйте новый рендерер
  7. Выгода!

     public class MainActivity extends Activity {
    
     private RelativeLayout layout;
            private ListView listView;
            private MyRenderer selectedRenderer;
    
            @Override
            protected void onCreate(Bundle savedInstanceState) {
                super.onCreate(savedInstanceState);
                layout = new RelativeLayout(this);
                setContentView(layout);
                listView = new ListView(this);
                RelativeLayout.LayoutParams rlp = new RelativeLayout.LayoutParams(
                        RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT);
                layout.addView(listView, rlp);
    
                listView.setAdapter(new MyAdapter());
                listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
                    @Override
                    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
    
                        // find out where the clicked view sits in relationship to the
                        // parent container
                        int t = view.getTop() + listView.getTop();
                        int l = view.getLeft() + listView.getLeft();
    
                        // create a copy of the listview and add it to the parent
                        // container
                        // at the same location it was in the listview
                        selectedRenderer = new MyRenderer(view.getContext());
                        RelativeLayout.LayoutParams rlp = new RelativeLayout.LayoutParams(view.getWidth(), view
                                .getHeight());
                        rlp.topMargin = t;
                        rlp.leftMargin = l;
                        selectedRenderer.textView.setText(((MyRenderer) view).textView.getText());
                        layout.addView(selectedRenderer, rlp);
                        view.setVisibility(View.INVISIBLE);
    
                        // animate out the listView
                        Animation outAni = new TranslateAnimation(Animation.RELATIVE_TO_SELF, 0f,
                                Animation.RELATIVE_TO_SELF, -1f, Animation.RELATIVE_TO_SELF, 0f,
                                Animation.RELATIVE_TO_SELF, 0f);
                        outAni.setDuration(1000);
                        outAni.setFillAfter(true);
                        outAni.setAnimationListener(new Animation.AnimationListener() {
                            @Override
                            public void onAnimationStart(Animation animation) {
                            }
    
                            @Override
                            public void onAnimationRepeat(Animation animation) {
                            }
    
                            @Override
                            public void onAnimationEnd(Animation animation) {
                                ScaleAnimation scaleAni = new ScaleAnimation(1f, 
                                        1f, 1f, 2f, 
                                        Animation.RELATIVE_TO_SELF, 0.5f,
                                        Animation.RELATIVE_TO_SELF, 0.5f);
                                scaleAni.setDuration(400);
                                scaleAni.setFillAfter(true);
                                selectedRenderer.startAnimation(scaleAni);
                            }
                        });
    
                        listView.startAnimation(outAni);
                    }
                });
            }
    
            public class MyAdapter extends BaseAdapter {
                @Override
                public int getCount() {
                    return 10;
                }
    
                @Override
                public String getItem(int position) {
                    return "Hello World " + position;
                }
    
                @Override
                public long getItemId(int position) {
                    return position;
                }
    
                @Override
                public View getView(int position, View convertView, ViewGroup parent) {
                    MyRenderer renderer;
                    if (convertView != null)
                        renderer = (MyRenderer) convertView;
                    else
                        renderer = new MyRenderer(MainActivity.this);
                    renderer.textView.setText(getItem(position));
                    return renderer;
                }
            }
    
            public class MyRenderer extends RelativeLayout {
    
                public TextView textView;
    
                public MyRenderer(Context context) {
                    super(context);
                    setPadding(20, 20, 20, 20);
                    setBackgroundColor(0xFFFF0000);
    
                    RelativeLayout.LayoutParams rlp = new RelativeLayout.LayoutParams(
                            RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
                    rlp.addRule(CENTER_IN_PARENT);
                    textView = new TextView(context);
                    addView(textView, rlp);
                }
    
            }        }
    
person Vishavjeet Singh    schedule 03.11.2015
comment
Спасибо за ваш ответ, @Vishavjeet, но в вашем примере элемент просто масштабируется. Но мне нужно новое окно с панелью инструментов, другими представлениями и т. д. - person Antonio; 03.11.2015

Попробуйте эту впечатляющую веб-страницу @ Начало работы с Переходы действий и фрагментов (часть 1). Здесь говорили об Activity и Fragment Transitions. Я не пробовал. Я считаю, что Fragment Transitions лучше и менее требовательна к компьютеру, так что это хорошее начало. И вам, возможно, не нужно менять панели инструментов, вы можете показать/скрыть их.

Еще одна хорошая ссылка SO - @ Анимировать переход между фрагментами, посмотрите на лучший ответ. В этом посте они говорили об objectAnimator.

Другое мнение по поводу выложенного вами образца анимации, в нем не показана плавная анимация от одного арта к другому. Это менее впечатляет, когда анимация не плавная.

Удачи, получайте удовольствие, держите нас всех в курсе.

person The Original Android    schedule 03.11.2015
comment
Спасибо за ваш ответ. Я работал с общими элементами, но не знаю, как применить их в анимации, которую хочу получить. Я изменил свой пост, добавив больше информации о том, чего я хочу достичь. - person Antonio; 04.11.2015