需求如下:顶部UI(这里随便写的),一些标签(服务器给的,这里写死),切换标签展示不同内容,内容分页展示,要求可以下拉刷新(重要页面,没刷新太low了吧),刷新之后还停在当前标签下面,但是内容也要刷新,切换内容也会如此。(插插更健康:github源码有ScrollableLayout三段式悬浮布局,相同需求)。demo效果如下:
需求分析:看上去很简单的一个页面,但是做起来不一定那么顺畅,看我一步步分析。选用的控件毫无疑问是SwipeRefreshLayout+TabLayout+ViewPager+Fragment+FragmentStatePagerAdapter 。
分析1:由于标签数量length不确定(比较多),Fragment有预加载机制,如果使用默认的setOffscreenPageLimit提前加载下一个fragment,切换的时候导致看过的fragment被销毁/detach,再重新切换回去的话又得重新加载页面,请求api,体验很不好。如果使用setOffscreenPageLimit(length)预加载所有的fragment,更浪费资源,也不合理。所以需要使用Fragment的懒加载了,具体实现看后面的代码。
分析2:为什么不用FragmentPagerAdapter而是用FragmentStatePagerAdapter?
FragmentPagerAdapter类内的每一个生成的 Fragment 都将保存在内存之中,因此适用于那些相对静态的页,数量也比较少的那种。Fragment在切换的时候,不会销毁,而只是调用事务中的detach方法,我们只会把我们的Fragment的view销毁,而保留了以前的Fragment对象。所以通过这种方式创建的Fragment一直不会被销毁。
FragmentSatetePagerAdapter 的实现将只保留当前页面,当页面离开视线后,就会被消除,仅保留状态信息,释放其资源;而在页面需要显示时,恢复状态信息,这样更节省内存。
综上,根据需求选定FragmentStatePagerAdapter!
代码实现:
fragment懒加载基类:
import android.support.v4.app.Fragment;
/**
* Created by zrg on 2018/1/2.
* Fragment的懒加载模式
* 使用方法:
* ***** 必须继承 FragmentPagerAdapter/FragmentStatePagerAdapter,才可以对setUserVisibleHint()调用
* setUserVisibleHint() 设置Fragment的可见状态
* getUserVisibleHint() 获取Fragment的可见状态
* mIsVisible 是父类的成员变量,子类不需要重写
* mIsprepared 子类需要在OnCreateView()中重写
* mHasLoadedOnce 子类需要在lazyLoad()中重写
* <p>
* 接入步骤:
* 1:继承BaseLazyFragment,实现lazyLoad()
* 2:在onCreateView()中,添加以下代码:
* mIsprepared = true;
* lazyLoad();
* 3:重写lazyLoad(){
* if (!mIsprepared || !mIsVisible || mHasLoadedOnce) {
* return;
* }
* mHasLoadedOnce = true;
* //UI和业务逻辑
* }
* 4:拓展方法
* stopLoad()://当视图已经对用户不可见并且加载过数据,
* //如果需要在切换到其他页面时停止加载数据,可以覆写此方法
* //此方法只在Fragment之间切换生效
* visibleToUser(): 用户可看见当前的Fragment (神策埋点需要)
* inVisibleToUser():用户看不见当前的Fragment,包含Fragment之间切换和跳转到Activity (神策埋点需要)
*/
public abstract class BaseLazyFragment extends Fragment {
/**
* 判断当前的Fragment是否可见(相对于其他的Fragment)
*/
protected boolean mIsVisible;
/**
* 标志位,标志已经初始化完成
*/
protected boolean mIsprepared;
/**
* 是否已被加载过一次,第二次就不再去请求数据了
*/
protected boolean mHasLoadedOnce;
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
//设置Fragment的可见状态
super.setUserVisibleHint(isVisibleToUser);
if (getUserVisibleHint()) {//getUserVisibleHint获取Fragment可见状态
mIsVisible = true;
onVisible();
} else {
mIsVisible = false;
onInvisible();
}
if (isResumed()) {
onVisibilityChangedToUser(isVisibleToUser);
}
}
/**
* 可见
*/
protected void onVisible() {
lazyLoad();
}
/**
* 不可见
*/
protected void onInvisible() {
stopLoad();
}
/**
* 延迟加载
* 子类必须重写此方法
*/
protected abstract void lazyLoad();
/**
* 当视图已经对用户不可见并且加载过数据,如果需要在切换到其他页面时停止加载数据,可以覆写此方法
*/
protected void stopLoad() {
}
//region 神策埋点需要,统计Fragement 可见时间
@Override
public void onResume() {
super.onResume();
if (getUserVisibleHint()) {
onVisibilityChangedToUser(true);
}
}
@Override
public void onPause() {
super.onPause();
if (getUserVisibleHint()) {
onVisibilityChangedToUser(false);
}
}
/**
* 当Fragment对用户的可见性发生了改变的时候就会回调此方法
*
* @param isVisibleToUser true:用户能看见当前Fragment;false:用户看不见当前Fragment
*/
public void onVisibilityChangedToUser(boolean isVisibleToUser) {
if (isVisibleToUser) {
visibleToUser();
} else {
inVisibleToUser();
}
}
protected void visibleToUser() {
}
protected void inVisibleToUser() {
}
//endregion
}
ViewPagerActivity
public class ViewPagerActivity extends AppCompatActivity {
private String[] TITLES = {"语文", "数学", "英语", "化学", "物理", "生物"};
SwipeRefreshLayout mSwipeRefreshLayout;
TabLayout mTabLayout;
ViewPager mViewPager;
private ViewPagerAdapter mAdapter;
private int mCurrentIndex;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_view_pager);
mSwipeRefreshLayout = (SwipeRefreshLayout) findViewById(R.id.swipeRefreshLayout);
mTabLayout = (TabLayout) findViewById(R.id.tab_layout);
mViewPager = (ViewPager) findViewById(R.id.view_pager);
mAdapter = new ViewPagerAdapter(getSupportFragmentManager(), TITLES);
mViewPager.setAdapter(mAdapter);
mViewPager.setCurrentItem(0);
mViewPager.setOffscreenPageLimit(TITLES.length - 1);
mTabLayout.setupWithViewPager(mViewPager);
final Handler handler = new Handler(Looper.getMainLooper());
mSwipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
loadData();
handler.postDelayed(new Runnable() {
@Override
public void run() {
mSwipeRefreshLayout.setRefreshing(false);
}
}, 1000);
}
});
mViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
@Override
public void onPageSelected(int position) {
mCurrentIndex = position;
}
@Override
public void onPageScrollStateChanged(int state) {
}
});
}
private void loadData() {
String[] titles = {"语文1", "数学1", "英语1", "化学1", "物理1", "生物1"};
mAdapter.setTitles(titles);
}
}
activity_view_pager
<android.support.v4.widget.SwipeRefreshLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/swipeRefreshLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.hp.viewpagerdemo.ViewPagerActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<ImageView
android:layout_width="match_parent"
android:layout_height="120dp"
android:src="@mipmap/ic_launcher"/>
<android.support.design.widget.TabLayout
android:id="@+id/tab_layout"
style="@style/MyTablayoutStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<android.support.v4.view.ViewPager
android:id="@+id/view_pager"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
</android.support.v4.widget.SwipeRefreshLayout>
<!--Tablayout 设置属性--> <style name="MyTablayoutStyle" parent="Widget.Design.TabLayout"> <item name="tabIndicatorColor">#ff4f47</item> <item name="tabSelectedTextColor">#4d4d4d</item> <item name="tabBackground">@android:color/white</item> <item name="tabTextAppearance">@style/MyTabLayoutTextAppearance</item> </style> <!--TabLayout 小标题--> <style name="MyTabLayoutTextAppearance" parent="TextAppearance.AppCompat.Widget.ActionBar.Title"> <item name="android:textColor">#999999</item> <item name="android:textSize">14sp</item> </style>
ViewPagerAdapter
public class ViewPagerAdapter extends FragmentStatePagerAdapter {
private String[] mTitles;
public ViewPagerAdapter(FragmentManager fm, String[] titles) {
super(fm);
mTitles = titles;
}
@Override
public Fragment getItem(int position) {
EmptyFragment fragment = EmptyFragment.newInstance(mTitles[position]);
Log.e("zrg", "getItem: 当前位置position=" + position);
return fragment;
}
@Override
public int getCount() {
return mTitles == null ? 0 : mTitles.length;
}
@Override
public Object instantiateItem(ViewGroup container, int position) {
Log.e("zrg", "instantiateItem: 当前位置position=" + position);
return super.instantiateItem(container, position);
}
@Override
public CharSequence getPageTitle(int position) {
return mTitles == null ? "" : mTitles[position];
}
@Override
public int getItemPosition(Object object) {
return POSITION_NONE;
}
public void setTitles(String[] titles) {
mTitles = titles;
notifyDataSetChanged();
}
}
EmptyFragment
public class EmptyFragment extends BaseLazyFragment {
private static final String ARG_TITLE = "arg_title";
TextView mTvClick;
private String mTitle;
public EmptyFragment() {
}
public static EmptyFragment newInstance(String title) {
EmptyFragment fragment = new EmptyFragment();
Bundle bundle = new Bundle();
bundle.putString(ARG_TITLE, title);
fragment.setArguments(bundle);
return fragment;
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mTitle = getArguments().getString(ARG_TITLE);
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_empty, container, false);
mTvClick = (TextView) view.findViewById(R.id.tv_click);
mIsprepared = true;
lazyLoad();
return view;
}
@Override
protected void lazyLoad() {
if (!mIsprepared || !mIsVisible || mHasLoadedOnce) {
return;
}
mHasLoadedOnce = true;
//UI和业务逻辑
Log.e("zrg", "lazyLoad: 当前的fragment是:" + mTitle);
mTvClick.setText(mTitle);
}
public String getTitle() {
return mTitle;
}
}
fragment_empty
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/tv_click"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
</android.support.constraint.ConstraintLayout>
注意事项:
1、更新fragment不能使用重新设置adapter的方式,只能通过adapter.setTitle(args...)让adapter完成更新。
private void errorLoadData(){
String[] titles = {"语文1", "数学1", "英语1", "化学1", "物理1", "生物1"};
mAdapter = new ViewPagerAdapter(getSupportFragmentManager(), titles);
mViewPager.setAdapter(mAdapter);
mViewPager.setCurrentItem(mCurrentIndex);
mViewPager.setOffscreenPageLimit(titles.length - 1);
mTabLayout.setupWithViewPager(mViewPager);
}
源码地址:https://github.com/zrg1215/ViewPagerRefreshDemo
源码包括ScrollableLayout三段式悬浮布局,实现方式大致相同,由于切换的时候需要知道当前fragment,源码里有 ,如果不了解ScrollableLayout的童鞋, 传送门。