com.arlib.floatingsearchview.util.view.MenuView.java Source code

Java tutorial

Introduction

Here is the source code for com.arlib.floatingsearchview.util.view.MenuView.java

Source

/*
 * Copyright (C) 2015 Arlib
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.arlib.floatingsearchview.util.view;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.support.v4.graphics.drawable.DrawableCompat;
import android.support.v4.view.ViewCompat;
import android.support.v7.view.SupportMenuInflater;
import android.support.v7.view.menu.MenuBuilder;
import android.support.v7.view.menu.MenuItemImpl;
import android.util.AttributeSet;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;
import android.view.animation.LinearInterpolator;
import android.widget.ImageView;
import android.widget.LinearLayout;

import com.arlib.floatingsearchview.R;
import com.arlib.floatingsearchview.util.MenuPopupHelper;
import com.arlib.floatingsearchview.util.Util;
import com.bartoszlipinski.viewpropertyobjectanimator.ViewPropertyObjectAnimator;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

/**
 * A view that shows menu items as actions or
 * as items in a overflow popup.
 */
public class MenuView extends LinearLayout {

    private final int HIDE_IF_ROOM_ITEMS_ANIM_DURATION = 400;
    private final int SHOW_IF_ROOM_ITEMS_ANIM_DURATION = 450;

    private final float ACTION_DIMENSION_PX;

    private int mMenu = -1;
    private MenuBuilder mMenuBuilder;
    private SupportMenuInflater mMenuInflater;
    private MenuPopupHelper mMenuPopupHelper;

    private MenuBuilder.Callback mMenuCallback;

    private int mActionIconColor;
    private int mOverflowIconColor;

    //all menu items
    private List<MenuItemImpl> mMenuItems;

    //items that are currently presented as actions
    private List<MenuItemImpl> mActionItems = new ArrayList<>();

    private List<MenuItemImpl> mActionShowAlwaysItems = new ArrayList<>();

    private boolean mHasOverflow = false;

    private OnVisibleWidthChanged mOnVisibleWidthChanged;

    private List<ObjectAnimator> anims = new ArrayList<>();

    public interface OnVisibleWidthChanged {
        void onVisibleWidthChanged(int newVisibleWidth);
    }

    public MenuView(Context context) {
        this(context, null);
    }

    public MenuView(Context context, AttributeSet attrs) {
        super(context, attrs);
        ACTION_DIMENSION_PX = context.getResources().getDimension(R.dimen.square_button_size);
        init();
    }

    private void init() {
        mMenuBuilder = new MenuBuilder(getContext());
        mMenuPopupHelper = new MenuPopupHelper(getContext(), mMenuBuilder, this);

        mActionIconColor = getResources().getColor(R.color.gray_active_icon);
        mOverflowIconColor = getResources().getColor(R.color.gray_active_icon);
    }

    public void setActionIconColor(int actionColor) {
        this.mActionIconColor = actionColor;
        refreshColors();
    }

    public void setOverflowColor(int overflowColor) {
        this.mOverflowIconColor = overflowColor;
        refreshColors();
    }

    private void refreshColors() {

        for (int i = 0; i < getChildCount(); i++) {

            Util.setIconColor(((ImageView) getChildAt(i)).getDrawable(), mActionIconColor);

            if (mHasOverflow && i == getChildCount() - 1)
                Util.setIconColor(((ImageView) getChildAt(i)).getDrawable(), mOverflowIconColor);

        }
    }

    /**
     * Sets the resource reference to the
     * menu defined in xml that will be used
     * in subsequent calls to {@link #reset(int availWidth) reset}
     *
     * @param menu a reference to a menu defined in
     *             resources.
     */
    public void resetMenuResource(int menu) {

        this.mMenu = menu;
    }

    /**
     * Set the callback that will be called when menu
     * items a selected.
     *
     * @param menuCallback
     */
    public void setMenuCallback(MenuBuilder.Callback menuCallback) {
        this.mMenuCallback = menuCallback;
    }

