Pepa Zapletal
Pepa Zapletal

Reputation: 2979

Android - Rotation menu with one part out of the screen

I want develope this rotation in my App . I have implemented the "down" menu ("můj dealer", "moje Volvo", "kontakty") and I need implement the "upper" rotating menu.

How can I do it? Do you have tips? Hope you understand me.

Images (please, watch the video above in the link):

enter image description here enter image description here

Menu_item_test.xml:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
   android:layout_width="match_parent"
   android:layout_height="match_parent" >

<ImageView
    android:id="@+id/menuItemImg"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:scaleType="fitXY"
    android:src="@drawable/tab_1_1" />

</RelativeLayout>

Activity_main.xml:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity" >

<FrameLayout
    android:id="@+id/fragment_container"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_above="@+id/rotationMenu" >
</FrameLayout>

<ImageView
    android:id="@+id/imageViewShadow"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_alignParentBottom="true"
    android:layout_marginBottom="60dp"
    android:contentDescription="shadow"
    android:scaleType="fitXY"
    android:src="@drawable/half_round_back" />

<cz.mixedapps.volvista.RotationMenu
    android:id="@+id/rotationMenu"
    android:layout_width="match_parent"
    android:layout_height="265dp"
    android:layout_alignParentBottom="true" >
</cz.mixedapps.volvista.RotationMenu>

</RelativeLayout>

Upvotes: 2

Views: 1391

Answers (1)

Xaver Kapeller
Xaver Kapeller

Reputation: 49817

I have put together a custom view which replicates the behaviour, you still have to style it like in you example, but it works with an arbitrary number of child views and currently looks like this:

enter image description here

EDIT: I added the ability to show and hide the upper rotating menu with two methods, showRotationMenu() and hideRotationMenu():

enter image description here

Of course there is a lot you can do to improve this view. I just wrote this in 15 minutes and therefor it is a little rough around the edges. But it should be more than enough to put you on the right track. Both the looks of the buttons and of the views which rotate is completely customisable.

1) Source

RotationMenu.java:

import android.content.Context;
import android.content.res.Resources;
import android.database.DataSetObserver;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.*;
import android.widget.FrameLayout;
import android.widget.LinearLayout;

/**
 * Created by Xaver Kapeller on 26/03/14.
 */
public class RotationMenu extends LinearLayout {

    private final DataSetObserver dataSetObserver = new DataSetObserver() {
        @Override
        public void onChanged() {
            super.onChanged();
            reloadAdapter();
        }
    };

    private RotationMenuAdapter adapter;
    private FrameLayout flViewContainer;
    private LinearLayout llMenu;
    private View currentView;
    private View previousView;

    private int animationPivotX;
    private int animationPivotY;

    private int selectedPosition = 0;

    public RotationMenu(Context context) {
        super(context);

        setupMenu();
    }

    public RotationMenu(Context context, AttributeSet attrs) {
        super(context, attrs);

        setupMenu();
    }

    public RotationMenu(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);

