catalogue

  • 01.ViewPager
  • 02. Disadvantages analysis of ViewPager
  • 03. ViewPager preloading
  • 04 ViewPager part of the source code
  • 05. Lazy loading has problems
  • 06. How to implement the preloading mechanism
  • 07. Lazy loading with state manager

Good news

  • Summary of blog notes [March 2016 to present], including Java basic and in-depth knowledge points, Android technology blog, Python learning notes, etc., including the summary of bugs encountered in daily development, of course, I also collected a lot of interview questions in my spare time, updated, maintained and corrected for a long time, and continued to improve… Open source files are in Markdown format! At the same time, ALSO open source life blog, from 12 years, accumulated a total of N [nearly 1 million words, gradually moved to the Internet], reprint please indicate the source, thank you!
  • Link address:Github.com/yangchong21…
  • If you feel good, you can star, thank you! Of course, also welcome to put forward suggestions, everything starts from small, quantitative change causes qualitative change!

01.ViewPager

  • Instead of managing the View, the ViewPager uses a key object to associate each page. This key is used to track and uniquely identify a page in a separate location in the Adapter. Calling the method startUpdate(ViewGroup) indicates that the contents of the ViewPager need to change.
  • Construct the page view by calling one or more calls to instantiateItem(ViewGroup, int).
  • Call destroyItem(ViewGroup, Int, Object) to remove the page view associated with the ViewPager.
  • Finally, when an update (add and/or remove) is complete, finishUpdate(ViewGroup) is called to notify the Adapter and commit the association and/or unassociation operations. These three methods are used by the ViewPager to call back the PagerAdapter to manage the pages in it.
  • A very simple way is to associate each page view with itself as a key and return that page view after creating and adding them to the ViewGroup in the method instantiateItem(ViewGroup, int). The matching method destroyItem(ViewGroup, int, Object) removes the view from the ViewGroup. Of course you have to do this in isViewFromObject(View, Object) : Return View == Object; .
  • PagerAdapter supports a refresh interface when data changes, which must be called in the main thread, and the method notifyDataSetChanged() is called after the data changes are complete, similar to the BaseAdapter derived from AdapterView. A data change may be associated with adding, removing, or changing the location of a page. The ViewPager will use the result returned by the Adapter implementation getItemPosition(Object) method to determine whether to keep the currently constructed active page (that is, to reuse it rather than completely construct it itself).

02. Disadvantages analysis of ViewPager

  • The normal viewpager will load the left and right pages of the page by default if you don’t use setoffScreenPagelimit (int Limit), which means that when you enter the first page of the viewpager, the second and first pages will be loaded together. If we set setoffScreenPagelimit to 3, then when we enter the viewpager, we will load 4 fragments at the same time. In our projects, these fragments will send network requests. That is to say, we have four fragments sending network requests to get data at the same time, which obviously results in bad user experience (e.g., waste of user traffic, lag, etc.).
  • Implementation disadvantages of lazy loading
    • Concept: Load the object when needed and keep it there.
    • None of the PagerAdapters that Fragment implementations hold their references and state completely. The FragmentPageAdapter needs to rebuild the View, the FragmentStatePageAdapter uses state recovery, the View is destroyed, but the recovery is different, and generally what we want is that once the Fragment is loaded, the View is not destroyed. That is, it does not go through the life cycle again. And the ViewPager preloads the left and right pages in order to slide.
    • There are two things we generally want to achieve: no slippage, build only when needed, and only go through the life cycle once, avoiding too much state saving and recovery in fragments.

03. ViewPager preloading

  • ViewPager’s preloading mechanism. So, can we set the ViewPager preload to 0 and solve the problem? That is, the code works like this:
    vp.setOffscreenPageLimit(0);
    Copy the code
  • Then look at the source code
    • Even if you set it to 0, you’ll still check inside and set it to 1 by default. So it’s not going to work.
    public void setOffscreenPageLimit(int limit) {
        if (limit < 1) {
            Log.w("ViewPager"."Requested offscreen page limit " + limit + " too small; defaulting to " + 1);
            limit = 1;
        }
    
        if (limit! = this.mOffscreenPageLimit) { this.mOffscreenPageLimit =limit; this.populate(); }}Copy the code
  • ViewPager is loaded by default. When switching to the current page, the left and right sides of the layout are preloaded into the ViewPager by default, even though the views on both sides are not visible. This is called preloading. Because ViewPager places a limit on offscreenPageLimit, page preloading is inevitable…
  • Initializing the cache (mOffscreenPageLimit == 1)
    • When initialized, the current display page is page 0; MOffscreenPageLimit is set to 1, so the preload page is page 1, and pages beyond that do not need to be loaded (pages 2, 3, 4 here)
  • Intermediate page cache (mOffscreenPageLimit == 1)
    • When you swipe right to page 2, you need to cache the left and right pages, destroy page 0, preload page 3, and leave page 4 unloaded

