Material Design: добавлена ​​круговая анимация раскрытия при переключении между панелями инструментов.

Я читал раздел «Выбор» в руководстве по дизайну материалов (https://material.io/guidelines/patterns/selection.html), и одним из эффектов, которые я хотел добавить в свое приложение, была круговая анимация раскрытия при переключении между панелью приложения и ActionMode? Другая панель инструментов?

Вот пример из руководства: https://storage.googleapis.com/material-design/publish/material_v_10/assets/0Bwp7jtSjaW36RGF3eUFsRkdqU1U/patterns_selection_item_controlling_desktop_click.webm

Я не нашел никаких объяснений о том, как это сделать. Я даже не знаю, используют ли они ActionMode или что-то еще...

Есть ли кто-нибудь, кто мог бы дать мне хорошее направление для подражания?

изменить: minSdk 21

редактировать 2: посмотрите на строку состояния, которая также меняется...

Спасибо, Франсуа.


person François BOURLIEUX    schedule 30.01.2017    source источник


Ответы (2)


Хорошо, наконец, я нашел решение.

Это не очень хорошо... но у меня нет другой идеи использовать что-то другое, поэтому, если у вас есть другие советы, которыми вы можете поделиться... добро пожаловать!

Вот окончательный результат и код ниже:

Шаг 0

Шаг 1

Шаг 2

Пример проекта на GitHub

https://github.com/fbourlieux/android-material-circular_reveal_animation

Цель и идея

Переключение с панели инструментов на другую с помощью «плавной» круговой раскрывающейся анимации. Эта анимация должна обновить панель приложения и строку состояния.

Для этого сначала нам нужно заставить активность отображать свое содержимое в строке состояния, используя свойство android:fitsSystemWindows=false в основном контейнере макета и <item name="android:windowTranslucentStatus">true</item> в теме приложения. Основываясь на этом, мы создадим не только Toolbar, но и вид, который будет отображаться в строке состояния, просто чтобы нарисовать красивый фон во время анимации. Вот момент, который мне не нравится в моем образце, но другого решения я не нашел.

Давайте посмотрим код

styles.xml

<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
    <!-- Customize your theme here. -->
    <item name="colorPrimary">@color/colorPrimary</item>
    <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
    <item name="colorAccent">@color/colorAccent</item>
    <item name="android:windowTranslucentStatus">true</item>
</style>

мы только что добавили свойство android:windowTranslucentStatus.

app_bar_main.xml

<android.support.design.widget.CoordinatorLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="false"
    tools:context="sample.test.fbo.circularrevealanimation.MainActivity">

    <android.support.design.widget.AppBarLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:theme="@style/AppTheme.AppBarOverlay">

        <!-- used to force the two toolbars to display above each other -->
        <RelativeLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent">

            <!-- initial toolbar layout with the status bar 
            and the original toolbar. That layout need to have a 
            background to show the elevation even if it will never 
            be visible (because of inner component backgrounds) -->
            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:background="@color/colorPrimary"
                android:elevation="4dp"
                android:orientation="vertical">

                <!-- status bar background: height of 24dp 
                and initial color darker than the toolbar color -->
                <View
                    android:layout_width="match_parent"
                    android:layout_height="24dp"
                    android:background="@color/colorPrimaryDark" />

                <!-- main toolbar. A very basic one.-->
                <android.support.v7.widget.Toolbar
                    android:id="@+id/toolbar"
                    android:layout_width="match_parent"
                    android:layout_height="?attr/actionBarSize"
                    android:background="?attr/colorPrimary"
                    app:popupTheme="@style/AppTheme.PopupOverlay" />

            </LinearLayout>

            <!-- reveal section layout. Here is our second toolbar
            section which will be animated. It contains a view to
            fake the status bar background and the second toolbar
            to display. -->
            <LinearLayout
                android:id="@+id/revealedToolBar"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:background="@color/colorAccentDark"
                android:elevation="4dp"
                android:orientation="vertical"
                android:visibility="invisible">

                <!-- revealed status bar. Just to change it background. -->
                <View
                    android:id="@+id/revealBackgroundStatus"
                    android:layout_width="match_parent"
                    android:layout_height="24dp"
                    android:background="@color/colorAccentDark" />

                <!-- revealed toolbar. The second one with in our case 
                a simple button and text inside. -->
                <android.support.v7.widget.Toolbar
                    android:id="@+id/toolbar2"
                    android:layout_width="match_parent"
                    android:layout_height="?attr/actionBarSize"
                    android:background="@color/colorAccent"
                    app:popupTheme="@style/AppTheme.PopupOverlay">

                    <!-- a click on that button will trigger 
                         the animation close event -->
                    <ImageButton
                        android:id="@+id/toolbar_arrow"
                        android:layout_width="48dp"
                        android:layout_height="48dp"
                        android:background="@android:color/transparent"
                        android:src="@drawable/arrow_left" />

                    <TextView
                        android:layout_width="match_parent"
                        android:layout_height="match_parent"
                        android:layout_marginLeft="24dp"
                        android:fontFamily="sans-serif-regular"
                        android:gravity="center_vertical"
                        android:text="Foo Bar Baz"
                        android:textColor="@android:color/white"
                        android:textSize="18sp"
                        android:textStyle="bold"
                        tools:text="Foo Bar Baz" />
                </android.support.v7.widget.Toolbar>
            </LinearLayout>

        </RelativeLayout>
    </android.support.design.widget.AppBarLayout>

    <!-- content_main just contains a ToggleButton to trigger 
    the animation-->
    <include layout="@layout/content_main" />

</android.support.design.widget.CoordinatorLayout>

Создайте 2 перекрывающихся макета, которые содержат представление для рисования строки состояния и представление для рисования панели инструментов. По умолчанию макет для анимации установлен невидимым.

MainActivity.java

public class MainActivity extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener, View.OnClickListener {

    private final static int ANIMATION_DURATION = 400;
    private ToggleButton mActionButton;
    private View mRevealedToolBar;
    private ImageButton mArrowButton;
    private boolean mIsHidden = true;

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

        // main toolbar
        final Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        final DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
        final ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(this, drawer, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close);
        drawer.setDrawerListener(toggle);
        toggle.syncState();

        final NavigationView navigationView = (NavigationView) findViewById(R.id.nav_view);
        navigationView.setNavigationItemSelectedListener(this);

        // trigger circular reveal animation
        mActionButton = (ToggleButton) findViewById(R.id.actionButton);
        mActionButton.setOnClickListener(this);

        // toolbar view to reveal. Inivisible by default
        mRevealedToolBar = findViewById(R.id.revealedToolBar);
        mRevealedToolBar.setVisibility(View.INVISIBLE);

        // button in revealed toolbar to dismiss it
        mArrowButton = (ImageButton) findViewById(R.id.toolbar_arrow);
        mArrowButton.setOnClickListener(this);
    }

    @Override
    public void onBackPressed() {
        final DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
        if (drawer.isDrawerOpen(GravityCompat.START)) {
            drawer.closeDrawer(GravityCompat.START);
        } else {
            super.onBackPressed();
        }
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(final MenuItem item) {
        if (item.getItemId() == R.id.action_settings) {
            return true;
        }
        return super.onOptionsItemSelected(item);
    }

    @SuppressWarnings("StatementWithEmptyBody")
    @Override
    public boolean onNavigationItemSelected(final MenuItem item) {
        DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
        drawer.closeDrawer(GravityCompat.START);
        return true;
    }



    @Override
    public void onClick(final View view) {

        if (view == mActionButton || view == mArrowButton) {


            // compute started X and Y co-ordinates for the animation + radius
            int x = mRevealedToolBar.getLeft();
            int y = mRevealedToolBar.getBottom();
            int startRadius = 0;
            int endRadius = Math.max(mRevealedToolBar.getWidth(), mRevealedToolBar.getHeight());
            int reverseStartRadius = endRadius;
            int reverseEndRadius = startRadius;



            if (mIsHidden) {

                // show secondary toolbar
                // performing circular reveal when icon will be tapped
                Animator animator = ViewAnimationUtils.createCircularReveal(mRevealedToolBar, x, y, startRadius, endRadius);
                animator.setInterpolator(new AccelerateDecelerateInterpolator());
                animator.setDuration(ANIMATION_DURATION);

                mRevealedToolBar.setVisibility(View.VISIBLE);
                animator.start();
                mIsHidden = false;


            } else {

                // dismiss secondary toolbar
                // performing circular reveal for reverse animation
                Animator animate = ViewAnimationUtils.createCircularReveal(mRevealedToolBar, x, y, reverseStartRadius, reverseEndRadius);
                animate.setInterpolator(new AccelerateDecelerateInterpolator());
                animate.setDuration(ANIMATION_DURATION);

                // to hide layout on animation end
                animate.addListener(new AnimatorListenerAdapter() {
                    @Override
                    public void onAnimationEnd(Animator animation) {
                        super.onAnimationEnd(animation);
                        mRevealedToolBar.setVisibility(View.INVISIBLE);
                        mIsHidden = true;
                    }
                });

                mRevealedToolBar.setVisibility(View.VISIBLE);
                animate.start();
            }
        }
    }
}

Итак, в MainActivity, после прослушивания события onclick моего ToggleButton, я запускаю анимацию моей второй группы панелей инструментов (представление состояния + панель инструментов), используя методы ViewAnimationUtils.createCircularReveal. Первый аргумент — это представление для анимации, за которым следует начальная координата анимации, а затем — радиус.

В методе onClick я также запускаю анимацию reverse, когда я нажимаю стрелку или второй раз на моем ToggleButton.

Наконец, это довольно простое решение, даже если нам нужно подделать фон строки состояния.

Надеюсь, мое решение может помочь кому-то.

Франсуа

Полезные ссылки:

person François BOURLIEUX    schedule 31.01.2017

Вы можете использовать пользовательскую библиотеку ripple для такой анимации. Его можно найти здесь.

Затем, чтобы иметь тот же эффект, ваша панель инструментов будет находиться в rippleview, как указано в инструкциях к пользовательской библиотеке. Вам нужно иметь координаты, откуда должен начинаться волновой эффект, для вашего случая эта координата является центром плавающей кнопки действия со знаком плюс. Затем, при выборе элемента, запустите эту пульсирующую анимацию и исчезните с плавающей кнопкой действия, вы получите тот же эффект.

Некоторые подобные примеры можно увидеть на вышеупомянутой странице библиотеки.

Надеюсь, поможет !

person Abhinav Puri    schedule 30.01.2017