        setupMenu();
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);

        this.animationPivotX = w / 2;
        this.animationPivotY = h;
    }

    private void setupMenu() {
        this.setOrientation(VERTICAL);
        this.flViewContainer = new FrameLayout(getContext());
        this.addView(this.flViewContainer, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, dpToPixel(150)));

        this.llMenu = new LinearLayout(getContext());
        this.llMenu.setOrientation(LinearLayout.HORIZONTAL);
        this.addView(this.llMenu, new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
    }

    private int dpToPixel(int dp) {
        float scale = getDisplayDensityFactor();
        return (int) (dp * scale + 0.5f);
    }

    private float getDisplayDensityFactor() {
        Resources res = getResources();
        if (res != null) {
            DisplayMetrics metrics = res.getDisplayMetrics();
            if(metrics != null) {
                return metrics.density;
            }
        }
        return 1.0f;
    }

    public RotationMenuAdapter getAdapter() {
        return this.adapter;
    }

    public void setAdapter(RotationMenuAdapter adapter) {
        if (adapter != null) {
            if (this.adapter != null) {
                this.adapter.unregisterDataSetObserver(this.dataSetObserver);
            }
            adapter.registerDataSetObserver(this.dataSetObserver);
            this.adapter = adapter;
            reloadAdapter();
        }
    }

    public boolean isRotationMenuVisible() {
        return this.flViewContainer.getVisibility() == View.VISIBLE;
    }

    public void showRotationMenu() {
            if(this.flViewContainer.getVisibility() != View.VISIBLE) {
                this.flViewContainer.setVisibility(View.VISIBLE);
                AnimationSet set = new AnimationSet(false);

                TranslateAnimation translateAnimation = new TranslateAnimation(Animation.RELATIVE_TO_SELF, 0, Animation.RELATIVE_TO_SELF, 0, Animation.RELATIVE_TO_SELF, 1, Animation.RELATIVE_TO_SELF, 0);
                translateAnimation.setDuration(1000);
                set.addAnimation(translateAnimation);

                AlphaAnimation alphaAnimation = new AlphaAnimation(0, 1);
                alphaAnimation.setDuration(1000);
                set.addAnimation(alphaAnimation);

                this.flViewContainer.startAnimation(set);
            }
    }

    public void hideRotationMenu() {
        if(this.flViewContainer.getVisibility() == View.VISIBLE) {
            AnimationSet set = new AnimationSet(false);

            TranslateAnimation translateAnimation = new TranslateAnimation(Animation.RELATIVE_TO_SELF, 0, Animation.RELATIVE_TO_SELF, 0, Animation.RELATIVE_TO_SELF, 0, Animation.RELATIVE_TO_SELF, 1);
            translateAnimation.setDuration(1000);
            set.addAnimation(translateAnimation);

            AlphaAnimation alphaAnimation = new AlphaAnimation(1, 0);
            alphaAnimation.setDuration(1000);
            set.addAnimation(alphaAnimation);

            set.setAnimationListener(new ViewAnimationEndListener(this.flViewContainer) {
                @Override
                protected void onAnimationEnd(Animation animation, View view) {
                    view.setVisibility(View.GONE);
                }
            });

            this.flViewContainer.startAnimation(set);
        }
    }

    private void reloadAdapter() {
        Context context = getContext();
        if (this.adapter != null && context != null) {
            int viewCount = this.adapter.getCount();
            int oldViewCount = this.llMenu.getChildCount();
            for (int i = 0; i < Math.max(oldViewCount, viewCount); i++) {
                if (i < viewCount) {
                    LayoutParams layoutParams = new LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1);
                    View menuItem;
                    if (i < this.llMenu.getChildCount()) {
                        menuItem = this.adapter.getMenuItemView(i, this.llMenu.getChildAt(i), this.llMenu);
                        if(menuItem.getParent() == null) {
                            this.llMenu.removeViewAt(i);
                            this.llMenu.addView(menuItem, i, layoutParams);
                        }
                    } else {
                        menuItem = this.adapter.getMenuItemView(i, null, this.llMenu);
                        this.llMenu.addView(menuItem, layoutParams);
                    }
                    menuItem.setOnClickListener(new MenuItemClickListener(i));
                } else {
                    this.llMenu.removeViewAt(i);
                }
            }

            this.flViewContainer.removeAllViews();
            this.previousView = this.currentView;
            if (this.selectedPosition >= viewCount) {
                this.selectedPosition = viewCount - 1;
            }
            this.currentView = this.adapter.getView(this.selectedPosition, this.previousView, this);
            addViewWithAnimation(this.currentView, false);
        }
    }

    public void switchToItem(int position, boolean animate) {
        if (this.adapter != null) {
            int viewCount = this.adapter.getCount();
            position = valueInRange(position, 0, viewCount - 1);

            if (position != this.selectedPosition) {
                View oldView = this.currentView;
                this.currentView = this.adapter.getView(position, this.previousView, this);
                this.previousView = oldView;

                addViewWithAnimation(this.currentView, animate, position < this.selectedPosition);
                removeViewWithAnimation(this.previousView, animate, position < this.selectedPosition);

                this.selectedPosition = position;
            }
        }
    }

    private int valueInRange(int value, int min, int max) {
        if (value > max) {
            value = max;
        } else if (value < min) {
            value = min;
        }
        return value;
    }

    private void addViewWithAnimation(View view, boolean animate, boolean leftToRight) {
        if (view != null) {
            if(view.getParent() == null) {
                this.flViewContainer.addView(view, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
            }
            if (animate) {

                int start = leftToRight ? -90 : 90;

                Animation rotateIn = new RotateAnimation(start, 0, this.animationPivotX, this.animationPivotY);
                rotateIn.setDuration(1000);

                view.startAnimation(rotateIn);
            }
        }
    }

    private void addViewWithAnimation(View view, boolean animate) {
        addViewWithAnimation(view, animate, true);
    }

    private void removeViewWithAnimation(View view, boolean animate, boolean leftToRight) {
        if (view != null) {
            if (animate) {

                int target = leftToRight ? 90 : -90;

                Animation rotateOut = new RotateAnimation(0, target, this.animationPivotX, this.animationPivotY);
                rotateOut.setDuration(1000);
                rotateOut.setAnimationListener(new ViewAnimationEndListener(view) {
                    @Override
                    protected void onAnimationEnd(Animation animation, View view) {
                        flViewContainer.removeView(view);
                    }
                });
                view.startAnimation(rotateOut);
            } else {
                this.flViewContainer.removeView(view);
            }
        }
    }

    private void removeViewWithAnimation(View view, boolean animate) {
        removeViewWithAnimation(view, animate, true);
    }

    private abstract class ViewAnimationEndListener implements Animation.AnimationListener {

        private final View view;

        private ViewAnimationEndListener(View view) {
            this.view = view;
        }

        @Override
        public void onAnimationStart(Animation animation) {

        }

        @Override
        public void onAnimationEnd(Animation animation) {
            onAnimationEnd(animation, this.view);
        }

        @Override
        public void onAnimationRepeat(Animation animation) {

        }

        protected abstract void onAnimationEnd(Animation animation, View view);
    }

    private class MenuItemClickListener implements OnClickListener {

        private final int position;

        MenuItemClickListener(int position) {
            this.position = position;
        }

        @Override
        public void onClick(View v) {
            if(adapter != null && adapter.isMenuItemEnabled(this.position) && isRotationMenuVisible()) {
                switchToItem(this.position, true);
            }
        }
    }
}