04 ViewPager part of the source code

  • ViewPager. SetAdapter method
    • Destroy the old Adapter data and update the UI with the new Adaper
    • Remove the old Adapter and call destroyItem on the loaded item.
    • Scroll itself to its original position this.scrollto (0, 0)
    • Set PagerObserver: mAdapter setViewPagerObserver (mObserver);
    • Call the populate() method to calculate and initialize the View. (This method is covered later.)
    • If OnAdapterChangeListener is set, perform a callback
  • ViewPager.populate(int newCurrentItem)
    • This method is very important for ViewPager. It calculates the page to initialize and the page to destroy based on the parameters newCurrentItem and mOffscreenPageLimit. Then initialize the new page and destroy unwanted pages by calling Adapter’s methods on instantiateItem and destroyItem!
    • Calculate the page page to load based on newCurrentItem and mOffscreenPageLimit, and calculate startPos and endPos
    • InstantiateItem (startPos and endPos initialization page ItemInfo); if not, call addNewItem (mAdapter.InstantiateItem)
    • Remove unwanted ItemInfo: mitems.remove (itemIndex), and call the mAdapter.DestroyItem method
    • Set the LayoutParams parameter (including position and widthFactor) to sort the list of views to be drawn by position: mDrawingOrderedChildren, overriding the getChildDrawingOrder method
    • RequestFocus (view.focus_forward) : currView.requestfocus (view.focus_forward)
  • ViewPager.dataSetChanged()
    • This method is triggered when the Adapter notifyDataSetChanged is called, which recalculates the position of the current page,
    • Remove the ItemInfo object from the page that needs to be destroyed, and then call the Populate method to refresh the page
    • Loop through mItems (ItemInfo object for each page) and call the int newPos = mAdapter.getitemPosition method
    • POSITION_UNCHANGED means that the current page does not need to be updated or destroyed. When newPos is equal to pagerAdapter.position_None, the page needs to be updated and the item removed. Call mAdapter destroyItem
    • Populate after the loop completes, setCurrentItemInternal(newCurrItem, false, True) calculates the displayed page’s newCurrItem and updates the UI.
  • ViewPager.scrollToItem(int item, boolean smoothScroll, int velocity, boolean dispatchSelected)
    • Sliding to a specified page internally triggers an OnPageChangeListener
  • ViewPager.calculatePageOffsets(ItemInfo curItem, int curIndex, ItemInfo oldCurInfo)
    • This method is used to calculate the offset variable corresponding to ItemInfo for each page. This variable is used to record the current view index in all cache views (including the current display page), and is used to calculate where the view should be placed during layout
    • After the populate method has updated the page data, it calls the populate method to calculate the offsets for all the pages

05. Lazy loading has problems

  • The Fragment contains a setUserVisibleHint(Boolean isVisibleToUser) method that tells the user whether the UI is visible to the user.
    • Since ViewPager loads many fragments, to save content, call onDestroyView() at some point when the Fragment is not visible and destroy the user interface, but the instance of the Fragment is still there, so it may not be a problem to load the first time. But when you go back to the first Fragment and load it again, the UI is visible to the user but the view is not initialized.
  • Lazy loading needs to be dealt with several issues
    • Preloading, which doesn’t show up on the screen, but the Fragment on the previous and next pages of the current page has performed all of the life-cycle methods that a Fragment can show up on the screen, but we don’t want to actually construct the data view and request data until we jump to the page. So you can use a placeholder view, and you can think of using a ViewStub that executes the viewstub.inflate () method when you actually jump to the page, loading the real data view and requesting the data.
  • View to save
    • When a page is out of viewable and preloaded range, it is destroyed, FragmentStatePagerAdapter destruction of the fragments, and we can save the fragments, or use FragmentPagerAdapter let FragmentTransition to retain the Fragment reference. However, its periodic method has run out, so we have to manually save the reference to the Fragment root View and return to the original View when re-entering the new declaration periodic method
  • Whether the user has seen it
    • Itself, actually FragmentManager does not provide for fragments by the user can see the callback method, but in FragmentPagerAdapter and FragmentStatePagerAdapter, Call the fragments. SetUserVisibleHint (Boolean) to show whether the fragments have been as primaryFragment. So this method can be thought of as a callback method.

