[Android] Another lazy loading of Fragment (only loaded once)

Before use

2017-7-14 Update: At present, after some people use it, there are problems such as opening the blank interface for the first time, but clicking on it responds; or switching back and forth, the interface becomes blank again. I don't know how to solve these problems yet, and I will analyze how to solve the problems in detail when I have time later. So if you want to use this code, I hope to consider it, my own small application has not encountered these problems at present.

Effect

Old rules, let's take a look at the renderings first

demo

 

log

That's right, I got into the pit again and made a gank client again, because the previous code was too badly written, this time I have carefully considered things like the architecture, and the code should be easier to read. Click, let's go. Haha, welcome to star exchange again.

The above screenshot has annotation analysis, take a closer look at the content of the log to see if it is what you need.

Fragment lazy loading

If you want to look at the code directly, just skip to the bottom part of the code and use the introduction. If you are interested, you can take a look at my nagging.

I wrote an article about Fragment lazy loading and ViewPager's pit before, which analyzed some situations encountered when Fragment is used in conjunction with ViewPager, and why lazy loading is used, how to use it, and you can go back and see if you are interested.

Later, I found out that the Fragment base class I encapsulated in that blog was not enough to meet everyone's lazy loading needs, so I decided to re-encapsulate it. This encapsulation supports the following functions:

1. Support lazy loading of data and only load it once

2. Provide callback when Fragment is visible and invisible, support you to perform some ui operations here, such as showing/hiding the loading box

3. Supports the reuse of views to prevent the problem of repeated view creation when used with ViewPager

The first point should be a more necessary and commonly used point. It was my negligence that this application scenario was not considered in the previous blog. To explain a little, sometimes, when we open a Fragment page, we hope that it will only load data when it is visible, that is, do not start loading data in the background, and we also hope that the operation of loading data is only the first time to open the fragment. The operation is only performed when the Fragment is used. If the Fragment is reopened in the future, it is not necessary to repeatedly load the data.

Specifically, when Fragment and ViewPager are used together, due to the caching mechanism of ViewPager, when a Fragment is opened, several Fragments next to it have actually been created. If onCreat()we onCreateView()interact with the server in Fragment or in the download interface data, then these Fragments that have been created at this time will all appear to download data in the background. So we usually need setUserVisibleHint()to judge whether the current Fragment is visible in , and then download the data when it is visible, but there will still be a problem, that is, the data will be downloaded repeatedly every time it is visible, and we hope that it is only visible for the first time. If you need to download it, then you need to make some judgments. This is to encapsulate a base class to do these things, see the specific code later.

Even if we setUserVisibleHint()do a lot of judgment and implement loading when visible and only load the first time it is visible, we may still encounter other problems. For example, after downloading the data, I need to directly operate the ui to display the data, but sometimes the ui control null exception is reported. This is because setUserVisibleHint()it may be onCreateView()called before the view is created, and the data loading time is very short. There may be a null exception, so we need to make some more judgments to ensure that the ui control has been created after the data is downloaded.

In addition to the requirement of lazy loading and loading only once, we may also need to display the progress of data loading every time the Fragment is opened or closed. Right, when we open a Fragment, if the data has not been downloaded yet, we should give a download progress or a loading box prompt. If a new Fragment page is opened at this time, and then return, if the data has not been loaded, Then you should continue to give prompts, right? This requires a callback method that is triggered when the Fragment is visible and invisible, and this method has to be guaranteed to be triggered after the view is created, so as to support the operation of the ui.

The above is what our encapsulated BaseFragment base class is going to do. Code below.

code


/**
 * Created by dasu on 2016/9/27.
 *
 * Fragment基类,封装了懒加载的实现
 *
 * 1、Viewpager + Fragment情况下,fragment的生命周期因Viewpager的缓存机制而失去了具体意义
 * 该抽象类自定义新的回调方法,当fragment可见状态改变时会触发的回调方法,和 Fragment 第一次可见时会回调的方法
 *
 * @see #onFragmentVisibleChange(boolean)
 * @see #onFragmentFirstVisible()
 */
public abstract class BaseFragment extends Fragment {

    private static final String TAG = BaseFragment.class.getSimpleName();

    private boolean isFragmentVisible;
    private boolean isReuseView;
    private boolean isFirstVisible;
    private View rootView;


