Lesson 125. ViewPager

Lesson 125. ViewPager


In this lesson:

– we understand ViewPager

ViewPager allows us to organize a convenient and beautiful view of the data with the ability to swipe from left to right. ViewPager itself is responsible for displaying and scrolling. But he also needs PagerAdapter, which is responsible for providing the data.

PagerAdapter is a basic abstract class for which the developer writes implementation as it is necessary. There is a common standard (partial) implementation of PagerAdapter that works with fragments – it’s FragmentPagerAdapter. The developer only has to create a snippet and determine the number of pages.

Let’s write a simple example and look at the main features of ViewPager and FragmentPagerAdapter.

Let’s create a project:

Project name: P1251_ViewPager
Build Target: Android 2.3.3
Application name: ViewPager
Package name: en.startandroid.develop.p1251viewpager
Create Activity: MainActivity

IN main.xml we write:



	
	

ViewPager component only.

Let’s create a fragment.

layout file fragment.xml:



	
	

Here is just a TextView that will display the content of the page.

class PageFragment.java:

package ru.startandroid.develop.p1251viewpager;

import java.util.Random;

import android.graphics.Color;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

public class PageFragment extends Fragment {
  
  static final String ARGUMENT_PAGE_NUMBER = "arg_page_number";
  
  int pageNumber;
  int backColor;
  
  static PageFragment newInstance(int page) {
    PageFragment pageFragment = new PageFragment();
    Bundle arguments = new Bundle();
    arguments.putInt(ARGUMENT_PAGE_NUMBER, page);
    pageFragment.setArguments(arguments);
    return pageFragment;
  }
  
  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    pageNumber = getArguments().getInt(ARGUMENT_PAGE_NUMBER);
    
    Random rnd = new Random();
    backColor = Color.argb(40, rnd.nextInt(256), rnd.nextInt(256), rnd.nextInt(256));
  }
  
  @Override
  public View onCreateView(LayoutInflater inflater, ViewGroup container,
      Bundle savedInstanceState) {
    View view = inflater.inflate(R.layout.fragment, null);
    
    TextView tvPage = (TextView) view.findViewById(R.id.tvPage);
    tvPage.setText("Page " + pageNumber);
    tvPage.setBackgroundColor(backColor);
    
    return view;
  }
}

method newInstance creates a new instance of the snippet and writes it to the attributes of the number that came in. This number is the page number that ViewPager wants to display. The snippet will determine what content to create in the snippet.

IN onCreate we read the page number from the arguments. Next, we form the color of the random components. It will be used as a page background to visually distinguish one page from another.

IN onCreateView we create a View, find a TextView on it, write simple text with a page number, and set a background color.

That is, we go to the page number, and at the exit we get a snippet that displays that number and has a random background color.

The fragment is ready, we write MainActivity.java:

package ru.startandroid.develop.p1251viewpager;

import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentPagerAdapter;
import android.support.v4.view.PagerAdapter;
import android.support.v4.view.ViewPager;
import android.support.v4.view.ViewPager.OnPageChangeListener;
import android.util.Log;

public class MainActivity extends FragmentActivity {

  static final String TAG = "myLogs";
  static final int PAGE_COUNT = 10;

  ViewPager pager;
  PagerAdapter pagerAdapter;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);

    pager = (ViewPager) findViewById(R.id.pager);
    pagerAdapter = new MyFragmentPagerAdapter(getSupportFragmentManager());
    pager.setAdapter(pagerAdapter);

    pager.setOnPageChangeListener(new OnPageChangeListener() {

      @Override
      public void onPageSelected(int position) {
        Log.d(TAG, "onPageSelected, position = " + position);
      }

      @Override
      public void onPageScrolled(int position, float positionOffset,
          int positionOffsetPixels) {
      }

      @Override
      public void onPageScrollStateChanged(int state) {
      }
    });
  }

  private class MyFragmentPagerAdapter extends FragmentPagerAdapter {

    public MyFragmentPagerAdapter(FragmentManager fm) {
      super(fm);
    }

    @Override
    public Fragment getItem(int position) {
      return PageFragment.newInstance(position);
    }

    @Override
    public int getCount() {
      return PAGE_COUNT;
    }

  }

}

