感觉 Android 到处都是坑,每个地方都要把人折腾半天。
今天来简单说说 Android之ActionBar、Tabs、Fragment、ViewPager 实现标签页切换并缓存页面
关于他们的介绍就不多说了,网上到处都是,只说关键的部分:
我在开发的时候遇到几个疑难问题,花费大量时间处理,总结如下:
1. 关于 Fragment 内部逻辑处理该写在哪个事件回调部分?
2. ViewPager 页面切换动画卡顿,让我头疼了很久。
3. ViewPager 中如何保存 Fragment 当前视图的状态,让 Tabs 页面切换后不会重新加载,这地方很坑爹
4. ActionBar 中的 tab 很多时如何滚动显示
解答:
一、Fragment 的事件回调:
package com.ai9475.meitian.ui.fragment; import android.app.Activity; import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ListView; import com.ai9475.meitian.R; import com.ai9475.meitian.view.DiaryList; import com.ai9475.util.ZLog; /** * * Created by ZHOUZ on 14-1-21. */ public class DiaryListFragment extends BaseFragment { private static final String TAG = "DiaryListFragment"; @Override public void onAttach(Activity activity) { ZLog.i(TAG, "onAttach"); super.onAttach(activity); } @Override public void onCreate(Bundle savedInstanceState) { ZLog.i(TAG, "onCreate"); super.onCreate(savedInstanceState); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { ZLog.i(TAG, "onCreateView"); return inflater.inflate(R.layout.fragment_diary_list, container, false); } @Override public void onActivityCreated(Bundle savedInstanceState) { ZLog.i(TAG, "onActivityCreated"); super.onActivityCreated(savedInstanceState); ZLog.i(TAG, "DiaryList0"); DiaryList diaryList = new DiaryList( getActivity().getApplicationContext(), (ListView) getView().findViewById(R.id.diaryListCt) ); ZLog.i(TAG, "DiaryList load0"); diaryList.load("http://m.ai9475.com/?con=meitian_app"); this.setRetainInstance(true); } @Override public void onStart() { ZLog.i(TAG, "onStart"); super.onStart(); } @Override public void onResume() { ZLog.i(TAG, "onResume"); super.onResume(); } @Override public void onPause() { ZLog.i(TAG, "onPause"); super.onPause(); } @Override public void onStop() { ZLog.i(TAG, "onStop"); super.onStop(); } @Override public void onDestroyView() { ZLog.i(TAG, "onDestroyView"); super.onDestroyView(); } @Override public void onDestroy() { ZLog.i(TAG, "onDestroy"); super.onDestroy(); } @Override public void onDetach() { ZLog.i(TAG, "onDetach"); super.onDetach(); } }
上面的类中的 on 事件就是Fragment主要处理的时间回调,注意复写父类方法时要回调执行父类同名方法,否则会出错
主要复写 onCreateView 方法,返回该 Fragment 所对应的视图对象,这里可以在返回视图对象前进行一些简单的配置,但千万不要写太耗时的处理阻塞UI主线程。
另外 onActivityCreated 方法,是当 activity 的 onCreate 事件结束时的回调,此时当前的Fragment对应的view已经并入到整个布局中,此时可以使用 getView() 方法获取视图对象。
其他几个事件没什么太多可说,有些我也还不是太清楚,还有些动画调用的事件。
二、切换页面卡顿问题
这个问题的产生主要可能是两方面,
1. 没有使用 ViewPager 的缓存,每次切换都重新加载。
2. 加载 Fragment 内部有耗时耗资源的逻辑处理。
这里主要说下第二种情况,我一开始没处理掉 缓存问题时有一个解决办法,
<android.support.v4.view.ViewPager android:id="@+id/tabsViewPager" android:layout_width="match_parent" android:layout_height="match_parent" > </android.support.v4.view.ViewPager>
mViewPager = (ViewPager) findViewById(R.id.tabsViewPager); mViewPager.setOnPageChangeListener( new ViewPager.SimpleOnPageChangeListener() { private static final String TAG = "ViewPager.SimpleOnPageChangeListener"; private ArrayList hasLoadedPages = new ArrayList<Integer>(); @Override public void onPageSelected(int position) { ZLog.i(TAG, "onPageSelected position:"+ position); getSupportActionBar().setSelectedNavigationItem(position); } @Override public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { ZLog.i(TAG, "onPageScrolled position: "+ position +", positionOffset:"+ positionOffset +", positionOffsetPixels:"+ positionOffsetPixels); } @Override public void onPageScrollStateChanged(int state) { ZLog.i(TAG, "onPageScrollStateChanged"); int position = mViewPager.getCurrentItem(); switch (state) { // 正在拖动 case ViewPager.SCROLL_STATE_DRAGGING : ZLog.i(TAG, "ViewPager.SCROLL_STATE_DRAGGING position:"+ position); break; // 拖动释放后正在沉降的过程 case ViewPager.SCROLL_STATE_SETTLING : ZLog.i(TAG, "ViewPager.SCROLL_STATE_SETTLING position:"+ position); break; // 切换动画全部完成结束 case ViewPager.SCROLL_STATE_IDLE : ZLog.i(TAG, "ViewPager.SCROLL_STATE_IDLE position:"+ position); // 已加载过则不再加载 if (hasLoadedPages.contains(position)) break; Fragment fragment = mPager.getFragments().get(position); runCallback(position, fragment); hasLoadedPages.add(position); break; } } public void runCallback(int position, Fragment fragment) { ZLog.i(TAG, "runCallback"); DiaryList diaryList; switch (position) { case 0 : ZLog.i(TAG, "DiaryList0"); diaryList = new DiaryList( getApplicationContext(), (ListView) fragment.getView().findViewById(R.id.diaryListCt) ); ZLog.i(TAG, "DiaryList load0"); diaryList.load("http://m.ai9475.com/?con=meitian_app"); break; case 1: ZLog.i(TAG, "DiaryList1"); diaryList = new DiaryList( getApplicationContext(), (ListView) fragment.getView().findViewById(R.id.diaryListCt) ); ZLog.i(TAG, "DiaryList load1"); diaryList.load("http://m.ai9475.com/?con=meitian_app&act=hot"); break; case 2: ZLog.i(TAG, "DiaryList2"); diaryList = new DiaryList( getApplicationContext(), (ListView) fragment.getView().findViewById(R.id.diaryListCt) ); ZLog.i(TAG, "DiaryList load2"); diaryList.load("http://m.ai9475.com/?con=meitian_app"); break; } } }
这里主要用到 public void onPageScrollStateChanged(int state) 页面滚动切换状态变化的事件监听
当滚动动画完全结束 case ViewPager.SCROLL_STATE_IDLE 时再执行 Fragment 的逻辑处理,这样动画就会流畅了。
但经过后来的测试发现有个很简单的解决方案,就是下面要说到的 ViewPager 的缓存功能,其实很简单。
三、缓存 Tabs 页面切换不重新加载数据
我在这地方折腾的最久,而且很多时候无从下手的感觉,网上搜索了很多文章都有说道保存 Fragment 数据和状态,但是没有整整提到如何来保存他的当前view状态,不知道如何保存,当然实际上我还是没搞懂如何仅仅缓存 Fragment 的状态,但对于 ViewPager 缓存 Tab 对应的 Fragment 还是找到了办法,之前花了很大功夫来自己实现,后来偶然发现他居然有个自带的方法
// 设置缓存多少个 Tab对应的 fragment mViewPager.setOffscreenPageLimit(6);
我测试时用了 6个 listView 加载图片列表数据,切换动画也没有任何卡顿现象,非常流畅,就这么简单一句就搞定了。
配置该项后,ViewPager在切换时将不会清理不可见的 Fragment,不会触发 Fragment 的任何事件,因此也就不会导致其重新加载。
四、ActionBar 中的 Tabs
这个其实不用操作,在tabs数量超过一屏后,例如我现在设置6个宽度超过了屏幕则会变成可以横向滚动的状态,而不需要自己实现,之前不知道在这方面查了很多资料都无果,只知道可以用 tabhost 可以实现,但在ActionBar 里面却又用不了,自己试了下方多个tab才发现原来会自动实现,无语。
还是贴上 MainActivity.class 完整代码:
package com.ai9475.meitian.ui; import android.os.Bundle; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentPagerAdapter; import android.support.v4.app.FragmentStatePagerAdapter; import android.support.v4.app.FragmentTransaction; import android.support.v4.view.ViewPager; import android.support.v7.app.ActionBar; import android.view.Menu; import android.widget.ListView; import com.ai9475.meitian.AppManager; import com.ai9475.meitian.R; import com.ai9475.meitian.ui.fragment.DiaryListFragment; import com.ai9475.meitian.ui.fragment.Test2Fragment; import com.ai9475.meitian.ui.fragment.Test3Fragment; import com.ai9475.meitian.view.DiaryList; import com.ai9475.util.ZLog; import java.util.ArrayList; public class MainActivity extends BaseActivity { private static final String TAG = "MainActivity"; private MyTabsPagerAdapter mPager; private ViewPager mViewPager; @Override protected void onCreate(Bundle savedInstanceState) { ZLog.i(TAG, "start"); // 执行父级初始化方法 super.onCreate(savedInstanceState); //requestWindowFeature(Window.FEATURE_NO_TITLE); ZLog.i(TAG, "setContentView"); setContentView(R.layout.activity_main); // 滑动页面视图配置 ZLog.i(TAG, "MyTabsPagerAdapter start"); mPager = new MyTabsPagerAdapter(getSupportFragmentManager()); mPager.getFragments().add(new DiaryListFragment()); mPager.getFragments().add(new Test2Fragment()); mPager.getFragments().add(new Test3Fragment()); mPager.getFragments().add(new Test3Fragment()); mPager.getFragments().add(new Test3Fragment()); mPager.getFragments().add(new Test3Fragment()); // 滑动分页容器 mViewPager = (ViewPager) findViewById(R.id.tabsViewPager); // 设置缓存多少个 fragment mViewPager.setOffscreenPageLimit(6); mViewPager.setAdapter(mPager); // 页面滑动事件 mViewPager.setOnPageChangeListener( new ViewPager.SimpleOnPageChangeListener() { private static final String TAG = "ViewPager.SimpleOnPageChangeListener"; private ArrayList hasLoadedPages = new ArrayList<Integer>(); @Override public void onPageSelected(int position) { ZLog.i(TAG, "onPageSelected position:"+ position); getSupportActionBar().setSelectedNavigationItem(position); } @Override public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { ZLog.i(TAG, "onPageScrolled position: "+ position +", positionOffset:"+ positionOffset +", positionOffsetPixels:"+ positionOffsetPixels); } @Override public void onPageScrollStateChanged(int state) { ZLog.i(TAG, "onPageScrollStateChanged"); int position = mViewPager.getCurrentItem(); switch (state) { // 正在拖动 case ViewPager.SCROLL_STATE_DRAGGING : ZLog.i(TAG, "ViewPager.SCROLL_STATE_DRAGGING position:"+ position); break; // 拖动释放后正在沉降的过程 case ViewPager.SCROLL_STATE_SETTLING : ZLog.i(TAG, "ViewPager.SCROLL_STATE_SETTLING position:"+ position); break; // 切换动画全部完成结束 case ViewPager.SCROLL_STATE_IDLE : ZLog.i(TAG, "ViewPager.SCROLL_STATE_IDLE position:"+ position); /*if (hasLoadedPages.contains(position)) break; Fragment fragment = mPager.getFragments().get(position); runCallback(position, fragment); hasLoadedPages.add(position);*/ break; } } public void runCallback(int position, Fragment fragment) { ZLog.i(TAG, "runCallback"); DiaryList diaryList; switch (position) { case 0 : ZLog.i(TAG, "DiaryList0"); diaryList = new DiaryList( getApplicationContext(), (ListView) fragment.getView().findViewById(R.id.diaryListCt) ); ZLog.i(TAG, "DiaryList load0"); diaryList.load("http://m.ai9475.com/?con=meitian_app"); break; case 1: ZLog.i(TAG, "DiaryList1"); diaryList = new DiaryList( getApplicationContext(), (ListView) fragment.getView().findViewById(R.id.diaryListCt) ); ZLog.i(TAG, "DiaryList load1"); diaryList.load("http://m.ai9475.com/?con=meitian_app&act=hot"); break; case 2: ZLog.i(TAG, "DiaryList2"); diaryList = new DiaryList( getApplicationContext(), (ListView) fragment.getView().findViewById(R.id.diaryListCt) ); ZLog.i(TAG, "DiaryList load2"); diaryList.load("http://m.ai9475.com/?con=meitian_app"); break; } } } ); ActionBar actionBar = getSupportActionBar(); actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS); // Tab 页面切换 MyTabListener listener = new MyTabListener(); // 默认的首页 tab ActionBar.Tab indexTab = actionBar.newTab() .setText(getString(R.string.tab_index)) .setTabListener(listener); actionBar.addTab(indexTab); actionBar.addTab(actionBar.newTab() .setText(getString(R.string.tab_hot)) .setTabListener(listener) ); actionBar.addTab(actionBar.newTab() .setText(getString(R.string.tab_tag)) .setTabListener(listener) ); actionBar.addTab(actionBar.newTab() .setText(getString(R.string.tab_tag)) .setTabListener(listener) ); actionBar.addTab(actionBar.newTab() .setText(getString(R.string.tab_tag)) .setTabListener(listener) ); actionBar.addTab(actionBar.newTab() .setText(getString(R.string.tab_tag)) .setTabListener(listener) ); // 显示首页 indexTab.select(); } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.main, menu); return true; } private class MyTabListener implements ActionBar.TabListener { @Override public void onTabSelected(ActionBar.Tab tab, FragmentTransaction ft) { int position = tab.getPosition(); ZLog.i(TAG, "tab selected: "+ position); // 数据通信 /*Bundle bundle = new Bundle(); Fragment fragment = mPager.getItem(tab.getPosition()); Toast.makeText(getApplicationContext(), "position:"+ tab.getPosition(), Toast.LENGTH_SHORT).show(); fragment.setArguments(bundle); FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction(); fragmentTransaction.add(R.id.fragmentContainer, fragment); fragmentTransaction.commit();*/ mViewPager.setCurrentItem(position); } @Override public void onTabReselected(ActionBar.Tab tab, FragmentTransaction fragmentTransaction) { ZLog.i(TAG, "tab reselected: "+ tab.getPosition()); } @Override public void onTabUnselected(ActionBar.Tab tab, FragmentTransaction ft) { ZLog.i(TAG, "tab unselected: "+ tab.getPosition()); } }; public class MyTabsPagerAdapter extends FragmentPagerAdapter { private ArrayList<Fragment> mFragments = new ArrayList<Fragment>(); public MyTabsPagerAdapter(FragmentManager fm) { super(fm); } public ArrayList<Fragment> getFragments() { return this.mFragments; } @Override public Fragment getItem(int i) { return this.mFragments.get(i); } @Override public int getCount() { return this.mFragments.size(); } } }