    //setUserVisibleHint()在Fragment创建时会先被调用一次,传入isVisibleToUser = false
    //如果当前Fragment可见,那么setUserVisibleHint()会再次被调用一次,传入isVisibleToUser = true
    //如果Fragment从可见->不可见,那么setUserVisibleHint()也会被调用,传入isVisibleToUser = false
    //总结:setUserVisibleHint()除了Fragment的可见状态发生变化时会被回调外,在new Fragment()时也会被回调
    //如果我们需要在 Fragment 可见与不可见时干点事,用这个的话就会有多余的回调了,那么就需要重新封装一个
    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);
        //setUserVisibleHint()有可能在fragment的生命周期外被调用
        if (rootView == null) {
            return;
        }
        if (isFirstVisible && isVisibleToUser) {
            onFragmentFirstVisible();
            isFirstVisible = false;
        }
        if (isVisibleToUser) {
            onFragmentVisibleChange(true);
            isFragmentVisible = true;
            return;
        }
        if (isFragmentVisible) {
            isFragmentVisible = false;
            onFragmentVisibleChange(false);
        }
    }

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        initVariable();
    }

    @Override
    public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
        //如果setUserVisibleHint()在rootView创建前调用时,那么
        //就等到rootView创建完后才回调onFragmentVisibleChange(true)
        //保证onFragmentVisibleChange()的回调发生在rootView创建完成之后,以便支持ui操作
        if (rootView == null) {
            rootView = view;
            if (getUserVisibleHint()) {
                if (isFirstVisible) {
                    onFragmentFirstVisible();
                    isFirstVisible = false;
                }
                onFragmentVisibleChange(true);
                isFragmentVisible = true;
            }
        }
        super.onViewCreated(isReuseView ? rootView : view, savedInstanceState);
    }

    @Override
    public void onDestroyView() {
        super.onDestroyView();
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        initVariable();
    }

    private void initVariable() {
        isFirstVisible = true;
        isFragmentVisible = false;
        rootView = null;
        isReuseView = true;
    }

    /**
     * 设置是否使用 view 的复用,默认开启
     * view 的复用是指,ViewPager 在销毁和重建 Fragment 时会不断调用 onCreateView() -> onDestroyView() 
     * 之间的生命函数,这样可能会出现重复创建 view 的情况,导致界面上显示多个相同的 Fragment
     * view 的复用其实就是指保存第一次创建的 view,后面再 onCreateView() 时直接返回第一次创建的 view
     *
     * @param isReuse
     */
    protected void reuseView(boolean isReuse) {
        isReuseView = isReuse;
    }

    /**
     * 去除setUserVisibleHint()多余的回调场景,保证只有当fragment可见状态发生变化时才回调
     * 回调时机在view创建完后,所以支持ui操作,解决在setUserVisibleHint()里进行ui操作有可能报null异常的问题
     *
     * 可在该回调方法里进行一些ui显示与隐藏,比如加载框的显示和隐藏
     *
     * @param isVisible true  不可见 -> 可见
     *                  false 可见  -> 不可见
     */
    protected void onFragmentVisibleChange(boolean isVisible) {

    }

    /**
     * 在fragment首次可见时回调,可在这里进行加载数据,保证只在第一次打开Fragment时才会加载数据,
     * 这样就可以防止每次进入都重复加载数据
     * 该方法会在 onFragmentVisibleChange() 之前调用,所以第一次打开时,可以用一个全局变量表示数据下载状态,
     * 然后在该方法内将状态设置为下载状态,接着去执行下载的任务
     * 最后在 onFragmentVisibleChange() 里根据数据下载状态来控制下载进度ui控件的显示与隐藏
     */
    protected void onFragmentFirstVisible() {

    }

    protected boolean isFragmentVisible() {
        return isFragmentVisible;
    }
}


Instructions

It is very simple to use. Create a new Fragment class that inherits from the BaseFragment, then rewrite the two callback methods, and perform corresponding operations in the callback method according to your needs, such as downloading data.
E.g:

public class CategoryFragment extends BaseFragment {
    private static final String TAG = CategoryFragment.class.getSimpleName();

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_category, container, false);
        initView(view);
        return view;
    }

    @Override
    protected void onFragmentVisibleChange(boolean isVisible) {
        if (isVisible) {
            //更新界面数据,如果数据还在下载中,就显示加载框
            notifyDataSetChanged();
            if (mRefreshState == STATE_REFRESHING) {
                mRefreshListener.onRefreshing();
            }
        } else {
            //关闭加载框
            mRefreshListener.onRefreshFinish();
        }
    }

    @Override
    protected void onFragmentFirstVisible() {
        //去服务器下载数据
        mRefreshState = STATE_REFRESHING;
        mCategoryController.loadBaseData();
    }
}


Precautions

  1. If you want the fragment's layout to be reused successfully, you need to rewrite the destroyItem()method remove super, that is, not to destroy the view.

  2. If there is a problem of blank interface when switching back or non-adjacent Tab switching, the solution is to reuse the layout in onCreateView + Override the destroyItem() method in the adapter of ViewPager to remove super.

Finally, continue to shamelessly paste the project address of the Gank client that I am working on recently. The project does not introduce any advanced libraries, and it is implemented with the most basic code. The project is also divided into modules, and as much as possible To achieve the division of ui and logic, each module also strictly controls permissions, and try to reduce the coupling between modules and classes. The reason for this is to prepare for a deeper understanding of mvp later. In short, the code should still be easy to read. Understand, welcome to star to communicate.

GanHuo: https://github.com/woshidasusu/GanHuo
Meizi: https://github.com/woshidasusu/Meizi

GanHuo

 

QQ picture 20180316094923.jpg

 

I just opened a public account recently. I want to motivate myself to keep on writing. In the initial stage, I mainly shared some original Android or Android-Tv knowledge. If you are interested, you can click on it. Thank you for your support~~



Author: Please call me Dasu
Link: https://www.jianshu.com/p/254dc5ddffea
Source: Jianshu
The copyright belongs to the author. For commercial reprints, please contact the author for authorization, and for non-commercial reprints, please indicate the source.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325443591&siteId=291194637