RotationMenuAdapter.java:

import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;

/**
 * Created by Xaver Kapeller on 26/03/14.
 */
public abstract class RotationMenuAdapter extends BaseAdapter {

    public abstract View getMenuItemView(int position, View convertView, ViewGroup parent);
    public abstract long getMenuItemId(int position);
    public abstract boolean isMenuItemEnabled(int position);
}

2) Usage:

The custom view is making use of adapters and view recycling, so you use it the same way you would use a ListView. Because of the view recycling it is not very memory intensive and actually pretty fast. The logic for the creating of the menu items at the bottom is also in the adapter and it too uses view recycling. So be sure that you implement getMenuItemView() with the same care you would use to implement getView(). First you have to write an adapter:

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import at.test.app.R;
import at.test.app.RotationMenu.RotationMenuAdapter;

import java.util.List;

/**
 * Created by Xaver Kapeller on 26/03/14.
 */
public class TestAdapter extends RotationMenuAdapter {

    private static final long TEST_VIEW_ID = 0;
    private static final long DEFAULT_VIEW_ID = TEST_VIEW_ID;

    private static final long TEST_MENU_ID = 0;
    private static final long DEFAULT_MENU_ID = TEST_MENU_ID;

    private final LayoutInflater inflater;
    private final List<TestViewModel> viewModels;

    public TestAdapter(Context context, List<TestViewModel> viewModels) {
        this.inflater = LayoutInflater.from(context);
        this.viewModels = viewModels;
    }

    @Override
    public int getCount() {
        return this.viewModels.size();
    }

    @Override
    public Object getItem(int position) {
        return this.viewModels.get(position);
    }

    @Override
    public long getItemId(int position) {

        Object viewModel = getItem(position);

        if(viewModel instanceof TestViewModel) {
            return TEST_VIEW_ID;
        }

        return DEFAULT_VIEW_ID;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {

        if(getItemId(position) == TEST_VIEW_ID) {
            TestViewModel viewModel = (TestViewModel) getItem(position);

            TestRow row;
            if(convertView == null) {
                convertView = this.inflater.inflate(TestRow.LAYOUT, parent, false);
                row = new TestRow(convertView);
                convertView.setTag(row);
            }

            row = (TestRow) convertView.getTag();
            row.bind(viewModel);
        }

        return convertView;
    }

    @Override
    public View getMenuItemView(int position, View convertView, ViewGroup parent) {

        if(getMenuItemId(position) == TEST_MENU_ID) {
            TestViewModel viewModel = (TestViewModel)getItem(position);

            MenuRow row;
            if(convertView == null) {
                convertView = this.inflater.inflate(MenuRow.LAYOUT, parent, false);
                row = new MenuRow(convertView);
                convertView.setTag(row);
            }

            row = (MenuRow)convertView.getTag();
            row.bind(viewModel);
        }

        return convertView;
    }

    @Override
    public long getMenuItemId(int position) {
        Object item = getItem(position);

        if(item instanceof TestViewModel) {
            return TEST_MENU_ID;
        }

        return DEFAULT_MENU_ID;
    }

    @Override
    public boolean isMenuItemEnabled(int position) {
        return true;
    }
}

Nothing special, except the extra methods for the creating of the menu items. In isMenuItemEnabled() you can add logic to enable/disable menu items if you need to.

And then you apply your adapter to the view:

List<TestViewModel> viewModels = new ArrayList<TestViewModel>();

TestViewModel menuItemOne = new TestViewModel();
menuItemOne.setMenuItemIconResId(R.drawable.icon);
viewModels.add(menuItemOne);

TestViewModel menuItemTwo = new TestViewModel();
menuItemTwo.setMenuItemIconResId(R.drawable.icon);
viewModels.add(menuItemTwo);

TestAdapter adapter = new TestAdapter(getActivity(), viewModels);
this.rotationMenu.setAdapter(adapter);

EDIT:

Try this layout with wrap_content instead match_parent:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content" >

    <ImageView
            android:id="@+id/menuItemImg"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:scaleType="fitXY"
            android:src="@drawable/tab_1_1" />

</RelativeLayout>

I'm also no so sure about your layout_width, are you sure you need match_parent there? I don't think so.

Upvotes: 4

Related Questions