06. How to implement the preloading mechanism

  • The main method is setUserVisibleHint() in the Fragment. This method is executed before onCreateView() and is called when the Fragment changes its visible state in viewPager. When the Fragment goes from visible to invisible, GetUserVisibleHint () returns whether or not the fragment is visible, either by switching from invisible to visible.
  • BaseLazyFragment calls lazyLoad() in both onActivityCreated() and setUserVisibleHint(). If you only call lazyLoad() in setUserVisibleHint(), when the default home page is loaded first, the viewPager’s home page is shown for the first time with no data, and only if you toggle it will have data. Because the home fragment setUserVisible() is called before onActivityCreated() and isPrepared is false, the home fragment cannot call onLazyLoad() to load data.
    / * * * < pre > * @ author yangchong * blog: https://github.com/yangchong211 * time: 2017/7/22 * desc: lazy loading * revise: Lazy loading time: onCreateView() method completes +setThe UserVisibleHint() method returnstrue* </pre> */ public Abstract Class BaseLazyFragment extends BaseFragment {/* * Preload page callback lifecycle process: *setUserVisibleHint() -->onAttach() --> onCreate()-->onCreateView()--> * onActivityCreate() --> onStart() --> onResume() */ /** * protected Boolean isLazyLoaded =false; /** * Private Boolean isPrepared =false; When the onViewCreated() method is executed to indicate that the View is already loaded, change isPrepared totrue@override public void onActivityCreated(@nullable Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); isPrepared =true; LazyLoad (); lazyLoad(); } /** * Step 2 * This method is executed before onCreateView() * this method is called when the fragment changes its visible state in viewPager * This method is called when the fragment changes from visible to invisible, or from invisible to visibletrueThe current page is visible,false*/ @override public voidsetUserVisibleHint(boolean isVisibleToUser) {
            super.setUserVisibleHint(isVisibleToUser);
            LogUtil.d("setUserVisibleHint---"+isVisibleToUser); // Load data only when the fragment is visibleif(isVisibleToUser){ lazyLoad(); }} /** * call lazyLoad */ private void */lazyLoad() {
            if(getUserVisibleHint() && isPrepared && ! isLazyLoaded) { showFirstLoading(); onLazyLoad(); isLazyLoaded =true;
            } else{// This method can be overridden if the view is already invisible to the user and the data has been loaded and you need to stop loading data while switching to another pageif(isLazyLoaded) { stopLoad(); }}} /** * The Fragment is initialized when the view is destroyedfalse
         */
        @Override
        public void onDestroyView() {
            super.onDestroyView();
            isLazyLoaded = false;
            isPrepared = false; } /** * This method can be used for showLoading when it is seen for the first time. Note that this is global loading loading */ protected voidshowFirstLoading() {
            LogUtil.i("Show global loading when first visible"); } /** * Stops loading * When the view is already invisible to the user and has loaded data, but is not finished loading, instead loading loading. * You can override this method if you need to stop loading data while switching to another page. * How do I stop loading the network */ protected voidstopLoad@uithread protected abstract void onLazyLoad(); @uithread protected onLazyLoad(); }Copy the code
  • OnLazyLoad () loads the data condition
    • GetUserVisibleHint () returns whether the fragment is visible or not, which is the key to lazy loading. OnLazyLoad () is called only if the fragment is visible.
    • The isPrepared parameter is set to true when onActivityCreated is called, and the onCreateView method has already been called (findViewById, etc.) to ensure that onLazyLoad() does not raise a null pointer exception.
    • IsLazyLoaded ensures that the initData method of the BaseFragment is not called repeatedly when ViewPager switches back and forth. OnLazyLoad is called only once during the Fragment’s lifetime, isLazyLoaded = true immediately after the first call to onLazyLoad().
    • Then you can inherit the BaseLazyFragment and implement the onLazyLoad() method. It will automatically load data when the fragment is displayed
  • There are a few details that need to be refined
    • You can override this method, known as stopLoad, if you need to stop loading data when switching to another page when the view is already invisible to the user
    • The state of whether the Fragment is initialized is false during view destruction
    • The showFirstLoading method can be used to load data globally. This is not the same as pulling down or Loading data locally. For some app developers, loading is not so detailed.

