小迈 2016-06-02
转载请注明出处:http://blog.csdn.net/allen315410/article/details/39611355
在实际项目开发中,定制一个菜单,能让用户得到更好的用户体验,诚然菜单的样式各种各样,但是有一种菜单——滑动菜单,是被众多应用广泛使用的。关于这种滑动菜单的实现,我在前面的博文中也介绍了如何自定义去实现,请参考Android自定义控件——侧滑菜单,这篇博文描述的是如何从无到有创建一个侧滑菜单的控件,里面的代码不多,但是处理的逻辑和各种效果比较复杂,如果稍有不慎,这种自定义控件就要BUG不断,难以在项目中使用,而且实现的效果比较单一。
好在有开源力量的存在,在开源世界里,一切常用的实用的东西,都会有大牛帮我们做好了。所以,这种侧滑菜单不难被找到,下面就来介绍一下这个开源的侧滑菜单SlidingMenu。
SlidingMenu在GitHub中可以被找到,下面是GitHub源码的地址,大家可以点进去,下载这个源码。另外,SlidingMenu这个开源组件也是基于另外一个开源组件之上的,这个开源组件是ActionBarSherlock,也需要下载下来。
SlidingMenu项目:https://github.com/jfeinstein10/SlidingMenu
ActionBarSherlock项目:https://github.com/JakeWharton/ActionBarSherlock
PS:关于SlidingMenu的作者Jeremy Feinstein和ActionBarSherlock的作者JakeWharton,都是大牛,前者还是JazzyViewPager的作者,后者更是Android-ViewPagerIndicator、NineOldAndroids、DiskLruCache等等的作者,大家可以注册一下GitHub的账号,选择Follow一下这些大牛,站在巨人的肩膀上进步。
首先解压这个ActionBarSherlock的压缩包,找到actionbarsherlock这个包,这个工程是类库,下面打开eclipse,Import->Android->Android Project from Existing Code,导入这个actionbarsherlock。
然后解压这个SlidingMenu的压缩包,找到library,同样的方法Import->Android->Android Project from Existing Code,导入SlidingMenu的library,然后右键这个library,Properties->Android->add,选择上面导入的actionbarsherlock。
接着新建一个Android项目,在这个项目里引用SlidingMenu,同样,右键工程目录->Properties->Android->add,添加上面导入的SlidingMenu的类库library,这样,这个SlidingMenu就算是导入到了工程中了。注意,当导入这个包的时候有可能会报下面的错误:
引起这个错误的原因是ActionBarSherlock和SlidingMenu以及我们的工程里面的android-support-v4.jar这个包版本不一致,解决的办法是,将我们自己工程libs目录下的android-support-v4.jar复制黏贴到ActionBarSherlock和SlidingMenu的类库的libs目录下,选择同意覆盖,这样这个报错就消除了。
1.你可以通过new SlidingMenu(Context context)的方式把你的activity包含在一个slidingmenu里,然后调用SlidingMenu.attachToActivity(Activity activity, SlidingMenu.SLIDING_WINDOW | SlidingMenu.SLIDING_CONTENT)方法。SLIDING_WINDOW会在SlidingMenu的内容部分包含ActionBar,而SLIDING_CONTENT不会。你可以参加示例项目里的AttachExample。
2.你可以让你的activity继承SlidingActivity来在activity级别上嵌入SlidingMenu。
2.1 在你Activity的onCreate()方法里,像平常一样调用setContentView()方法,也要调用setBehindContentView()方法,它和setContentView()方法有同样的语法结构。setBehindContentView()方法会把view放置在SlidingMenu的后面。你也可以使用getSlidingMenu()方法,这样你就可以自定义你链接的slidingMenu了。
2.2 如果你想使用其它的库,例如ActionBarSherlock,你只需要改变SlidingActivity的继承关系,让它继承SherlockActivity就可以了,原来继承的是Activity。这一点尤为重要,若是在Activity中需要引用ActionBar时,必须修改当前Activity继承于SherlockActivity,不然会产生意想不到的错误。
3.你可以在Java代码里用编程来使用SlidingMenu,也可以在xml布局文件里使用。你可以把SlidingMenu当成一种其它的视图类型,并可以把它放在一些非常棒的地方,例如ListView的行里。
1,下面是SlidingMenu在GitHub主页中的介绍,翻译过来大致的意思如下:
Simple Example - 简单示例
public class SlidingExample extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setTitle(R.string.attach); // set the content view setContentView(R.layout.content); // configure the SlidingMenu SlidingMenu menu = new SlidingMenu(this); menu.setMode(SlidingMenu.LEFT); menu.setTouchModeAbove(SlidingMenu.TOUCHMODE_FULLSCREEN); menu.setShadowWidthRes(R.dimen.shadow_width); menu.setShadowDrawable(R.drawable.shadow); menu.setBehindOffsetRes(R.dimen.slidingmenu_offset); menu.setFadeDegree(0.35f); menu.attachToActivity(this, SlidingMenu.SLIDING_CONTENT); menu.setMenu(R.layout.menu); } }
XML Usage - xml用法
如果你决定要把SlidingMenu当作一个view,那你可以在xml文件里定义它:
public class SlidingFragmentActivity extends FragmentActivity implements SlidingActivityBase
会发现SlidingFragmentActivity是继承了FragmentActivity,那么就决定着,接下来的UI界面都是使用Fragment来实现的,包括左右菜单,主页UI都是Fragment。先来看看这个左右菜单的实现,比较简单,直接上主要代码:
package com.example.slidingmenudemo; import com.example.slidingmenudemo.fragment.BaseFragment; import com.example.slidingmenudemo.fragment.FocusFragment; import com.example.slidingmenudemo.fragment.LocalFragment; import com.example.slidingmenudemo.fragment.PicsFragment; import com.example.slidingmenudemo.fragment.ReadFragment; import com.example.slidingmenudemo.fragment.TiesFragment; import com.example.slidingmenudemo.fragment.UgcFragment; import com.example.slidingmenudemo.fragment.VoteFragment; import android.os.Bundle; import android.support.v4.app.Fragment; import android.view.LayoutInflater; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; public class LeftMenuFragment extends Fragment implements OnClickListener { private MainActivity mAct; private View view; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { view = inflater.inflate(R.layout.menu_left_frag, null); mAct = (MainActivity) getActivity(); view.findViewById(R.id.tab_news).setOnClickListener(this); view.findViewById(R.id.tab_read).setOnClickListener(this); view.findViewById(R.id.tab_local).setOnClickListener(this); view.findViewById(R.id.tab_ties).setOnClickListener(this); view.findViewById(R.id.tab_pics).setOnClickListener(this); view.findViewById(R.id.tab_focus).setOnClickListener(this); view.findViewById(R.id.tab_vote).setOnClickListener(this); view.findViewById(R.id.tab_ugc).setOnClickListener(this); return view; } @Override public void onClick(View v) { BaseFragment fragment = null; switch (v.getId()) { case R.id.tab_news: fragment = new HomeFragment(); break; case R.id.tab_read: fragment = new ReadFragment(); break; case R.id.tab_local: fragment = new LocalFragment(); break; case R.id.tab_ties: fragment = new TiesFragment(); break; case R.id.tab_pics: fragment = new PicsFragment(); break; case R.id.tab_focus: fragment = new FocusFragment(); break; case R.id.tab_vote: fragment = new VoteFragment(); break; case R.id.tab_ugc: fragment = new UgcFragment(); break; default: break; } mAct.switchContent(fragment); fragment = null; } }
由于右边菜单的主要代码跟左边菜单的代码很相似,限于篇幅,这里就不贴了,有兴趣的请点击下面的源码链接,下载源码。看源码里面可以发现,当点击菜单上某一个标签时,要将标签代表的内容放到主页上去显示,可以看出这个所谓的“内容”都是一个个的Fragment实现的,要让这些Fragment在FragmentActivity上不停的切换来达到主页内容变化的效果,下面是内容的代码段,很简单,只贴出一段了,不同的内容页不同的布局,在项目按照需求定夺,我这里是简单的处理。
1,Fragment布局XML
package com.example.slidingmenudemo.fragment; import com.example.slidingmenudemo.MainActivity; import com.example.slidingmenudemo.R; import com.jeremyfeinstein.slidingmenu.lib.SlidingMenu; import android.app.Activity; import android.content.Context; import android.os.Bundle; import android.support.v4.app.Fragment; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.View.OnClickListener; import android.widget.ImageButton; public abstract class BaseFragment extends Fragment implements OnClickListener { protected Context ct; /** SlidingMenu对象 */ protected SlidingMenu sm; public View rootView; protected Activity MenuChangeHome; /** 左菜单按钮 */ private ImageButton leftMenuBtn; /** 右菜单按钮 */ private ImageButton rightMenuBtn; @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); sm = ((MainActivity) getActivity()).getSlidingMenu(); initData(savedInstanceState); } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ct = getActivity(); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { rootView = initView(inflater); leftMenuBtn = (ImageButton) rootView.findViewById(R.id.ib_menu_left); rightMenuBtn = (ImageButton) rootView.findViewById(R.id.ib_menu_right); leftMenuBtn.setOnClickListener(this); rightMenuBtn.setOnClickListener(this); setListener(); return rootView; } public View getRootView() { return rootView; } @Override public void onClick(View v) { // TODO Auto-generated method stub switch (v.getId()) { case R.id.ib_menu_left: // 点击左边的按钮,左菜单收放 sm.toggle(); break; case R.id.ib_menu_right: // 点击右边按钮,右菜单缩放 sm.showSecondaryMenu(); break; default: break; } } /** * 初始化UI * * @param inflater * @return */ protected abstract View initView(LayoutInflater inflater); /** * 初始化数据 * * @param savedInstanceState */ protected abstract void initData(Bundle savedInstanceState); /** * 设置监听 */ protected abstract void setListener(); }
4,其中的一个Fragment主要代码,其实里面什么也没有
public class FocusFragment extends BaseFragment { @Override protected View initView(LayoutInflater inflater) { // TODO Auto-generated method stub return inflater.inflate(R.layout.frag_focus, null); } @Override protected void initData(Bundle savedInstanceState) { // TODO Auto-generated method stub } @Override protected void setListener() { // TODO Auto-generated method stub } }
最后,来重点看一下这个MainActivity里面的代码,在这里才是构建一个侧滑菜单效果的主要代码,源代码如下,可以参考注释来看:
package com.example.slidingmenudemo; import com.jeremyfeinstein.slidingmenu.lib.SlidingMenu; import com.jeremyfeinstein.slidingmenu.lib.SlidingMenu.CanvasTransformer; import com.jeremyfeinstein.slidingmenu.lib.app.SlidingFragmentActivity; import android.graphics.Canvas; import android.os.Bundle; import android.support.v4.app.Fragment; import android.view.animation.Interpolator; public class MainActivity extends SlidingFragmentActivity { /** 侧滑菜单 */ private SlidingMenu sm; /** 左边菜单 */ private LeftMenuFragment mLeftMenu; /** 右边菜单 */ private RightMenuFragment mRightMenu; /** 主界面 */ private HomeFragment mHomeFragment; /** 动画类 */ private CanvasTransformer mTransformer; /** 保存Fragment的状态 */ private Fragment mContent; @Override public void onCreate(Bundle savedInstanceState) { // TODO Auto-generated method stub super.onCreate(savedInstanceState); initAnimation(); sm = getSlidingMenu(); setContentView(R.layout.content_frame); setBehindContentView(R.layout.menu_left_frag); sm.setSecondaryMenu(R.layout.menu_right_frag); if (savedInstanceState == null) { mLeftMenu = new LeftMenuFragment(); mRightMenu = new RightMenuFragment(); mHomeFragment = new HomeFragment(); getSupportFragmentManager().beginTransaction().replace(R.id.menu_left_frag, mLeftMenu, "Left").commit(); getSupportFragmentManager().beginTransaction().replace(R.id.content_frame, mHomeFragment, "Home").commit(); getSupportFragmentManager().beginTransaction().replace(R.id.menu_right_frag, mRightMenu, "Right").commit(); } sm.setSecondaryShadowDrawable(R.drawable.rightshadow); // 设置右边菜单的阴影 sm.setShadowDrawable(R.drawable.shadow); // 设置阴影图片 sm.setShadowWidthRes(R.dimen.shadow_width); // 设置阴影图片的宽度 sm.setBehindOffsetRes(R.dimen.slidingmenu_offset); // 显示主界面的宽度 sm.setFadeDegree(0f); // SlidingMenu滑动时的渐变程度 sm.setBehindScrollScale(0f); sm.setTouchModeAbove(SlidingMenu.TOUCHMODE_FULLSCREEN); // 设置滑动的屏幕范围,该设置为全屏区域都可以滑动 sm.setMode(SlidingMenu.LEFT_RIGHT); // 设置菜单同时兼具左右滑动 sm.setBehindCanvasTransformer(mTransformer); // 设置动画 } /** * 初始化菜单滑动的效果动画 */ private void initAnimation() { mTransformer = new CanvasTransformer() { @Override public void transformCanvas(Canvas canvas, float percentOpen) { canvas.scale(interp.getInterpolation(percentOpen), interp.getInterpolation(percentOpen), canvas.getWidth() / 2, canvas.getHeight() / 2); // canvas.translate(0, canvas.getHeight() * (1 - // interp.getInterpolation(percentOpen))); } }; } private static Interpolator interp = new Interpolator() { @Override public float getInterpolation(float t) { t -= 1.0f; return t * t * t + 1.0f; } }; /** * 切换到主界面 * * @param fragment */ public void switchContent(Fragment fragment) { mContent = fragment; getSupportFragmentManager().beginTransaction().replace(R.id.content_frame, fragment).commit(); getSlidingMenu().showContent(); } /** * 保存Fragment的状态 */ @Override protected void onSaveInstanceState(Bundle outState) { // TODO Auto-generated method stub super.onSaveInstanceState(outState); getSupportFragmentManager().putFragment(outState, "Home", mContent); } }
上面的代码里关于Fragment使用以及Fragment的API不是这篇博文的重点,如果看不明白就先学习一下Fragment。我们关注这个菜单的实现即可,我使用的方式是Activity继承SlidingFragmentActivity,这种方式是很常见的,也很好维护,只要理解Fragment使用即可。诚然,我的这个Demo里没有使用SlidingMenu的依赖库ActionBarSherlock,是因为这个Demo已经自己定义了左右菜单导航按钮,无需使用ActionBar。
关于代码中的初始化动画,有必要解释一下。这个初始化动画可以不设置,默认就是左右水平方向的平移,这样比较死板,但是SlidingMenu的作者考虑到了我们的担忧,很“t贴心”的为我们写好了实现菜单滑动效果动画的接口,不得不说这外国大牛想的还真细腻。
sm.setBehindCanvasTransformer(mTransformer); // 设置动画
其中mTransformer是SlidingMenu类下的内部接口CanvasTransformer对象,源码如下:
/** * The Interface CanvasTransformer. */ public interface CanvasTransformer { /** * Transform canvas. * * @param canvas the canvas * @param percentOpen the percent open */ public void transformCanvas(Canvas canvas, float percentOpen); }
实现这个接口,复写这个transformCanvas方法,使用该方法中提供的Canvas对象来实现相应的动画效果。当SlidingMenu触发了setBehindCanvasTransformer(mTransformer)时,这个方法会触发SlidingMenu下的自定义View对象的dispatchDraw(Canvas canvas)方法,即每次菜单界面重绘的时候,这个动画就会被调用。关于动画的写法,我提供两个吧,实际情况得根据项目需求来定夺。
SlidingMemu的缩放动画:
/** * 初始化菜单滑动的效果动画 */ private void initAnimation() { mTransformer = new CanvasTransformer() { @Override public void transformCanvas(Canvas canvas, float percentOpen) { canvas.scale(interp.getInterpolation(percentOpen), interp.getInterpolation(percentOpen), canvas.getWidth() / 2, canvas.getHeight() / 2); } }; } private static Interpolator interp = new Interpolator() { @Override public float getInterpolation(float t) { t -= 1.0f; return t * t * t + 1.0f; } };
SlidingMenu的平移动画:
/** * 初始化菜单滑动的效果动画 */ private void initAnimation() { mTransformer = new CanvasTransformer() { @Override public void transformCanvas(Canvas canvas, float percentOpen) { canvas.translate(0, canvas.getHeight() * (1 - interp.getInterpolation(percentOpen))); } }; } private static Interpolator interp = new Interpolator() { @Override public float getInterpolation(float t) { t -= 1.0f; return t * t * t + 1.0f; } };
SlidingMenu的拉伸动画:
private void initAnimation() { mTransformer = new CanvasTransformer() { @Override public void transformCanvas(Canvas canvas, float percentOpen) { canvas.scale(percentOpen, 1, 0, 0); } }; }
最后一点,需要注意的是,在给SlidingMenu添加了动画效果后,运行时会看见菜单的背景处是一片“白色”或者“透明色”的,这样的用户体验非常的不好,那么怎么解决呢?这个很简单的,第一个考虑到是SlidingMenu本身没有设置背景,所以我们要手动的给SlidingMenu控件设置一下背景,打开SlidingMenu的类库library,在res/layout目录下找到slidingmenumain.xml的文件,打开看见这里就简单的定义了一个SlidingMenu,其它什么都没有,此时我们就可以在这个slidingmenumain.xml的节点下设置一下drawable属性即可。
library/res/layout/slidingmenumain.xml: