Делаем свой почти Extended Floating Action Button

    Всем привет.

    Не прошло и полгода как работает мое приложение, в котором Floating Action Button меню было реализовано сторонней библиотекой.

    В какой-то момент захотелось сделать его более приятным и удобным.

    Было


    Стало


    Подарком было то что я наткнулся на неэффективный кусок кода в другом месте, который благополучно оптимизировал.

    История создания приложения
    История создания виджета корзины покупок

    Реализация

    1. Разметка подменю кнопки
    fab_layout.xml
    
    <FrameLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:cardView="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    
        <android.support.v7.widget.CardView
            android:id="@+id/fab_spending"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="bottom|end"
            android:visibility="invisible"
            cardView:cardBackgroundColor="@color/red"
            cardView:cardCornerRadius="12dp"
            cardView:cardElevation="8dp">
    
            <TextView
                style="@style/White16"
                android:layout_width="120dp"
                android:layout_height="36dp"
                android:drawableRight="@drawable/remove"
                android:drawablePadding="6dp"
                android:gravity="center"
                android:padding="4dp"
                android:text="@string/spending"/>
        </android.support.v7.widget.CardView>
    
        <android.support.v7.widget.CardView
            android:id="@+id/fab_income"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="bottom|end"
            android:visibility="invisible"
            cardView:cardBackgroundColor="@color/green"
            cardView:cardCornerRadius="12dp"
            cardView:cardElevation="8dp">
    
            <TextView
                style="@style/White16"
                android:layout_width="120dp"
                android:layout_height="36dp"
                android:drawableRight="@drawable/add"
                android:drawablePadding="6dp"
                android:gravity="center"
                android:text="@string/income"/>
        </android.support.v7.widget.CardView>
    
        <android.support.v7.widget.CardView
            android:id="@+id/fab_transfer"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="bottom|end"
            android:visibility="invisible"
            cardView:cardBackgroundColor="@color/gunmetal"
            cardView:cardCornerRadius="12dp"
            cardView:cardElevation="8dp">
    
            <TextView
                style="@style/White16"
                android:layout_width="120dp"
                android:layout_height="36dp"
                android:drawableRight="@drawable/autorenew"
                android:drawablePadding="6dp"
                android:gravity="center"
                android:text="@string/transfer"/>
        </android.support.v7.widget.CardView>
    
    </FrameLayout>
    


    2.анимация

    2.1 появление
    
    нижнего подменю
    
    <?xml version="1.0" encoding="utf-8"?>
    <set xmlns:android="http://schemas.android.com/apk/res/android"
        android:fillAfter="true">
    
        <!--Move-->
        <translate
            android:duration="300"
            android:fromXDelta="125%"
            android:fromYDelta="25%"
            android:interpolator="@android:anim/linear_interpolator"
            android:toXDelta="0%"
            android:toYDelta="0%"></translate>
    
        <!--Fade In-->
        <alpha
            android:duration="300"
            android:fromAlpha="0.0"
            android:interpolator="@android:anim/decelerate_interpolator"
            android:toAlpha="1.0"></alpha>
    
    </set>
    среднего 
    
    <?xml version="1.0" encoding="utf-8"?>
    <set xmlns:android="http://schemas.android.com/apk/res/android"
        android:fillAfter="true">
    
        <!--Move-->
        <translate
            android:duration="300"
            android:fromXDelta="75%"
            android:fromYDelta="210%"
            android:interpolator="@android:anim/linear_interpolator"
            android:toXDelta="0%"
            android:toYDelta="0%"></translate>
    
        <!--Fade In-->
        <alpha
            android:duration="300"
            android:fromAlpha="0.0"
            android:interpolator="@android:anim/decelerate_interpolator"
            android:toAlpha="1.0"></alpha>
    </set>
    
    верхнее 
    
    <?xml version="1.0" encoding="utf-8"?>
    <set xmlns:android="http://schemas.android.com/apk/res/android"
         android:fillAfter="true">
    
        <!--Move-->
        <translate
            android:duration="300"
            android:fromXDelta="25%"
            android:fromYDelta="400%"
            android:interpolator="@android:anim/linear_interpolator"
            android:toXDelta="0%"
            android:toYDelta="0%"></translate>
    
        <!--Fade In-->
        <alpha
            android:duration="300"
            android:fromAlpha="0.0"
            android:interpolator="@android:anim/decelerate_interpolator"
            android:toAlpha="1.0"></alpha>
    
    </set>
    


    2.1 исчезание
    нижнего подменю

    <?xml version="1.0" encoding="utf-8"?>
    <set xmlns:android="http://schemas.android.com/apk/res/android"
        android:fillAfter="true">
    
        <!--Move-->
        <translate
            android:duration="300"
            android:fromXDelta="-125%"
            android:fromYDelta="-25%"
            android:interpolator="@android:anim/linear_interpolator"
            android:toXDelta="0%"
            android:toYDelta="0%"></translate>
    
        <!--Fade Out-->
        <alpha
            android:duration="300"
            android:fromAlpha="1.0"
            android:interpolator="@android:anim/accelerate_interpolator"
            android:toAlpha="0.0"></alpha>
    
    </set>
    
    среднее
    
    <?xml version="1.0" encoding="utf-8"?>
    <set xmlns:android="http://schemas.android.com/apk/res/android"
        android:fillAfter="true">
    
        <!--Move-->
        <translate
            android:duration="300"
            android:fromXDelta="-75%"
            android:fromYDelta="-210%"
            android:interpolator="@android:anim/linear_interpolator"
            android:toXDelta="0%"
            android:toYDelta="0%"></translate>
    
        <!--Fade Out-->
        <alpha
            android:duration="300"
            android:fromAlpha="1.0"
            android:interpolator="@android:anim/accelerate_interpolator"
            android:toAlpha="0.0"></alpha>
    
    </set>
    
    верхнее
    
    <?xml version="1.0" encoding="utf-8"?>
    <set xmlns:android="http://schemas.android.com/apk/res/android"
        android:fillAfter="true">
    
        <!--Move-->
        <translate
            android:duration="300"
            android:fromXDelta="-25%"
            android:fromYDelta="-400%"
            android:interpolator="@android:anim/linear_interpolator"
            android:toXDelta="0%"
            android:toYDelta="0%"></translate>
    
        <!--Fade Out-->
        <alpha
            android:duration="300"
            android:fromAlpha="1.0"
            android:interpolator="@android:anim/accelerate_interpolator"
            android:toAlpha="0.0"></alpha>
    
    </set>
    


    3. Разметка экрана
    <?xml version="1.0" encoding="utf-8"?>
    <android.support.design.widget.CoordinatorLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:card_view="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
    
        <ua.karelov.beans.ui.custom.MyRelativeLayout
            android:id="@+id/l_root"
            android:layout_width="match_parent"
            android:layout_height="wrap_content">
    
    
                <android.support.v7.widget.RecyclerView
                    android:id="@+id/rv_transactions"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_marginTop="4dp"
                    android:clickable="true"
                    android:fadingEdgeLength="20dp"
                    android:focusable="true"
                    android:requiresFadingEdge="vertical"/>
            
        </ua.karelov.beans.ui.custom.MyRelativeLayout>
    
        <android.support.design.widget.FloatingActionButton
            android:id="@+id/fab"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="bottom|end"
            android:layout_marginRight="16dp"
            android:layout_marginBottom="46dp"
            android:rotation="45"
            android:src="@drawable/close"
            app:layout_behavior=".ui.custom.FABScroll"
            />
    
        <include
            layout="@layout/fab_layout"
            android:layout_width="match_parent"
            android:layout_height="500dp"
            android:layout_gravity="bottom|end"
            android:layout_marginBottom="46dp"/>
    
    
    </android.support.design.widget.CoordinatorLayout>
    


    4. FAB scroll behavior
    import android.content.Context;
    import android.support.annotation.NonNull;
    import android.support.design.widget.CoordinatorLayout;
    import android.support.design.widget.FloatingActionButton;
    import android.support.v4.view.ViewCompat;
    import android.util.AttributeSet;
    import android.view.View;
    import android.view.animation.LinearInterpolator;
    
    
    public class FABScroll extends FloatingActionButton.Behavior {
    
    
        public FABScroll(Context context, AttributeSet attrs) {
            super();
        }
    
        @Override
        public void onNestedScroll(CoordinatorLayout coordinatorLayout, FloatingActionButton child, View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
            super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed);
    
            //child -> Floating Action Button
            if (dyConsumed > 0) {
                CoordinatorLayout.LayoutParams layoutParams = (CoordinatorLayout.LayoutParams) child.getLayoutParams();
                int fab_bottomMargin = layoutParams.bottomMargin;
                child.animate().translationY(child.getHeight() + fab_bottomMargin).setInterpolator(new LinearInterpolator()).start();
            } else if (dyConsumed < 0) {
                child.animate().translationY(0).setInterpolator(new LinearInterpolator()).start();
            }
        }
    
        @Override
        public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, FloatingActionButton child, View directTargetChild, View target, int nestedScrollAxes) {
            return nestedScrollAxes == ViewCompat.SCROLL_AXIS_VERTICAL;
        }
    
    }


    5. Наследуемся от RelativeLayout для обработки нажатия по свободному месту, чтобы закрыть меню
    import android.content.Context;
    import android.util.AttributeSet;
    import android.view.MotionEvent;
    import android.widget.RelativeLayout;
    
    public class MyRelativeLayout extends RelativeLayout {
        private boolean layoutClickable = true;
    
        public void setLayoutClickable(boolean layoutClickable) {
            this.layoutClickable = layoutClickable;
        }
    
        public boolean isLayoutClickable() {
            return layoutClickable;
        }
    
        public MyRelativeLayout(Context context) {
            super(context);
        }
    
        public MyRelativeLayout(Context context, AttributeSet attrs) {
            super(context, attrs);
        }
    
        public MyRelativeLayout(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
        }
    
          @Override
        public boolean onInterceptTouchEvent(MotionEvent ev) {
            // true if you do not want the children to be clickable.
            return !layoutClickable;
        }
    }


    6. Соединяем все во фрагменте
    
    
    private static final double transitionSmall = 0.25;
        private static final double transitionMediumX = 0.75;
        private static final double transitionMediumY = 2.1;
        private static final double transitionBigY = 4;
    
        @BindView(R.id.fab)
        FloatingActionButton fab;
        @BindView(R.id.fab_spending)
        CardView fabSpending;
        @BindView(R.id.fab_income)
        CardView fabIncome;
        @BindView(R.id.fab_transfer)
        CardView fabTransfer;
    
        //Animations
        Animation fadeIn;
        Animation fadeOut;
        Animation showFabSpending;
        Animation hideFabSpending;
        Animation showFabIncome;
        Animation hideFabIncome;
        Animation showFabTransfer;
        Animation hideFabTransfer;
    
       @Override
        protected void initView() {
            super.initView();
    
            //Animations
            fadeIn = AnimationUtils.loadAnimation(getActivity(), R.anim.fade_in);
            fadeOut = AnimationUtils.loadAnimation(getActivity(), R.anim.fade_out);
            showFabSpending = AnimationUtils.loadAnimation(getActivity(), R.anim.fab1_show);
            hideFabSpending = AnimationUtils.loadAnimation(getActivity(), R.anim.fab1_hide);
            showFabIncome = AnimationUtils.loadAnimation(getActivity(), R.anim.fab2_show);
            hideFabIncome = AnimationUtils.loadAnimation(getActivity(), R.anim.fab2_hide);
            showFabTransfer = AnimationUtils.loadAnimation(getActivity(), R.anim.fab3_show);
            hideFabTransfer = AnimationUtils.loadAnimation(getActivity(), R.anim.fab3_hide);
    
        }
    
        @OnTouch(R.id.l_root)
        public boolean clickRoot() {
            if(!lRoot.isLayoutClickable()) {
                //Close FAB menu
                hideFAB();
            }
            return true;
        }
    
        private void expandFAB() {
            ViewCompat.animate(fab).
                    rotation(-90).
                    withLayer().
                    setDuration(300).
                    start();
            lRoot.setLayoutClickable(false);
    
            lRoot.startAnimation(fadeOut);
            //Floating Action Button 1
            FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) fabSpending.getLayoutParams();
            layoutParams.rightMargin += (int) (fabSpending.getWidth() * (1 + transitionSmall));
            layoutParams.bottomMargin += (int) (fabSpending.getHeight() * transitionSmall);
            fabSpending.setLayoutParams(layoutParams);
            fabSpending.startAnimation(showFabSpending);
            fabSpending.setClickable(true);
    
            //Floating Action Button 2
            FrameLayout.LayoutParams layoutParams2 = (FrameLayout.LayoutParams) fabIncome.getLayoutParams();
            layoutParams2.rightMargin += (int) (fabIncome.getWidth() * transitionMediumX);
            layoutParams2.bottomMargin += (int) (fabIncome.getHeight() * transitionMediumY);
            fabIncome.setLayoutParams(layoutParams2);
            fabIncome.startAnimation(showFabIncome);
            fabIncome.setClickable(true);
    
            //Floating Action Button 3
            FrameLayout.LayoutParams layoutParams3 = (FrameLayout.LayoutParams) fabTransfer.getLayoutParams();
            layoutParams3.rightMargin += (int) (fabTransfer.getWidth() * transitionSmall);
            layoutParams3.bottomMargin += (int) (fabTransfer.getHeight() * transitionBigY);
            fabTransfer.setLayoutParams(layoutParams3);
            fabTransfer.startAnimation(showFabTransfer);
            fabTransfer.setClickable(true);
        }
    
    
        private void hideFAB() {
            ViewCompat.animate(fab).
                    rotation(45f).
                    withLayer().
                    setDuration(300).
                    start();
            lRoot.setLayoutClickable(true);
           
            lRoot.startAnimation(fadeIn);
            //Floating Action Button 1
            FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) fabSpending.getLayoutParams();
            layoutParams.rightMargin -= (int) (fabSpending.getWidth() * (1 + transitionSmall));
            layoutParams.bottomMargin -= (int) (fabSpending.getHeight() * transitionSmall);
            fabSpending.setLayoutParams(layoutParams);
            fabSpending.startAnimation(hideFabSpending);
            fabSpending.setClickable(false);
    
            //Floating Action Button 2
            FrameLayout.LayoutParams layoutParams2 = (FrameLayout.LayoutParams) fabIncome.getLayoutParams();
            layoutParams2.rightMargin -= (int) (fabIncome.getWidth() * transitionMediumX);
            layoutParams2.bottomMargin -= (int) (fabIncome.getHeight() * transitionMediumY);
            fabIncome.setLayoutParams(layoutParams2);
            fabIncome.startAnimation(hideFabIncome);
            fabIncome.setClickable(false);
    
            //Floating Action Button 3
            FrameLayout.LayoutParams layoutParams3 = (FrameLayout.LayoutParams) fabTransfer.getLayoutParams();
            layoutParams3.rightMargin -= (int) (fabTransfer.getWidth() * transitionSmall);
            layoutParams3.bottomMargin -= (int) (fabTransfer.getHeight() * transitionBigY);
            fabTransfer.setLayoutParams(layoutParams3);
            fabTransfer.startAnimation(hideFabTransfer);
            fabTransfer.setClickable(false);
        }
    
        private void initMenu() {
            fab.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    if(lRoot.isLayoutClickable()) {
                        //Display FAB menu
                        expandFAB();
                    } else {
                        //Close FAB menu
                        hideFAB();
                    }
                }
            });
    
        }
    
    // выполняем когда экран обновился (если записей будет мало или не будет то нужно вернуть кнопку)
    fab.animate().translationY(0).setInterpolator(new LinearInterpolator()).start();
    

    Поделиться публикацией

    Комментарии 0

    Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

    Самое читаемое