IN onCreate create an adapter and install it for ViewPager. We also create an event handler for ViewPager. It has three methods:

onPageSelected – gives the number of the currently displayed page

onPageScrolled – difficult to explain in words. The method gives us an idea of ​​the current value of the scroller while viewing. I recommend putting a log there, flipping through and seeing what comes out.

onPageScrollStateChanged – Tells us about the status of the scroller (SCROLL_STATE_IDLE – Scrolls nothing, SCROLL_STATE_DRAGGING – user pulls the page, SCROLL_STATE_SETTLING – scroller flips the page to the end)

FragmentPagerAdapter class – abstract. We need to implement a couple of methods in it. To do this, we create a class MyFragmentPagerAdapter. We implement the following methods:

getItem – we need to return the snippet by page number, we use our newInstance method

getCount – here we have to return the number of pages, we use a constant

We all save and launch the application.

We turn pages, in logs we see:

onPageSelected, position = 1
onPageSelected, position = 2
onPageSelected, position = 3
onPageSelected, position = 4
onPageSelected, position = 3
onPageSelected, position = 2
onPageSelected, position = 1
onPageSelected, position = 0

Flip handler triggers.

PagerTitleStrip

You can attach an item to the ViewPager that will display the headers – PagerTitleStrip.

rewrite main.xml:



	
		
		
	

We place PagerTitleStrip inside ViewPager.

And in the adapter code, you must add the getPageTitle method to let PagerTitleStrip know which text to display in the header. In class MyFragmentPagerAdapter in MainActivity.java add:

    @Override
    public CharSequence getPageTitle(int position) {
      return "Title " + position;
    }

We will simply return “Title” and page number

Save, run.

Headlines appeared above.

PagerTabStrip

Headers can participate in navigation. This is done using the PagerTabStrip component. This is analogous to PagerTitleStrip, but when you click on a title, it flips the page. Its embedding is exactly the same as PagerTitleStrip just reviewed

change main.xml:



	
		
		
	

It also requires the adapter getPageTitle method, which we have already implemented in MyFragmentPagerAdapter.

Save, run.

Click on the left and right headers, the pages will burn.

Lifecycle

Let’s take a closer look at what happens to snippets at the time of the flip. To do this, rewrite PageFragment.java:

package ru.startandroid.develop.p1251viewpager;

import java.util.Random;

import android.graphics.Color;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

public class PageFragment extends Fragment {
  
  static final String TAG = "myLogs";
  
  static final String ARGUMENT_PAGE_NUMBER = "arg_page_number";
  static final String SAVE_PAGE_NUMBER = "save_page_number";
  
  int pageNumber;
  int backColor;
  
  static PageFragment newInstance(int page) {
    PageFragment pageFragment = new PageFragment();
    Bundle arguments = new Bundle();
    arguments.putInt(ARGUMENT_PAGE_NUMBER, page);
    pageFragment.setArguments(arguments);
    return pageFragment;
  }
  
  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    pageNumber = getArguments().getInt(ARGUMENT_PAGE_NUMBER);
    Log.d(TAG, "onCreate: " + pageNumber);
    
    Random rnd = new Random();
    backColor = Color.argb(40, rnd.nextInt(256), rnd.nextInt(256), rnd.nextInt(256));
    
    int savedPageNumber = -1;
    if (savedInstanceState != null) {
      savedPageNumber = savedInstanceState.getInt(SAVE_PAGE_NUMBER);
    }
    Log.d(TAG, "savedPageNumber = " + savedPageNumber);
    
  }
  
  @Override
  public View onCreateView(LayoutInflater inflater, ViewGroup container,
      Bundle savedInstanceState) {
    View view = inflater.inflate(R.layout.fragment, null);
    
    TextView tvPage = (TextView) view.findViewById(R.id.tvPage);
    tvPage.setText("Page " + pageNumber);
    tvPage.setBackgroundColor(backColor);
    
    return view;
  }
  
  @Override
  public void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    outState.putInt(SAVE_PAGE_NUMBER, pageNumber);
  }
  
  @Override
  public void onDestroy() {
    super.onDestroy();
    Log.d(TAG, "onDestroy: " + pageNumber);
  }
}

