Back to project page LibSlideMenu.
The source code is released under:
Apache License
If you think the Android project LibSlideMenu listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.
/* * A sliding menu for Android, very much like the Google+ and Facebook apps have. *// w w w .ja va 2s . c o m * Copyright (C) 2012 CoboltForge * * Based upon the great work done by stackoverflow user Scirocco (http://stackoverflow.com/a/11367825/361413), thanks a lot! * The XML parsing code comes from https://github.com/darvds/RibbonMenu, thanks! * * 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.coboltforge.slidemenu; import java.lang.reflect.Method; import java.util.ArrayList; import org.xmlpull.v1.XmlPullParser; import android.annotation.SuppressLint; import android.app.Activity; import android.content.Context; import android.content.res.XmlResourceParser; import android.graphics.Rect; import android.graphics.Typeface; import android.graphics.drawable.Drawable; import android.os.Build; import android.os.Bundle; import android.os.Parcelable; import android.util.AttributeSet; import android.util.TypedValue; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.Window; import android.view.animation.Interpolator; import android.view.animation.TranslateAnimation; import android.widget.AdapterView; import android.widget.AdapterView.OnItemClickListener; import android.widget.ArrayAdapter; import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.ListView; import android.widget.TextView; public class SlideMenu extends LinearLayout { // keys for saving/restoring instance state private final static String KEY_MENUSHOWN = "menuWasShown"; private final static String KEY_STATUSBARHEIGHT = "statusBarHeight"; private final static String KEY_SUPERSTATE = "superState"; public static class SlideMenuItem { public int id; public Drawable icon; public String label; } // a simple adapter private static class SlideMenuAdapter extends ArrayAdapter<SlideMenuItem> { Activity act; SlideMenuItem[] items; Typeface itemFont; class MenuItemHolder { public TextView label; public ImageView icon; } public SlideMenuAdapter(Activity act, SlideMenuItem[] items, Typeface itemFont) { super(act, R.id.menu_label, items); this.act = act; this.items = items; this.itemFont = itemFont; } @Override public View getView(int position, View convertView, ViewGroup parent) { View rowView = convertView; if (rowView == null) { LayoutInflater inflater = act.getLayoutInflater(); rowView = inflater.inflate(R.layout.slidemenu_listitem, null); MenuItemHolder viewHolder = new MenuItemHolder(); viewHolder.label = (TextView) rowView.findViewById(R.id.menu_label); if(itemFont != null) viewHolder.label.setTypeface(itemFont); viewHolder.icon = (ImageView) rowView.findViewById(R.id.menu_icon); rowView.setTag(viewHolder); } MenuItemHolder holder = (MenuItemHolder) rowView.getTag(); String s = items[position].label; holder.label.setText(s); holder.icon.setImageDrawable(items[position].icon); return rowView; } } // this tells whether the menu is currently shown private boolean menuIsShown = false; // this just tells whether the menu was ever shown private boolean menuWasShown = false; private int statusHeight = -1; private static View menu; private static ViewGroup content; private static FrameLayout parent; private static int menuSize; private Activity act; private Drawable headerImage; private Typeface font; private TranslateAnimation slideRightAnim; private TranslateAnimation slideMenuLeftAnim; private TranslateAnimation slideContentLeftAnim; private ArrayList<SlideMenuItem> menuItemList; private SlideMenuInterface.OnSlideMenuItemClickListener callback; /** * Constructor used by the inflation apparatus. * To be able to use the SlideMenu, call the {@link #init init()} method. * @param context */ public SlideMenu(Context context) { super(context); } /** * Constructor used by the inflation apparatus. * To be able to use the SlideMenu, call the {@link #init init()} method. * @param attrs */ public SlideMenu(Context context, AttributeSet attrs) { super(context, attrs); } /** * Constructs a SlideMenu with the given menu XML. * @param act The calling activity. * @param menuResource Menu resource identifier. * @param cb Callback to be invoked on menu item click. * @param slideDuration Slide in/out duration in milliseconds. */ public SlideMenu(Activity act, int menuResource, SlideMenuInterface.OnSlideMenuItemClickListener cb, int slideDuration) { super(act); init(act, menuResource, cb, slideDuration); } /** * Constructs an empty SlideMenu. * @param act The calling activity. * @param cb Callback to be invoked on menu item click. * @param slideDuration Slide in/out duration in milliseconds. */ public SlideMenu(Activity act, SlideMenuInterface.OnSlideMenuItemClickListener cb, int slideDuration) { this(act, 0, cb, slideDuration); } /** * If inflated from XML, initializes the SlideMenu. * @param act The calling activity. * @param menuResource Menu resource identifier, can be 0 for an empty SlideMenu. * @param cb Callback to be invoked on menu item click. * @param slideDuration Slide in/out duration in milliseconds. */ public void init(Activity act, int menuResource, SlideMenuInterface.OnSlideMenuItemClickListener cb, int slideDuration) { this.act = act; this.callback = cb; // set size menuSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 250, act.getResources().getDisplayMetrics()); // create animations accordingly slideRightAnim = new TranslateAnimation(-menuSize, 0, 0, 0); slideRightAnim.setFillAfter(true); slideMenuLeftAnim = new TranslateAnimation(0, -menuSize, 0, 0); slideMenuLeftAnim.setFillAfter(true); slideContentLeftAnim = new TranslateAnimation(menuSize, 0, 0, 0); slideContentLeftAnim.setFillAfter(true); setAnimationDuration(slideDuration); // and get our menu parseXml(menuResource); } /** * Set how long slide animation should be * @see TranslateAnimation#setDuration(long) * @param slideDuration * How long to set the slide animation */ public void setAnimationDuration(long slideDuration) { slideRightAnim.setDuration(slideDuration); slideMenuLeftAnim.setDuration(slideDuration*3/2); slideContentLeftAnim.setDuration(slideDuration*3/2); } /** * Set an Interpolator for the slide animation. * @see TranslateAnimation#setInterpolator(Interpolator) * @param i * The {@link Interpolator} object to set. */ public void setAnimationInterpolator(Interpolator i) { slideRightAnim.setInterpolator(i); slideMenuLeftAnim.setInterpolator(i); slideContentLeftAnim.setInterpolator(i); } /** * Sets an optional image to be displayed on top of the menu. * @param d */ public void setHeaderImage(Drawable d) { headerImage = d; } /** * Optionally sets the font for the menu items. * @param f A font. */ public void setFont(Typeface f) { font = f; } /** * Dynamically adds a menu item. * @param item */ public void addMenuItem(SlideMenuItem item) { menuItemList.add(item); } /** * Empties the SlideMenu. */ public void clearMenuItems() { menuItemList.clear(); } /** * Slide the menu in. */ public void show() { this.show(true); } /** * Set the menu to shown status without displaying any slide animation. */ public void setAsShown() { this.show(false); } @SuppressLint("NewApi") private void show(boolean animate) { /* * We have to adopt to status bar height in most cases, * but not if there is a support actionbar! */ try { Method getSupportActionBar = act.getClass().getMethod("getSupportActionBar", (Class[])null); Object sab = getSupportActionBar.invoke(act, (Object[])null); sab.toString(); // check for null if (android.os.Build.VERSION.SDK_INT >= 11) { // over api level 11? add the margin getStatusbarHeight(); } } catch(Exception es) { // there is no support action bar! getStatusbarHeight(); } // modify content layout params try { content = ((LinearLayout) act.findViewById(android.R.id.content).getParent()); } catch(ClassCastException e) { /* * When there is no title bar (android:theme="@android:style/Theme.NoTitleBar"), * the android.R.id.content FrameLayout is directly attached to the DecorView, * without the intermediate LinearLayout that holds the titlebar plus content. */ if(Build.VERSION.SDK_INT < 18) content = (ViewGroup) act.findViewById(android.R.id.content); else content = (ViewGroup) act.findViewById(android.R.id.content).getParent(); //FIXME? what about the corner cases (fullscreen etc) } FrameLayout.LayoutParams parm = new FrameLayout.LayoutParams(-1, -1, 3); parm.setMargins(menuSize, 0, -menuSize, 0); content.setLayoutParams(parm); // animation for smooth slide-out if(animate) content.startAnimation(slideRightAnim); // quirk for sony xperia devices on ICS only, shouldn't hurt on others if(Build.VERSION.SDK_INT >= 11 && Build.VERSION.SDK_INT <= 15 && Build.MANUFACTURER.contains("Sony") && menuWasShown) content.setX(menuSize); // add the slide menu to parent try{ parent = (FrameLayout) content.getParent(); }catch(ClassCastException e){ /* * Most probably a LinearLayout, at least on Galaxy S3. * https://github.com/bk138/LibSlideMenu/issues/12 */ LinearLayout realParent = (LinearLayout) content.getParent(); parent = new FrameLayout(act); realParent.addView(parent, 0); // add FrameLayout to real parent of content realParent.removeView(content); // remove content from real parent parent.addView(content); // add content to FrameLayout } LayoutInflater inflater = (LayoutInflater) act.getSystemService(Context.LAYOUT_INFLATER_SERVICE); menu = inflater.inflate(R.layout.slidemenu, null); FrameLayout.LayoutParams lays = new FrameLayout.LayoutParams(-1, -1, 3); lays.setMargins(0, statusHeight, 0, 0); menu.setLayoutParams(lays); parent.addView(menu); // set header try { ImageView header = (ImageView) act.findViewById(R.id.menu_header); header.setImageDrawable(headerImage); } catch(Exception e) { // not found } // connect the menu's listview ListView list = (ListView) act.findViewById(R.id.menu_listview); SlideMenuItem[] items = menuItemList.toArray(new SlideMenuItem[menuItemList.size()]); SlideMenuAdapter adap = new SlideMenuAdapter(act, items, font); list.setAdapter(adap); list.setOnItemClickListener(new OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { if(callback != null) callback.onSlideMenuItemClick(menuItemList.get(position).id); hide(); } }); // slide menu in if(animate) menu.startAnimation(slideRightAnim); menu.findViewById(R.id.overlay).setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { SlideMenu.this.hide(); } }); enableDisableViewGroup(content, false); menuIsShown = true; menuWasShown = true; } /** * Slide the menu out. */ @SuppressLint("NewApi") public void hide() { menu.startAnimation(slideMenuLeftAnim); parent.removeView(menu); content.startAnimation(slideContentLeftAnim); FrameLayout.LayoutParams parm = (FrameLayout.LayoutParams) content.getLayoutParams(); parm.setMargins(0, 0, 0, 0); content.setLayoutParams(parm); enableDisableViewGroup(content, true); // quirk for sony xperia devices on ICS only, shouldn't hurt on others if(Build.VERSION.SDK_INT >= 11 && Build.VERSION.SDK_INT <= 15 && Build.MANUFACTURER.contains("Sony")) content.setX(0); menuIsShown = false; } private void getStatusbarHeight() { // Only do this if not already set. // Especially when called from within onCreate(), this does not return the true values. if(statusHeight == -1) { Rect r = new Rect(); Window window = act.getWindow(); window.getDecorView().getWindowVisibleDisplayFrame(r); statusHeight = r.top; } } //originally: http://stackoverflow.com/questions/5418510/disable-the-touch-events-for-all-the-views //modified for the needs here private void enableDisableViewGroup(ViewGroup viewGroup, boolean enabled) { int childCount = viewGroup.getChildCount(); for (int i = 0; i < childCount; i++) { View view = viewGroup.getChildAt(i); if(view.isFocusable()) view.setEnabled(enabled); if (view instanceof ViewGroup) { enableDisableViewGroup((ViewGroup) view, enabled); } else if (view instanceof ListView) { if(view.isFocusable()) view.setEnabled(enabled); ListView listView = (ListView) view; int listChildCount = listView.getChildCount(); for (int j = 0; j < listChildCount; j++) { if(view.isFocusable()) listView.getChildAt(j).setEnabled(false); } } } } // originally: https://github.com/darvds/RibbonMenu // credit where credits due! private void parseXml(int menu){ menuItemList = new ArrayList<SlideMenuItem>(); // use 0 id to indicate no menu (as specified in JavaDoc) if(menu == 0) return; try{ XmlResourceParser xpp = act.getResources().getXml(menu); xpp.next(); int eventType = xpp.getEventType(); while(eventType != XmlPullParser.END_DOCUMENT){ if(eventType == XmlPullParser.START_TAG){ String elemName = xpp.getName(); if(elemName.equals("item")){ String textId = xpp.getAttributeValue("http://schemas.android.com/apk/res/android", "title"); String iconId = xpp.getAttributeValue("http://schemas.android.com/apk/res/android", "icon"); String resId = xpp.getAttributeValue("http://schemas.android.com/apk/res/android", "id"); SlideMenuItem item = new SlideMenuItem(); item.id = Integer.valueOf(resId.replace("@", "")); if (iconId != null) { item.icon = act.getResources().getDrawable(Integer.valueOf(iconId.replace("@", ""))); } item.label = resourceIdToString(textId); menuItemList.add(item); } } eventType = xpp.next(); } } catch(Exception e){ e.printStackTrace(); } } private String resourceIdToString(String text){ if(!text.contains("@")){ return text; } else { String id = text.replace("@", ""); return act.getResources().getString(Integer.valueOf(id)); } } @Override protected void onRestoreInstanceState(Parcelable state) { try{ if (state instanceof Bundle) { Bundle bundle = (Bundle) state; statusHeight = bundle.getInt(KEY_STATUSBARHEIGHT); if(bundle.getBoolean(KEY_MENUSHOWN)) show(false); // show without animation super.onRestoreInstanceState(bundle.getParcelable(KEY_SUPERSTATE)); return; } super.onRestoreInstanceState(state); } catch(NullPointerException e) { // in case the menu was not declared via XML but added from code } } @Override protected Parcelable onSaveInstanceState() { Bundle bundle = new Bundle(); bundle.putParcelable(KEY_SUPERSTATE, super.onSaveInstanceState()); bundle.putBoolean(KEY_MENUSHOWN, menuIsShown); bundle.putInt(KEY_STATUSBARHEIGHT, statusHeight); return bundle; } }