четверг, 7 января 2016 г.

Material Design. Простая менюшка.




Всем доброго времени суток и с Рождеством!) Сегодня я хотел рассказать о создании незамысловатого, но довольно удобного и простого меню в стиле Material Design. Начнем!


Создадим новый проект и в шаблонах выберем Blank Activity



Запускаем, проверяем - все работает отлично. Далее заходим в наш сгенерированный main*.xml (у всех по разному, кто как назвал) и смотрим что же у нас там есть. Там у нас AppBarLayout, Toolbar, FloatingActionButton в обвертке CoordinatorLayout, не плохо так). По замыслу, мы будем создавать меню, которое будет как бы появляться/расширять наш Toolbar. Для этого под Toolbar помещаем RelativeLayout. Сделаем ей высоту в 1dp и цвет самого Toolbar. В результате должно получиться такое:
Можно удалить FloatingActionButton, тут мы его использовать не будем.
Открываем код нашего активити, удаляем onCreateOptionsMenu метод и инициализацию FloatingButton, они нам не пригодятся. Далее добавляем пару строк после объявления Toolbar

  getSupportActionBar().setDisplayHomeAsUpEnabled(true);
  toolbar.setNavigationIcon(R.drawable.ic_burger);

Мы включили Home кнопку и поменяли ей иконку. Самое простое закончилось), дальше мы будем анимировать размеры добавленного RelativeLayout.

Может возникнуть вопрос:"Зачем эта возня с левыми слоями, почему просто не манипулировать размерами самого Tollbar-a?". Загвоздка тут в том, что контейнер тулбара - эластичный, изменяя размеры тулбара весь контент (Название активити, функциональный кнопки и тд.) остается в его центре и даже если установить выравнивание, то ничего хорошего при растяжении  у вас не получиться. А RelativeLayout всегда был хорошим помощником в создании кастомных меню.   

Создадим новый класс, для него я создал отдельный пакет Utils - AnimatingUtils.

Тут мы будем творить чудо нашего "кинематографа". В первую очередь нам понадобиться база, на которой будет работать анимация размеров. Добавим метод slideAnimator.

public static ValueAnimator slideAnimator(int start, int end, final View v) {
        ValueAnimator animator = ValueAnimator.ofInt(start, end);

        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator) {
                int value = (Integer) valueAnimator.getAnimatedValue();
                ViewGroup.LayoutParams layoutParams = v.getLayoutParams();
                layoutParams.height = value;
                v.setLayoutParams(layoutParams);
            }
        });
        return animator;
}

Из метода понятно три вещи: анимация довольно проста, метод универсален почти для любого View и все методы данного класса будут статичными.

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

Чуть не забыл! :)  В layout у нас лежит content_main.xml, который вставляется под наш тулбар. Зайдем не надолго на чаек. Удалим текст "Hello Word" и добавим RelativeLayout, сделаем его прозрачным и назначим ему черный цвет и удалим стандартные отступы внутри. Чуть позже я расскажу зачем мы это сделали.

Вернемся к нашему аниматору. Добавим одну переменную - animProgress. Она понадобиться для отслеживания прогресса анимации. И объявим следом AnimatorListener, его я сделал приватным, он понадобиться только в этом классе. И сразу установим значение animProgress: при старте = true, при окончании = false.

 public static boolean animProgress = false;

 private static Animator.AnimatorListener animator = new Animator.AnimatorListener() {

        @Override
        public void onAnimationStart(Animator animation) {
            animProgress = true;
        }

        @Override
        public void onAnimationEnd(Animator animator) {
            animProgress = false;
        }

        @Override
        public void onAnimationCancel(Animator animation) {

        }

        @Override
        public void onAnimationRepeat(Animator animation) {

        }
    };


Осталось дело за малым. Объявим метод expand. В нем определим границы нашего меню.

 public static void expand(View view, final View hide) {

        final int widthSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
        final int heightSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
        view.measure(widthSpec, heightSpec);

        final float size_menu = hide.getHeight()/3;

        ValueAnimator mAnimator = slideAnimator(1, (int)size_menu, view);
        mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                int animProgress = (Integer) animation.getAnimatedValue();
                float anim = animProgress / size_menu / 2.0f;
                if (anim <= 0.5f) {
                    hide.setAlpha((float) anim);
                } else {
                    hide.setAlpha(0);
                }
            }
        });

        mAnimator.addListener(animator);

        mAnimator.start();
    }

Тут нам и понадобится тот слой, который мы добавили в content_main. Он будет создавать впечатление затухания основного слоя при появлении меню.

Определим константу size_menu и возьмем 1/3 от скрытого слоя, он у нас растянут по габаритам экрана. на самом деле это костыль и лучше бы передавать сразу 1/3 от экрана, но для нашей цели сойдет ^_^.

И еще один момент, почему RelativeLayout нашего меню имеет высоту 1dp и в sliderAnimator мы передаем начальную высоту 1, а не 0. Встретил статью, что android версии ниже 5 не адекватно воспринимает анимацию слоев с высотой в 0.

Добавляем UpdateListener, он понадобиться что бы в рантайме изменять прозрачность слоя затухания.

Тянуть кота за уши не будем и сразу добавим метод collapse.

public static void collapse(final View v, final View hide) {
        int finalHeight = v.getHeight();
        ValueAnimator mAnimator = slideAnimator(finalHeight, 1, v);

        mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                int animProgress = (Integer) animation.getAnimatedValue();
                float anim = animProgress / (hide.getHeight()/3) / 2.0f;
                hide.setAlpha(anim);
            }
        });

        mAnimator.addListener(animator);
        mAnimator.start();
    }


Тут я уже ничего объяснять не буду и так все понятно. :)

Вернемся к нашему главному активити. Определим сразу наши слои меню и затухающий. Переходим к методу onOptionsItemSelected. Удаляем из него все лишнее и добавим обработку нажатия на кнопку Home, с использованием нашей анимации.

 @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        if (item.getItemId() == android.R.id.home) {
            if (!AnimatingUtils.animProgress)
                if (menuLayout.getMeasuredHeight() <= 10) {
                    AnimatingUtils.expand(menuLayout, hideLayer);
                } else {
                    AnimatingUtils.collapse(menuLayout, hideLayer);
                }
        }
        return super.onOptionsItemSelected(item);
    }

Все! Анимация готова, менюшка тоже. Можно запустить и проверить.


О том как размещать компоненты/менюшки на RelativeLayout, я писать не буду, но могу подсказать, что вместо прямого размещения кнопок меню лучше инклудить уже готовый xml, будет проще их менять при надобности.

Полный проект можно скачать на github.

Всех еще раз с Рождеством и да пребудет с вами сила!)






Комментариев нет :

Отправить комментарий