IN onCreate we added a log entry and an attempt to read the saved value from savedInstanceState.

IN onSaveInstanceState we store the page number.

IN onDestroy just write a log.

Thus, we will see whether the fragment is recreated and whether it can restore the stored values.

Save everything, launch the application.

In logs we see:

onCreate: 0
savedPageNumber = -1
onCreate: 1
savedPageNumber = -1

Zero and first page snippet created. Although only zero is visible now. The adapter reasonably pre-creates the next page to be displayed so that it does not have any brakes. Snippets are created for the first time, extract nothing from savedInstanceState, so see savedPageNumber = -1.

Scroll to the first page

In the logs:

onPageSelected, position = 1
onCreate: 2
savedPageNumber = -1

The first showed, the second created in advance.

Scroll through a couple more pages

onPageSelected, position = 2
onCreate: 3
savedPageNumber = -1
onPageSelected, position = 3
onCreate: 4
savedPageNumber = -1

Everything also remains. Note that onDestroy does not work. That is, all pages are not destroyed, but stored in memory.

We turn back.

onPageSelected, position = 2
onPageSelected, position = 1
onPageSelected, position = 0

Since there were no destruction, there are no creatures. Extracts pages from memory and shows.

We can conclude. This adapter is fast because it does not require reincarnation. But expendable because it holds everything in memory. That is suitable for a small number of pages. For example, a set of tabs or wizards.

The truth is a nuance: though the fragments themselves are not destroyed, but their View structure is destroyed and then recreated. Insert the log into the onCreateView method and make sure. Only the structure of the current page and one on the right and left are saved. This number of adjacent pages with a stored View structure can be set by the setOffscreenPageLimit method.

FragmentStatePagerAdapter

This adapter is similar to FragmentPagerAdapter, it also works with fragments. But it uses other mechanisms for working with pages. It does not store pages in memory, but creates them every time. Let’s use it. To do this, you need to MainActivity.java just replace the FragmentPagerAdapter with the FragmentStatePagerAdapter in the import section and in the MyFragmentPagerAdapter class description.

...
import android.support.v4.app.FragmentStatePagerAdapter;
...
private class MyFragmentPagerAdapter extends FragmentStatePagerAdapter {
...

That’s all. Save, run.

Zero page opens. In logs we see:

onCreate: 0
savedPageNumber = -1
onCreate: 1
savedPageNumber = -1

Scroll to the first page

onPageSelected, position = 1
onCreate: 2
savedPageNumber = -1

So far, the behavior is no different than the previous adapter.

Scroll to the second page.

onPageSelected, position = 2
onDestroy: 0
onCreate: 3
savedPageNumber = -1

That’s the difference. We see that the second page has also been opened, the third one has also been created, but the zero has been destroyed.

Scroll to the third:

onPageSelected, position = 3
onDestroy: 1
onCreate: 4
savedPageNumber = -1

The same situation: the third one appeared, the first one was destroyed, the fourth one was created.

Scroll back to the second:

onPageSelected, position = 2
onDestroy: 4
onCreate: 1
savedPageNumber = 1

The second one was displayed, the fourth one was deleted, the first one was created (and read the saved value from savedInstanceState).

That is, the adapter saves only the current page and one adjacent (right and left) so that you can quickly flip.

We can conclude. This adapter is not too fast, it will slow down when repeatedly swiped on both sides, as it is constantly converting pages. But it requires a minimum of memory. That is, it is suitable for a large number of pages. For example, browsing letters, sms, book pages.

Use the adapter that suits you best in this situation. Or, inherit the PagerAdapter class and create the adapter for your needs.

software rollover

To programmatically turn the ViewPager page, use the setCurrentItem method. And to get the current page, getCurrentItem.




Discuss in the forum [216 replies]

Leave a Comment