Tips:
如何设置 xml 的属性预览可见, 运行不可见
方法: xml 根布局添加xmlns:tool="http://schemas.android.com/tools", 子控件命名空间设置tool:text等, 只在Design视图能看见, 运行后不可见 Link
Fragment
ViewPager打开第 1 个fragment时会打开第 2 个 fragment 并执行第 2 个的生命周期从 onCreateView
到 onResume
(点击第 2 个时会打开第 3 个的...), 为避免在 onCreateView
里进行太多操作, 我们考虑只在 fragment可见 && 第一次加载 时 进行数据加载
- Fragment 搭配 ViewPager 时会多在生命周期夺走一个方法
setUserVisibleHint()
, 我们可手动调用getUserVisibleHint
看回调setUserVisibleHint()
用在单个 Fragment 时不会被调用setUserVisibleHint()
调用在onAttach之前
如何懒加载(两种)
- Fragment.setUserVisibleHint
- ViewPager.addOnPageChangeListener里的
onPageSelected(int position)
第一种.setUserVisibleHint
我们需要做的判断
- fragment可见, 我们可以根据setUserVisibleHint时判断isVisibleToUser
- 是否加载过View, 通过自己创建标识位
- 是否加载过数据, 通过自己创建标识位
写法
- 先写 BaseFragment
public abstract class BaseFragment extends Fragment {
protected Activity mActivity;
protected View mRootView;
private Unbinder unbinder;
/**
* 说明:在此处保存全局的Context
*
* @param context 上下文
*/
@Override
public void onAttach(Context context) {
super.onAttach(context);
mActivity = (Activity) context;
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
mRootView = inflater.inflate(getLayoutId(), container, false);
unbinder = ButterKnife.bind(this, mRootView);
init();
return mRootView;
}
/**
* @return 返回该Fragment的layout id
*/
protected abstract int getLayoutId();
/**
* 说明:创建视图时的初始化操作均写在该方法
*/
protected abstract void init();
/**
* 获取控件对象
*
* @param id 控件id
* @return 控件对象
*/
public View findViewById(int id) {
if (getContentView() != null) {
return getContentView().findViewById(id);
} else {
return null;
}
}
/**
* 说明:返回当前View
*
* @return view
*/
protected View getContentView() {
return mRootView;
}
@Override
public void onDestroyView() {
super.onDestroyView();
unbinder.unbind();
}
}
- 再写 lazyFragment
/**
* 懒加载Fragment
*
* 可以加载数据的条件:
* 1.视图已经初始化
* 2.视图对用户可见
*/
public abstract class LazyLoadFragment extends BaseFragment {
public boolean isInit = false;//视图是否已经初始化
public boolean isLoad = false;//视图是否已经加载过
/**
* 初始化
*/
@Override
protected void init() {
isInit = true;
isCanLoadData();
}
/**
* 判断是否可以加载数据, 如果可以便进行数据的加载
*/
private void isCanLoadData() {
if (!isInit) {//未初始化
return;
}
if (getUserVisibleHint()) {//用户可见
lazyLoad();
isLoad = true;
} else {
if (isLoad) {//用户不可见, 且已经加载过
stopLoad();
}
}
}
/**
* 当视图初始化并且对可见时加载数据
*/
public abstract void lazyLoad();
/**
* 当该视图对用户不可见并且已经加载过数据的时候, 如果需要在切换到其他页面时停止加载数据, 通过覆写此方法实现
*/
public void stopLoad() {
}
/**
* 说明:当前视图可见性发生变化时调用该方法
*
* @param isVisibleToUser 当前视图是否可见
*/
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
isCanLoadData();
}
/**
* 视图销毁时将Fragment是否初始化的状态变为false
*/
@Override
public void onDestroyView() {
super.onDestroyView();
isInit = false;
isLoad = false;
}
}
第二种, 通过onPageSelected判断
缺点: 第 1 页第 1 次进入不调用 onPageSelected
viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
@Override
public void onPageSelected(int position) {
// TODO
fragment.initData();
}
@Override
public void onPageScrollStateChanged(int state) {
}
});
作者:最最最最醉人
链接:http://www.jianshu.com/p/843088e1ad40
來源:简书
著作权归作者所有。商业转载请联系作者获得授权, 非商业转载请注明出处。
PagerAdapter
分为三种
(1)PagerAdapter 数据源:List<View> 适合轮播图,图片查看等
(2)FragmentPagerAdapter 数据源:List<Fragment>
(3)FragmentStatePagerAdapter 数据源:List<Fragment>
FragmentPagerAdapter 和 FragmentStatePagerAdapter区别
- FragmentPagerAdapter: fragmentTransaction()调用depatch(), 生命周期走到onDestroyView(), 适合主界面, 页面少, 只回收view, 数据保留
- FragmentStatePagerAdapter: fragmentTransaction()调用remove(), 生命周期走到onDetach(), 适合多页面, 数据多, view和data都回收, 但也会调onSaveInstance方法, 可存在bundle.
使用FragmentStatePagerAdapter打开多个Fragment:
-
第一次进入页面
0: onAttach 0: onCreate 1: onAttach 1: onCreate 0: onCreateView 0: onViewCreated 0: onActivityCreated 0: onStart 0: onResume 1: onCreateView 1: onViewCreated 1: onActivityCreated 1: onStart 1: onResume
-
从第0页到第1页
2: onAttach 2: onCreate 2: onCreateView 2: onViewCreated 2: onStart 2: onResume
-
从第1页到第2页, 会把第0页杀到
onDetach
3: onAttach 3: onCreate 0: onPause 0: onStop 0: onDestroyView 0: onDestroy 0: onDetach 3: onCreateView 3: onViewCreated 3: onActivityCreated 3: onStart 3: onResume
-
从第1页到第0页
2: onPause 2: onStop 2: onDestroyView 2: onDestroy 2: onDetach
-
FragmentPagerAdapter则是让不可见的页面其生命周期走到onDestroyView
3: onAttach 3: onCreate 0: onPause 0: onStop 0: onDestroyView 3: onCreateView 3: onViewCreated 3: onActivityCreated 3: onStart 3: onResume
adapter到底是外部还是内部的
- adapter别持有Activity的context, 否则可能内存泄漏OOM
- 尽量 内部, 按照谷歌的示例
tab关联viewpager
tabLayout.setupWithViewPager(viewpager); 传入viewpager
-
setupWithViewPager
方法会在populateFromPagerAdapter
清空tab, 再调用getPageTitle
, 所以正确设置tab的方式是getPageTitle里拼CharSequence Link//...省略一部分代码 //处理从方法中传入的viewpager if (viewPager != null) { mViewPager = viewPager; //①以下这部分代码是处理一些滑动的关联关系。例如滑动监听 // Add our custom OnPageChangeListener to the ViewPager if (mPageChangeListener == null) { mPageChangeListener = new TabLayoutOnPageChangeListener(this); } mPageChangeListener.reset(); viewPager.addOnPageChangeListener(mPageChangeListener); // Now we'll add a tab selected listener to set ViewPager's current item mCurrentVpSelectedListener = new ViewPagerOnTabSelectedListener(viewPager); addOnTabSelectedListener(mCurrentVpSelectedListener); //②以下这部分代码就可以解释为什么我们的tab设置的内容会不见了 final PagerAdapter adapter = viewPager.getAdapter(); if (adapter != null) { // Now we'll populate ourselves from the pager adapter, adding an observer if // autoRefresh is enabled //将外部设置给viewpager的adapter取出来通过setPagerAdapter重新设置 setPagerAdapter(adapter, autoRefresh); } ... } ... } void setPagerAdapter(@Nullable final PagerAdapter adapter, final boolean addObserver) { if (mPagerAdapter != null && mPagerAdapterObserver != null) { // If we already have a PagerAdapter, unregister our observer mPagerAdapter.unregisterDataSetObserver(mPagerAdapterObserver); } //注册observer mPagerAdapter = adapter; if (addObserver && adapter != null) { // Register our observer on the new adapter if (mPagerAdapterObserver == null) { mPagerAdapterObserver = new PagerAdapterObserver(); } adapter.registerDataSetObserver(mPagerAdapterObserver); } // Finally make sure we reflect the new adapter //关键代码 populateFromPagerAdapter(); } void populateFromPagerAdapter() { //移除所有的tab //我们之前设置的tab就是在这里被移除的 removeAllTabs(); if (mPagerAdapter != null) { final int adapterCount = mPagerAdapter.getCount(); for (int i = 0; i < adapterCount; i++) { //重新添加tab, 而setText得内容是在PagerAdapter中getPageTitle获取的, 因此问题的答案就已经很明显了。 addTab(newTab().setText(mPagerAdapter.getPageTitle(i)), false); } // Make sure we reflect the currently set ViewPager item if (mViewPager != null && adapterCount > 0) { final int curItem = mViewPager.getCurrentItem(); if (curItem != getSelectedTabPosition() && curItem < getTabCount()) { selectTab(getTabAt(curItem)); } } } } 作者:liaoweijian 链接:http://www.jianshu.com/p/d83f10c1a765 來源:简书 著作权归作者所有。商业转载请联系作者获得授权, 非商业转载请注明出处。
tab添加标题
- adapter的
getPageTitle(int position)
返回每个pager对应的标题名
tab添加图片
- adapter的
getPageTitle(int position)
的 CharSequence 看不见图片;
解决: 在 style 里把 textAllCaps 置为 false
style可以设置在styles里, 也可以单独在xml里添加style<style name="HomeTabLayout" parent = "Widget.Design.TabLayout"> <item name="tabTextAppearance">@style/HomeTabTextAppearance</item> </style> <style name="HomeTabTextAppearance" parent="TextAppearance.Design.Tab"> <!--显示tab图标--> <item name="textAllCaps">false</item> </style>
自定义tabItem
在activity中添加, 防止将context传给外部Adapter, setupWithViewPager先执行再for循环添加view, 简单的按钮切换用selector
@Override
protected void onCreate(Bundle savedInstanceState) {
tabLayout.setupWithViewPager(viewpager);
for (int i = 0; i < tabLayout.getTabCount(); i++){
tabLayout.getTabAt(i).setCustomView(getTabView(i));
}
}
public View getTabView(int position){
View view = LayoutInflater.from(this).inflate(R.layout.tab_home_bottom, null);
ImageView ivHomeTab = (ImageView) view.findViewById(R.id.iv_home_tab);
ivHomeTab.setImageResource(tabIcons[position]);
return view;
}
setTabMode
- TabLayout.MODE_SCROLLABLE 跟着viewpager滚动
- TabLayout.MODE_FIXED 固定的Tab, tabGravity.GRAVITY_FILL需要和 TabLayout.MODE_FIXED 一起使用才有效
下划线Indicator
- style里通过tabIndicatorColor设置颜色, tabIndicatorHeight设置宽高
处理横竖切换(待验证)
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt(POSITION,tabLayout.getSelectedTabPosition());
}
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
viewPager.setCurrentItem(savedInstanceState.getInt(POSITION));
}
ViewPager
保存多少个Pager 用 ViewPager.setOffscreenPageLimit(num)
- 保留 2 num + 1 个页面不被销毁, 即设置1时保留左右两边的
- 默认是1, 除非修改源码
Viewpager + PagerAdapter + TabLayout
ViewPager viewpager = (ViewPager) findViewById(R.id.viewpager);
viewpager.setAdapter(new FragmentStatePagerAdapter(getSupportFragmentManager()) {
@Override
public Fragment getItem(int position) {
Fragment fragment = null;
switch (position) {
case 0:
fragment = new FragmentZero();
break;
case 1:
fragment = new FragmentOne();
break;
case 2:
fragment = new FragmentTwo();
break;
case 3:
fragment = new FragmentThree();
break;
case 4:
fragment = new FragmentFour();
break;
case 5:
fragment = new FragmentFive();
break;
}
return fragment;
}
@Override
public int getCount() {
return 6;
}
@Override
public CharSequence getPageTitle(int position) {
switch (position){
case 0:
return "0";
case 1:
return "1";
case 2:
return "2";
case 3:
return "3";
case 4:
return "4";
default:
return "0";
}
}
});
TabLayout tabLayout = (TabLayout) findViewById(R.id.tablayout);
tabLayout.setupWithViewPager(viewpager);
参考
- 通用写法
- 通用写法原地址
- 完善写法
- fragment生命周期
- 哪些操作需要懒加载
- Fragment懒加载
- 10条 | Fragment 懒加载实战
- 简书 | 如何高效的使用ViewPager, 以及FragmentPagerAdapter与FragmentStatePagerAdapter的区别
- 简书 | ViewPager使用详解(三):FragmentStatePagerAdapter
- 简书 | TabLayout使用详解
- android design library提供的TabLayout的用法
- Android Fragment+ViewPager 组合, 一些你不可不知的注意事项
- 简书 | Android TabLayout 分分钟打造一个滑动标签页
- CSDN | 告诉你ListView的Adapter应该写在Activity外面还是里面