× {{alert.msg}} Never ask again
Get notified about new tutorials RECEIVE NEW TUTORIALS

Android - Rotation menu with one part out of the screen

Xaver Kapeller
Mar 13, 2015
<p>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: </p> <p><img src="http://i.stack.imgur.com/NVuVN.gif" alt="enter image description here"></p> <p><strong>EDIT:</strong> I added the ability to show and hide the upper rotating menu with two methods, <code>showRotationMenu()</code> and <code>hideRotationMenu()</code>:</p> <p><img src="http://i.stack.imgur.com/lXs55.gif" alt="enter image description here"></p> <p>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.</p> <h2>1) Source</h2> <p>RotationMenu.java:</p> <pre><code>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 &amp;&amp; context != null) { int viewCount = this.adapter.getCount(); int oldViewCount = this.llMenu.getChildCount(); for (int i = 0; i &lt; Math.max(oldViewCount, viewCount); i++) { if (i &lt; viewCount) { LayoutParams layoutParams = new LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1); View menuItem; if (i &lt; 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 &gt;= 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 &lt; this.selectedPosition); removeViewWithAnimation(this.previousView, animate, position &lt; this.selectedPosition); this.selectedPosition = position; } } } private int valueInRange(int value, int min, int max) { if (value &gt; max) { value = max; } else if (value &lt; 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 &amp;&amp; adapter.isMenuItemEnabled(this.position) &amp;&amp; isRotationMenuVisible()) { switchToItem(this.position, true); } } } } </code></pre> <p>RotationMenuAdapter.java:</p> <pre><code>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); } </code></pre> <h2>2) Usage:</h2> <p>The custom view is making use of adapters and view recycling, so you use it the same way you would use a <code>ListView</code>. 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 <code>getMenuItemView()</code> with the same care you would use to implement <code>getView()</code>. First you have to write an adapter:</p> <pre><code>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&lt;TestViewModel&gt; viewModels; public TestAdapter(Context context, List&lt;TestViewModel&gt; 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; } } </code></pre> <p>Nothing special, except the extra methods for the creating of the menu items. In <code>isMenuItemEnabled()</code> you can add logic to enable/disable menu items if you need to.</p> <p>And then you apply your adapter to the view:</p> <pre><code>List&lt;TestViewModel&gt; viewModels = new ArrayList&lt;TestViewModel&gt;(); 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); </code></pre> <p><strong>EDIT:</strong></p> <p>Try this layout with <code>wrap_content</code> instead <code>match_parent</code>:</p> <pre><code>&lt;?xml version="1.0" encoding="utf-8"?&gt; &lt;RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="wrap_content" android:layout_height="wrap_content" &gt; &lt;ImageView android:id="@+id/menuItemImg" android:layout_width="wrap_content" android:layout_height="wrap_content" android:scaleType="fitXY" android:src="@drawable/tab_1_1" /&gt; &lt;/RelativeLayout&gt; </code></pre> <p>I'm also no so sure about your <code>layout_width</code>, are you sure you need <code>match_parent</code> there? I don't think so. </p> <p>This tip was originally posted on <a href="http://stackoverflow.com/questions/22544972/Android%20-%20Rotation%20menu%20with%20one%20part%20out%20of%20the%20screen/22667608">Stack Overflow</a>.</p>

Get New Tutorials Delivered to Your Inbox

New tutorials will be sent to your Inbox once a week.

comments powered by Disqus