07. Lazy loading with state manager

  • What is a state manager?
    • Generally, in the scenario where users need to wait, displaying a Loading animation can let users know that the App is Loading data instead of the program being stuck, so as to give users a better use experience.
    • Displaying an empty view when the loaded data is empty, displaying the UI for a failed load when the data fails, and supporting retry is a better user experience than a blank screen.
    • Generally speaking, the UI style of loading, loading failure and empty data should be consistent in all pages of the App, that is, global unification should be achieved.
  • How to reduce compatibility and invasiveness
    • To completely separate the View state switch from the Activity, you must wrap these state views into a management class and expose several methods for switching between views. Different projects can require different views, so consider designing the management class as Builder mode to add the required state views.
    • So how do you reduce coupling and make your code less intrusive? Easy to maintain and modify, and portability? Something like this…
      • Can be used in activities or fragments
      • There is no need to add LoadingView in the layout, but unified management of different state views, and exposed external Settings of the custom state view method, convenient UI specific page customization
      • Support for customizing different state views, even if the BaseActivity handles state view management uniformly, it also supports individual page customization
      • Can you use a ViewStub instead of an exception or empty page when loading a view to reduce the rendering and only inflate the view out when an exception or empty page occurs
      • When the page appears network abnormal page, empty page, etc., the page will have interactive events, at this time you can set click to set the network or click reload, etc
  • So how do you do it?
    • You can freely switch content, empty data, abnormal error, load, network error and other 5 states. The parent BaseFragment class directly exposes 5 states, which is convenient for subclasses to manage state switches in a unified manner. Here, the encapsulation of fragments is similar to that of activities.
    /** * <pre> * @author yangchong * blog : https://github.com/yangchong211 * time : 2017/7/20 * desc : Fragment parent * revise: Pay attention to, This class has lazy loading * </pre> */ public Abstract Class BaseStateFragment extends BaseLazyFragment {protected StateLayoutManager statusLayoutManager; private View view; @Nullable @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {if(view==null){
                view = inflater.inflate(R.layout.base_state_view, container , false);
                initStatusLayout();
                initBaseView(view);
            }
            returnview; } @Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); initView(view); initListener(); } @Override public void onActivityCreated(@Nullable Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); } private void initBaseView(View View) {LinearLayout llStateView = view.findViewById(R.id.ll_state_view); llStateView.addView(statusLayoutManager.getRootLayout()); } /** * Initializes state manager operations */ protected abstract void initStatusLayout(); @param View */ public void initView(View View); /** * Public void initListener(); /** * When the state is visible for the first time, the showLoading operation is used. Note that this global loading */ @override protected void is not used in the drop-down refresh operationshowFirstLoading() {
            super.showFirstLoading();
            showLoading();
        }
    
        /*protected void initStatusLayout() { statusLayoutManager = StateLayoutManager.newBuilder(activity) .contentView(R.layout.common_fragment_list) .emptyDataView(R.layout.view_custom_empty_data) .errorView(R.layout.view_custom_data_error) .loadingView(R.layout.view_custom_loading_data) .netWorkErrorView(R.layout.view_custom_network_error) .build(); } * / / * -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- here are state switching method -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - * / / * * * load success * / protected voidshowContent() {
            if(statusLayoutManager! =null){ statusLayoutManager.showContent(); }} /** * Load no data */ protected voidshowEmptyData() {
            if(statusLayoutManager! =null){ statusLayoutManager.showEmptyData(); }} /** */ protected voidshowError() {
            if(statusLayoutManager! =null){ statusLayoutManager.showError(); }} /** * Network load exception */ protected voidshowNetWorkError() {
            if(statusLayoutManager! =null){ statusLayoutManager.showNetWorkError(); } /** * loading loading */ protected voidshowLoading() {
            if(statusLayoutManager! =null){ statusLayoutManager.showLoading(); }}} // How to switch states? showContent(); showEmptyData(); showError(); showLoading(); showNetWorkError(); / / or operations can also be statusLayoutManager. ShowLoading (); statusLayoutManager.showContent();Copy the code
  • Design idea of state manager
    • The StateFrameLayout custom layout is inherited from FrameLayout. It is mainly used to store different views and hide and show views
    • StateLayoutManager is a state manager that allows developers to set views of different state views and switch view states
      • ViewStub should be used for several abnormal states, because loading and content View are always loaded and displayed during interface state transition. However, the other three states can only be loaded and displayed when there is no data or network abnormality. Therefore, loading them with ViewStub can improve performance.
    • OnRetryListener is an interface for retries. You can use this if the load fails and you need to refresh the interface by clicking on the view. Developers can also set their own click events
    • As for the state view switching scheme, there are many ways to do it in the market. For details, you can see my blog: juejin.cn/post/684490…

The other is introduced

01. About blog summary links

  • 1. Tech blog round-up
  • 2. Open source project summary
  • 3. Life Blog Summary
  • 4. Himalayan audio summary
  • 5. Other summaries

02. About my blog

  • Github:github.com/yangchong21…
  • Zhihu: www.zhihu.com/people/yczb…
  • Jane: www.jianshu.com/u/b7b2c6ed9…
  • csdn:my.csdn.net/m0_37700275
  • The Himalayan listening: www.ximalaya.com/zhubo/71989…
  • Source: China my.oschina.net/zbj1618/blo…
  • Soak in the days of online: www.jcodecraeer.com/member/cont.
  • Email address: [email protected]
  • Blog: ali cloud yq.aliyun.com/users/artic… 239.headeruserinfo.3.dT4bcV
  • Segmentfault headline: segmentfault.com/u/xiangjian…
  • The Denver nuggets: juejin. Cn/user / 197877…

Project Address:Github.com/yangchong21…