slidingmenu+fragment实现经常使用的側滑效果(包含Fragment状态的保存)
一、需求
关于fragment的问题,一直想写一篇博客了。应该当初自己也是对这玩意一点都不熟悉到如今也大概知道个日常的使用的地步。
一个側滑的导航栏,内有4个条目。每个选项点击进入相应的界面,每个界面是一个fragment,各界面之间自由切换。且能够保存之前的状态,也就是说。切换的过程并不会产生新的对象。不会又一次去new 一个fragment对象,不须要每次点击又一次载入数据。这里就涉及了一个非常重要的问题,fragment状态的保存,在这篇文章里,我尽量用实例把这个问题说清楚。毕竟当初也是查了不少资料,摸索了不少地方才解决的。
另一个问题,是近期发现的。就是从一个activity界面跳到当中的一个fragment,这个在实际的应用中。也会涉及到,由于我们的项目就涉及到这里了,知道最后还是想办法把这个问题攻克了,以下来看看具体的介绍吧。
二、实际效果图
三、实现过程
1、准备工作
先导入slidingmenu库文件,这个相信大家都会把.导入slidingmenu的库文件后会报错。由于它是依赖还有一个库文件的,这个库文件就是ActionBarSherlock,将它的库文件也导入eclipse,这个时候要注意几点。首先。要将actionbarsherlock作为slidingmenu的库文件。然后将slidingmenu作为程序的库文件。
同一时候,要将actionbarsherlock中的android-support-v4包分别拷到slidingmenu库的libs中覆盖原来的android-support-v4包,同理也要覆盖程序的support-v4包,否则就会出现The hierarchy of the type MainActivity is inconsistent这个错误。
导入了对应的库文件后。将主Activity继承SlidingFragmentActivity 就能够使用sliding menu了
2.导航栏界面实现。打开slidingmenu后看到的界面
先做一个側滑导航的fragment,这个fragment就是应用程序的側边导航栏,側滑打开后,点击当中的某一项,就会跳到对应的fragment页面。
我们将这个fragment命名为navifragment。先看navifragment的布局文件
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/navi_list" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#cccccc" > <LinearLayout android:layout_width="fill_parent" android:layout_height="400dip" android:layout_centerInParent="true" android:gravity="center" android:orientation="vertical" > <TextView android:id="@+id/tv_navi_wechat" style="@style/navitext" android:drawableLeft="@drawable/wechat" android:text="微信" /> <TextView android:id="@+id/tv_navi_contacts" style="@style/navitext" android:drawableLeft="@drawable/contacts" android:text="通讯录" /> <TextView android:id="@+id/tv_navi_search" style="@style/navitext" android:drawableLeft="@drawable/search" android:text="发现" /> <TextView android:id="@+id/tv_navi_my" style="@style/navitext" android:drawableLeft="@drawable/my" android:text="我的" /> </LinearLayout> </RelativeLayout>效果例如以下图:
NaviFragment中的代码例如以下:
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { if (rootView == null) { rootView = inflater.inflate(R.layout.fragment_navi, null); } fragmentManager = getFragmentManager(); init(); return rootView; } @Override public void onAttach(Activity activity) { mActivity = (MainActivity) activity; super.onAttach(activity); } /** * 初始化,设置点击事件 */ private void init() { navi_wechat = (TextView) rootView.findViewById(R.id.tv_navi_wechat); navi_contacts = (TextView) rootView.findViewById(R.id.tv_navi_contacts); navi_search = (TextView) rootView.findViewById(R.id.tv_navi_search); navi_my = (TextView) rootView.findViewById(R.id.tv_navi_my); navi_wechat.setSelected(true);// 默认选中菜单 navi_wechat.setOnClickListener(this); navi_contacts.setOnClickListener(this); navi_search.setOnClickListener(this); navi_my.setOnClickListener(this); } /** * 点击导航栏切换 同一时候更改标题 */ @Override public void onClick(View view) { switch (view.getId()) { case R.id.tv_navi_wechat:// 打开微信界面 navi_wechat.setSelected(true);// 微信设置为被选中状态,其余设置为非选中状态 navi_contacts.setSelected(false); navi_my.setSelected(false); navi_search.setSelected(false); OnTabSelected(WECHATFRAGMENT); break; case R.id.tv_navi_contacts:// 打开通讯录界面 navi_wechat.setSelected(false); navi_contacts.setSelected(true); navi_my.setSelected(false); navi_search.setSelected(false); OnTabSelected(CONTACTSFRAGMENT); break; case R.id.tv_navi_search:// 打开发现界面 navi_wechat.setSelected(false); navi_contacts.setSelected(false); navi_my.setSelected(false); navi_search.setSelected(true); OnTabSelected(SEARCHFRAGMENT); break; case R.id.tv_navi_my:// 打开我的界面 navi_wechat.setSelected(false); navi_about.setSelected(false); navi_contacts.setSelected(false); navi_my.setSelected(true); navi_search.setSelected(false); navi_yuefan.setSelected(false); OnTabSelected(MYFRAGMENT); break; } mActivity.getSlidingMenu().toggle(); } //选中导航中相应的tab选项 private void OnTabSelected(int index) { FragmentTransaction transaction = fragmentManager.beginTransaction(); hideFragments(transaction); switch (index) { case WECHATFRAGMENT://微信 if (null == wechatFragment) { wechatFragment = new WechatFragment(); transaction.add(R.id.center_frame, wechatFragment); } else { transaction.show(wechatFragment); } break; case CONTACTSFRAGMENT: //通讯录 if (null == contactsFragment) { contactsFragment = new ContactsFragment(); transaction.add(R.id.center_frame, contactsFragment); } else { transaction.show(contactsFragment); // String phone = SharedPrefsUtil.getString(getActivity(), // AppConst.USERPHONE); // if (phone != null && !"".equals(phone)) { // contactsFragment.et_phone.setText(phone); // } } break; case SEARCHFRAGMENT://发现 if (null == serchFragment) { serchFragment = new SerchFragment(); transaction.add(R.id.center_frame, serchFragment); } else { transaction.show(serchFragment); } break; case MYFRAGMENT://我的 if (null == myFragment) { myFragment = new MyFragment(); transaction.add(R.id.center_frame, myFragment); } else { transaction.show(myFragment); } break; } transaction.commit(); } /** * 将全部fragment都置为隐藏状态 * * @param transaction * 用于对Fragment运行操作的事务 */ private void hideFragments(FragmentTransaction transaction) { if (wechatFragment != null) { transaction.hide(wechatFragment); } if (contactsFragment != null) { transaction.hide(contactsFragment); } if (serchFragment != null) { transaction.hide(serchFragment); } if (myFragment != null) { transaction.hide(myFragment); } }上面有两个重要的方法,涉及到fragment状态的保存
transcation.hide() transction.show()
前一个是fragment的隐藏,将fragment隐藏到后面去,当然。这个fragment并没有被destroy,一直保存着隐藏前的状态,当运行show()方法后。该fragment将再次显示,之前的全部状态都继续存在。这样做的优点就是不用每次点击某一个item就new 一个fragment的对象,避免了频繁的创建fragment。也避免了每次载入相同的数据,尤其是在实际应用中,可能载入一个主页的fragment会消耗大量的网络资源,不进行fragment的保存,用户体验也会差非常多,相同的内容,尤其是一些不是常常变化的数据。每次载入,非常让人难接受。
看上面的代码,当点击一个item条目的时候。首先是将之前全部的fragment都进行隐藏,然后进行推断是否存在该fragment,假设存在,就直接将该fragment显示。假设不存在,则new一个该fragment的对象,并将该fragment对象增加transaction事务控制中去。
FragmentManager fragmentManager = getFragmentManager(); FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();一般通过以上两句代码获得fragmenttransaction对象,通过该对象对fragment进行管理。
如今。导航栏的界面NaviFragment已经准备好了,接下来是slidingmenu,通过slidingmenu打开导航栏
3.slidingmenu的实现
slidingmenu对象是通过以下的方法获取到的
SlidingMenu mSlidingMenu = getSlidingMenu();然后,能够设置slidingmenu的一些參数和属性,这里举几个最经常使用的属性设置
mSlidingMenu.setMode(SlidingMenu.LEFT);// 设置slidingmeni从哪側出现 mSlidingMenu.setTouchModeAbove(SlidingMenu.TOUCHMODE_MARGIN);// 打开模式 有全屏。仅边界 mSlidingMenu.setBehindOffsetRes(R.dimen.slidingmenu_offset);// 側滑距离右边界的偏移量
<p style="margin-top: 0px; margin-bottom: 0px; font-size: 11px; font-family: Monaco;"> <span style="white-space:pre"> </span> //mSlidingMenu.setTouchModeAbove(SlidingMenu.<span style="color: #0326cc">TOUCHMODE_FULLSCREEN</span>);<span style="color: #4e9072">//全屏都能够打开</span></p><p style="margin-top: 0px; margin-bottom: 0px; font-size: 11px; font-family: Monaco;"><span style="white-space:pre"> </span> //mSlidingMenu.setTouchModeAbove(SlidingMenu.<span style="color: #0326cc">TOUCHMODE_NONE</span>);<span style="color: #4e9072">//不能够通过滑动打开</span></p> mSlidingMenu.setFadeEnabled(true);//设置側滑开关时是否须要淡入淡出的效果 mSlidingMenu.setFadeDegree(0.5f);//设置淡入淡出的程序 mSlidingMenu.setMenu(R.layout.frame_navi);
setBehindContentView(R.layout.frame_navi); // 给滑出的slidingmenu的fragment制定layout
MainActivity的代码
public class MainActivity extends SlidingFragmentActivity { private WechatFragment wechatFragment; private NaviFragment naviFragment; @Override public void onCreate(Bundle savedInstanceState) { // TODO Auto-generated method stub super.onCreate(savedInstanceState); initFragment(); } private void initFragment() { SlidingMenu mSlidingMenu = getSlidingMenu(); setBehindContentView(R.layout.frame_navi); // 给滑出的slidingmenu的fragment制定layout naviFragment = new NaviFragment(); getSupportFragmentManager().beginTransaction() .replace(R.id.frame_navi, naviFragment).commit(); // 设置slidingmenu的属性 mSlidingMenu.setMode(SlidingMenu.LEFT);// 设置slidingmeni从哪側出现 mSlidingMenu.setTouchModeAbove(SlidingMenu.TOUCHMODE_FULLSCREEN);//全屏都能够打开 mSlidingMenu.setBehindOffsetRes(R.dimen.slidingmenu_offset);// 偏移量 mSlidingMenu.setFadeEnabled(true); mSlidingMenu.setFadeDegree(0.5f); mSlidingMenu.setMenu(R.layout.frame_navi); Bundle mBundle = null; // 导航打开监听事件 mSlidingMenu.setOnOpenListener(new OnOpenListener() { @Override public void onOpen() { } }); // 导航关闭监听事件 mSlidingMenu.setOnClosedListener(new OnClosedListener() { @Override public void onClosed() { } }); } }
4.接下来是几个Fragment界面的实现,这几个界面就是实际开发中最基本的几个界面。
fragment的生命周期不同于activity,可是很类似,有兴趣的同学能够去研究一下,这里就不列出了。当中的oncreatview是用来载入界面的。返回值view就是要显示的view。
比如我们的首页,wechatfragment
public class WechatFragment extends Fragment { @Override public void onCreate(Bundle savedInstanceState) { // TODO Auto-generated method stub super.onCreate(savedInstanceState); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { // TODO Auto-generated method stub return inflater.inflat(R.layout.wechat_fargment,null); } }
其它几个fragment的页面跟这个页面类似。
最后,执行程序,就能够得到一个带slidingmenu的fragment应用,点击导航栏中的不同条目,跳转到不同的fragment。
效果图例如以下所看到的:
以下,我们来看一种奇怪的现象。fragment出现了重叠的情况,看图:
后面的fragment与首页的fragment出现了重叠的现象,这个在开发过程中是个很大的bug,你点击通讯录界面的button,可能就把微信界面的button也给点击了,这个在实际开发中是不同意出现的。
我之前就犯了这个大错误,每次在后面的fragment点击的时候。前面fragment界面也对点击事件进行了响应了。头疼了好久,最后最终找到原因,就是在MainAcitity里面加了这么一句代码:
wechatFragment = new WechatFragment(); getSupportFragmentManager().beginTransaction().replace(R.id.center_frame, wechatFragment).commit();导致了这个问题的产生。
当然,这个问题又能够带来一个应用,比方有好几个fragment标题栏有同一个控件,这个时候,就能够利用这个特性来合理利用了,这个我也用过。
只是如今找到这个bug的原因了,就不再这么干了。
fragment还有各种应用,会出现一些意想不到的问题,还有非常多值得探索的地方,实际开发中非常多问题都涉及到,这里就不细细说明了。
slidingmenu+fragment的简单应用就是这样了。
放上demo的下载地址:http://download.csdn.net/detail/yanglfree/7594673