    /**
     * Resets the the view to fit into a new
     * available width.
     *
     * <p>This clears and then re-inflates the menu items
     * , removes all of its associated action views, and recreates
     * the menu and action items to fit in the new width.</p>
     *
     * @param availWidth the width available for the menu to use. If
     *                   there is room, menu items that are flagged with
     *                   android:showAsAction="ifRoom" or android:showAsAction="always"
     *                   will show as actions.
     */
    public void reset(int availWidth) {

        if (mMenu == -1)
            return;

        //clean view first
        removeAllViews();
        mActionItems.clear();

        //reset menu
        mMenuBuilder.clearAll();
        getMenuInflater().inflate(mMenu, mMenuBuilder);

        int holdAllItemsCount;

        mMenuItems = mMenuBuilder.getActionItems();
        mMenuItems.addAll(mMenuBuilder.getNonActionItems());

        holdAllItemsCount = mMenuItems.size();

        Collections.sort(mMenuItems, new Comparator<MenuItemImpl>() {
            @Override
            public int compare(MenuItemImpl lhs, MenuItemImpl rhs) {
                return ((Integer) lhs.getOrder()).compareTo(rhs.getOrder());
            }
        });

        List<MenuItemImpl> menuItems = filter(mMenuItems, new MenuItemImplPredicate() {
            @Override
            public boolean apply(MenuItemImpl menuItem) {
                return menuItem.requiresActionButton() || menuItem.requestsActionButton();
            }
        });

        int availItemRoom = availWidth / (int) ACTION_DIMENSION_PX;
        boolean addOverflowAtTheEnd = false;
        if (((menuItems.size() < holdAllItemsCount) || availItemRoom < menuItems.size())) {
            addOverflowAtTheEnd = true;
            availItemRoom--;
        }

        ArrayList<Integer> actionMenuItems = new ArrayList<>();

        if (availItemRoom > 0)
            for (int i = 0; i < menuItems.size(); i++) {

                final MenuItemImpl menuItem = menuItems.get(i);

                if (menuItem.getIcon() != null) {

                    ImageView action = getActionHolder();
                    action.setImageDrawable(Util.setIconColor(menuItem.getIcon(), mActionIconColor));
                    addView(action);
                    mActionItems.add(menuItem);

                    action.setOnClickListener(new OnClickListener() {
                        @Override
                        public void onClick(View v) {

                            if (mMenuCallback != null)
                                mMenuCallback.onMenuItemSelected(mMenuBuilder, menuItem);
                        }
                    });

                    actionMenuItems.add(menuItem.getItemId());

                    availItemRoom--;
                    if (availItemRoom == 0)
                        break;
                }
            }

        if (addOverflowAtTheEnd) {

            ImageView overflowAction = getOverflowActionHolder();
            overflowAction.setImageDrawable(Util.setIconColor(
                    getResources().getDrawable(R.drawable.ic_more_vert_black_24dp), mOverflowIconColor));
            addView(overflowAction);

            overflowAction.setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View v) {

                    mMenuPopupHelper.show();
                }
            });

            mMenuBuilder.setCallback(mMenuCallback);

            mHasOverflow = true;
        }

        for (int id : actionMenuItems)
            mMenuBuilder.removeItem(id);

        actionMenuItems.clear();

        if (mOnVisibleWidthChanged != null)
            mOnVisibleWidthChanged.onVisibleWidthChanged(
                    ((int) ACTION_DIMENSION_PX * getChildCount()) - (mHasOverflow ? Util.dpToPx(8) : 0));
    }

    private ImageView getActionHolder() {
        return (ImageView) LayoutInflater.from(getContext()).inflate(R.layout.action_item_layout, this, false);
    }

    private ImageView getOverflowActionHolder() {
        return (ImageView) LayoutInflater.from(getContext()).inflate(R.layout.overflow_action_item_layout, this,
                false);
    }

    /**
     * Hides all the menu items flagged with "ifRoom"
     *
     * @param withAnim
     */
    public void hideIfRoomItems(boolean withAnim) {

        if (mMenu == -1)
            return;

        mActionShowAlwaysItems.clear();
        cancelChildAnimListAndClear();

        List<MenuItemImpl> showAlwaysActionItems = filter(mMenuItems, new MenuItemImplPredicate() {
            @Override
            public boolean apply(MenuItemImpl menuItem) {
                return menuItem.requiresActionButton();
            }
        });

        int actionItemIndex;
        for (actionItemIndex = 0; actionItemIndex < mActionItems.size()
                && actionItemIndex < showAlwaysActionItems.size(); actionItemIndex++) {

            final MenuItemImpl actionItem = showAlwaysActionItems.get(actionItemIndex);

            if (mActionItems.get(actionItemIndex).getItemId() != showAlwaysActionItems.get(actionItemIndex)
                    .getItemId()) {

                ImageView action = (ImageView) getChildAt(actionItemIndex);
                action.setImageDrawable(Util.setIconColor(actionItem.getIcon(), mActionIconColor));

                action.setOnClickListener(new OnClickListener() {
                    @Override
                    public void onClick(View v) {

                        if (mMenuCallback != null)
                            mMenuCallback.onMenuItemSelected(mMenuBuilder, actionItem);
                    }
                });

            }

            mActionShowAlwaysItems.add(actionItem);
        }

        final int diff = mActionItems.size() - actionItemIndex + (mHasOverflow ? 1 : 0);

        anims = new ArrayList<>();

        for (int i = 0; i < actionItemIndex; i++) {
            final View currentChild = getChildAt(i);
            final float destTransX = ACTION_DIMENSION_PX * diff - (mHasOverflow ? Util.dpToPx(8) : 0);
            anims.add(ViewPropertyObjectAnimator.animate(currentChild)
                    .setDuration(withAnim ? HIDE_IF_ROOM_ITEMS_ANIM_DURATION : 0)
                    .setInterpolator(new AccelerateInterpolator()).addListener(new AnimatorListenerAdapter() {
                        @Override
                        public void onAnimationEnd(Animator animation) {

                            currentChild.setTranslationX(destTransX);
                        }
                    }).translationXBy(destTransX).get());
        }

        for (int i = actionItemIndex; i < diff + actionItemIndex; i++) {

            final View currentView = getChildAt(i);

            currentView.setClickable(false);

            if (i != getChildCount() - 1)
                anims.add(ViewPropertyObjectAnimator.animate(currentView)
                        .setDuration(withAnim ? HIDE_IF_ROOM_ITEMS_ANIM_DURATION : 0)
                        .addListener(new AnimatorListenerAdapter() {
                            @Override
                            public void onAnimationEnd(Animator animation) {

                                currentView.setTranslationX(ACTION_DIMENSION_PX);
                            }
                        }).translationXBy(ACTION_DIMENSION_PX).get());

            anims.add(ViewPropertyObjectAnimator.animate(currentView)
                    .setDuration(withAnim ? HIDE_IF_ROOM_ITEMS_ANIM_DURATION : 0)
                    .addListener(new AnimatorListenerAdapter() {
                        @Override
                        public void onAnimationEnd(Animator animation) {

                            currentView.setScaleX(0.5f);
                        }
                    }).scaleX(.5f).get());
            anims.add(ViewPropertyObjectAnimator.animate(currentView)
                    .setDuration(withAnim ? HIDE_IF_ROOM_ITEMS_ANIM_DURATION : 0)
                    .addListener(new AnimatorListenerAdapter() {
                        @Override
                        public void onAnimationEnd(Animator animation) {

                            currentView.setScaleY(0.5f);
                        }
                    }).scaleY(.5f).get());
            anims.add(ViewPropertyObjectAnimator.animate(getChildAt(i))
                    .setDuration(withAnim ? HIDE_IF_ROOM_ITEMS_ANIM_DURATION : 0)
                    .addListener(new AnimatorListenerAdapter() {
                        @Override
                        public void onAnimationEnd(Animator animation) {

                            currentView.setAlpha(0.0f);
                        }
                    }).alpha(0.0f).get());
        }

        final int actinItemsCount = actionItemIndex;
        if (!anims.isEmpty()) {

            AnimatorSet animSet = new AnimatorSet();
            if (!withAnim)
                animSet.setDuration(0);
            animSet.playTogether(anims.toArray(new ObjectAnimator[anims.size()]));
            animSet.addListener(new AnimatorListenerAdapter() {
                @Override
                public void onAnimationEnd(Animator animation) {

                    if (mOnVisibleWidthChanged != null)
                        mOnVisibleWidthChanged.onVisibleWidthChanged(((int) ACTION_DIMENSION_PX * actinItemsCount));
                }
            });
            animSet.start();
        }

    }

    /**
     * Shows all the menu items that were hidden by hideIfRoomItems(boolean withAnim)
     *
     * @param withAnim
     */
    public void showIfRoomItems(boolean withAnim) {

        if (mMenu == -1)
            return;

        cancelChildAnimListAndClear();

        if (mMenuItems.isEmpty())
            return;

        anims = new ArrayList<>();

        for (int i = 0; i < getChildCount(); i++) {

            final View currentView = getChildAt(i);

            if (i < mActionItems.size()) {
                ImageView action = (ImageView) currentView;
                final MenuItem actionItem = mActionItems.get(i);
                action.setImageDrawable(Util.setIconColor(actionItem.getIcon(), mActionIconColor));

                action.setOnClickListener(new OnClickListener() {
                    @Override
                    public void onClick(View v) {

                        if (mMenuCallback != null)
                            mMenuCallback.onMenuItemSelected(mMenuBuilder, actionItem);
                    }
                });
            }

            //todo go over logic
            int animDuration = withAnim ? SHOW_IF_ROOM_ITEMS_ANIM_DURATION : 0;

            Interpolator interpolator = new DecelerateInterpolator();

            //todo check logic
            if (i > mActionShowAlwaysItems.size() - 1)
                interpolator = new LinearInterpolator();

            currentView.setClickable(true);
            anims.add(ViewPropertyObjectAnimator.animate(currentView).addListener(new AnimatorListenerAdapter() {
                @Override
                public void onAnimationEnd(Animator animation) {

                    currentView.setTranslationX(0);
                }
            }).setInterpolator(interpolator).setDuration(animDuration).translationX(0).get());
            anims.add(ViewPropertyObjectAnimator.animate(currentView).addListener(new AnimatorListenerAdapter() {
                @Override
                public void onAnimationEnd(Animator animation) {

                    currentView.setScaleX(1.0f);
                }
            }).setInterpolator(interpolator).setDuration(animDuration).scaleX(1.0f).get());
            anims.add(ViewPropertyObjectAnimator.animate(currentView).addListener(new AnimatorListenerAdapter() {
                @Override
                public void onAnimationEnd(Animator animation) {

                    currentView.setScaleY(1.0f);
                }
            }).setInterpolator(interpolator).setDuration(animDuration).scaleY(1.0f).get());
            anims.add(ViewPropertyObjectAnimator.animate(currentView).addListener(new AnimatorListenerAdapter() {
                @Override
                public void onAnimationEnd(Animator animation) {

                    currentView.setAlpha(1.0f);
                }
            }).setInterpolator(interpolator).setDuration(animDuration).alpha(1.0f).get());
        }

        AnimatorSet animSet = new AnimatorSet();

        //temporary, from laziness
        if (!withAnim)
            animSet.setDuration(0);
        animSet.playTogether(anims.toArray(new ObjectAnimator[anims.size()]));
        animSet.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {

                if (mOnVisibleWidthChanged != null)
                    mOnVisibleWidthChanged.onVisibleWidthChanged(
                            (getChildCount() * (int) ACTION_DIMENSION_PX) - (mHasOverflow ? Util.dpToPx(8) : 0));
            }
        });
        animSet.start();

    }

    private interface MenuItemImplPredicate {

        boolean apply(MenuItemImpl menuItem);
    }

    private List<MenuItemImpl> filter(List<MenuItemImpl> target, MenuItemImplPredicate predicate) {
        List<MenuItemImpl> result = new ArrayList<>();
        for (MenuItemImpl element : target) {
            if (predicate.apply(element)) {
                result.add(element);
            }
        }
        return result;
    }

    private MenuInflater getMenuInflater() {
        if (mMenuInflater == null) {
            mMenuInflater = new SupportMenuInflater(getContext());
        }
        return mMenuInflater;
    }

    public void setOnVisibleWidthChanged(OnVisibleWidthChanged listener) {
        this.mOnVisibleWidthChanged = listener;
    }

    private void cancelChildAnimListAndClear() {

        for (ObjectAnimator animator : anims)
            animator.cancel();
        anims.clear();
    }

    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();

        //clear anims if any to avoid leak
        cancelChildAnimListAndClear();
    }
}