Слайдинг экранов с помощью ViewPager

    Вступление

    Горизонтальная прокрутка экранов — удобная и модная вещь. На Хабре уже был топик, посвященный данной теме. Однако использование ViewFlipper не позволяет с легкостью добиться эффекта привязки, когда экраны двигаются вместе с пальцем. Также нужен механизм автоматической доводки экранов в ту или иную сторону. Примера, где это было бы хорошо разъяснено и реализовано, так и не нашел. В комментариях предлагали посмотреть исходники гугловского кода, где это реализовано. Однако удобнее было бы воспользоваться готовым решением. Такой механизм реализован в Android Support Package. Использовать эту библиотеку можно для версий Android 1.6 и старше. Конкретно нам понадобятся классы ViewPager и PagerAdapter.

    Предварительная настройка

    Чтобы подключить библиотеку к проекту, выполняем следующие действия.
    Идем в директорию, куда установлен Android SDK, запускаем SDK Manager и в списке доступных к установке компонентов выбираем Android Support package. Устанавливаем его. Все что нужно будет находиться в следующей директории: <директория с sdk>/extras/android/support/. В папке v4 будет лежать .jar файл, который и нужно подключить к проекту. Если вы работаете в Eclipse, то в Package Explorer щелкаем правым кликом по проекту, выбираем Build Path -> Add External Archives. В появившемся диалоговом окне переходим к скачанному .jar файлу, выбираем его и щелкаем OK. Всё, библиотека подключена к проекту и готова к использованию.
    Хочется заметить, что могут возникнуть проблемы, если вы используете старую версию SDK Manager. У меня указанный Support Package не появлялся в списке доступных загрузок. После обновления SDK проблема решилась.

    Разработка

    Выполнив инструкции, приведенные по указанной выше ссылке, и установив библиотеку, можно приступать к реализации поставленной задачи.
    ViewPager по принципу работы похож на ListView. Данные для отображения он берет из PagerAdapter. Нам потребуется унаследовать свой класс от PagerAdapter и реализовать в нем некоторые методы. Добавление и удаление экранов реализуется с помощью методов instantiateItem() и destroyItem() соответственно. View для отображения можно создавать прямо в адаптере. Такой подход хорош тем, что ViewPager можно настраивать так, чтобы в адаптере не хранились все экраны сразу. По умолчанию адаптер хранит текущий экран, и по одному слева и справа от него. Это может сэкономить память, если содержание экранов слишком сложное. Однако в данном примере создадим все экраны в activity приложения, добавим их в список, и передадим затем этот список адаптеру. Адаптер же не будет сам создавать view, а будет брать их их списка.
    Код адаптера получился следующим.
    public class SamplePagerAdapter extends PagerAdapter{
    	
    	List<View> pages = null;
    	
    	public SamplePagerAdapter(List<View> pages){
    		this.pages = pages;
    	}
    	
    	@Override
    	public Object instantiateItem(View collection, int position){
    		View v = pages.get(position);
    		((ViewPager) collection).addView(v, 0);
    		return v;
    	}
    	
    	@Override
    	public void destroyItem(View collection, int position, Object view){
    		((ViewPager) collection).removeView((View) view);
    	}
    	
    	@Override
    	public int getCount(){
    		return pages.size();
    	}
    	
    	@Override
    	public boolean isViewFromObject(View view, Object object){
    		return view.equals(object);
    	}
    
    	@Override
    	public void finishUpdate(View arg0){
    	}
    
    	@Override
    	public void restoreState(Parcelable arg0, ClassLoader arg1){
    	}
    
    	@Override
    	public Parcelable saveState(){
    		return null;
    	}
    
    	@Override
    	public void startUpdate(View arg0){
    	}
    }
    

    В адаптере нам потребовалось определить свою реализацию для методов instantiateItem(), destroyItem(), getCount() и isViewFromObject(). Для остальных методов тела можно оставить пустыми.

    Создадим файл разметки page.xml и опишем в нем содержимое экрана следующим образом:
    <?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:padding="10dip"
        android:background="#ddd">
    
        <TextView
            android:id="@+id/text_view"
            android:layout_width="fill_parent"
            android:layout_height="fill_parent"
            android:layout_centerInParent="true"
            android:gravity="center"
            android:background="#888"
            android:textColor="#fff"
            android:textSize="30dip"/>
    </RelativeLayout>
    

    В activity приложения, как было указано выше, создадим несколько View, которые и будут нашими экранами, добавим их в список и передадим список адаптеру. Затем создадим ViewPager и установим для него полученный адаптер.

    Код activity:
    public class ViewPagerSampleActivity extends Activity {
    	/** Called when the activity is first created. */
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            
            LayoutInflater inflater = LayoutInflater.from(this);
            List<View> pages = new ArrayList<View>();
            
            View page = inflater.inflate(R.layout.page, null);
            TextView textView = (TextView) page.findViewById(R.id.text_view);
            textView.setText("Страница 1");
            pages.add(page);
            
            page = inflater.inflate(R.layout.page, null);
            textView = (TextView) page.findViewById(R.id.text_view);
            textView.setText("Страница 2");
            pages.add(page);
            
            page = inflater.inflate(R.layout.page, null);
            textView = (TextView) page.findViewById(R.id.text_view);
            textView.setText("Страница 3");
            pages.add(page);
            
            SamplePagerAdapter pagerAdapter = new SamplePagerAdapter(pages);
            ViewPager viewPager = new ViewPager(this);
            viewPager.setAdapter(pagerAdapter);
            viewPager.setCurrentItem(1);     
            
            setContentView(viewPager);
        }
    }
    

    Как видно из кода, мы создали три экрана. Для каждого из них мы указали какой он по счету. Установив адаптер для ViewPager, укажем что сейчас будет показываться экран с индексом 1, т.е. на котором написано «Страница 2».
    Скриншот приложения, во время прокручивания экрана:


    Заключение

    Как видно из примера, использовать ViewPager и PagerAdapter достаточно легко. При слайдинге экранов, изображения привязывается к положению пальца. Работает автоматическая доводка в какую либо из сторон. Нажатия на дочерние элементы (различные кнопки, элементы списка, если таковой присутствует) обрабатываются корректно.

    И в заключение, ссылка на статью, в которой рассказывается о ViewPager.
    Поделиться публикацией
    Комментарии 26
      0
      а можете поподробнее рассказать где взять библиотеку и как ее установить?
        +3
        Добавил в начало статьи краткую инструкцию по установке.
        +1
          0
          Не так давно реализовывал табы со слайдингом(а ля приложение Google Music 4.0), и использовал как раз этот прием, только с фрагментами в качестве содержимого. Самая мутная часть была с организацией переключалки табов)
            0
            А можно ли на основе этого ViewPager реализовать «бесконечный» слайдинг страниц? Чтобы они создавались в процессе скролла? Или сразу рассчитывается количество по getCount?
              0
              По идее мы можем динамически добавлять. Кстати на деле достаточно 3(или 2) страницы с динамическим контентом, я думаю. Только нужно реализовать правильный адаптер, который обрабатывает переключение.
                0
                Видимо можно. Попробовал сделать добавление в список новых элементов при нажатии на экран — работает. То есть адаптером корректно подхватываются новые элементы списка. Динамическое удаление страниц тоже работает.
                  0
                  Спасибо. После работы попробую
                0
                Не смотрели, как этот механизм реализован в FBReaderJ?
                  0
                  Нет, не смотрел.
                  0
                  Спасибо, пригодится!
                    0
                    а как зафиксировать один View, чтобы он не перемещался при слайдинге? например элемент Button или ImageView
                      +1
                      Можно описать в файле разметки элементы так, чтобы ViewPager был лишь одним из компонентов. Например, создать такой файл main_layout.xml:
                      <LinearLayout
                      	xmlns:android="http://schemas.android.com/apk/res/android"
                      	android:orientation="vertical"
                      	android:layout_width="fill_parent"
                      	android:layout_height="fill_parent">
                      	
                      	<ImageView
                      		android:id="@+id/image"
                      		android:layout_width="wrap_content"
                      		android:layout_height="wrap_content"
                      		android:src="@drawable/icon"/>
                      	
                      	<android.support.v4.view.ViewPager
                      		android:id="@+id/view_pager"
                      		android:layout_width="wrap_content"
                      		android:layout_height="wrap_content">
                      	</android.support.v4.view.ViewPager>
                      	
                      </LinearLayout>
                      

                      Затем в activity приложения делаем setContentView(R.layout.main_layout), а экземпляр ViewPager добываем с помощью findViewById(R.id.view_pager).
                      Указанная картинка будет находиться наверху экрана, а страницы, которые скроллятся с помощью ViewPager будут располагаться ниже.
                      0
                      спвсибо, попробуем.
                        0
                        Класс, все получилось. Подскажи, а как добавить заголовки страниц, по типу как это сделано в маркете или гугл+?
                          0
                          С заголовками, видимо, посложнее будет. Сам такое не реализовывал, так что, к сожалению, подсказать не могу.
                            0
                            жаль (( те способы что удалось нагуглить простыми не назвать, видимо придется углубляться
                              0
                              И сам же себя опровергну :)
                              Нашлась зааамечательная библиотека с примером — github.com/JakeWharton/Android-ViewPagerIndicator.
                              Заголовки для ViewPager почти как в Google+.
                              Анимация немного другая, но мне даже больше нравится, ну и да — создание экранов в ViewPager очень отличается от представленного здесь способа, хотя надо покумекать, может и удастся объединить :)
                          0
                          Я использовал для этого HorizontalPager
                          code.google.com/p/deezapps-widgets/

                          Мне очень понравилось что для его использования вообще не надо писать java кода, только задать разметку в XML:

                          вот весь код чтобы сделать две перелистываемые странички. Каждая страница в примере это TextView, но можно заменить на свой Layout.

                          <com.deezapps.widget.HorizontalPager
                          android:layout_width=«fill_parent»
                          android:layout_height=«wrap_content»>

                          <TextView android:text=«Text 1»/>
                          <TextView android:text=«Text 2»/>

                          </com.deezapps.widget.HorizontalPager>

                          Библиотек подключать не надо, весь код идет одним Java файлом.
                            0
                            А можно пример полностью, просто у меня не распознает com.deezapps.widget.HorizontalPager
                              0
                              Сначала не туда поместил пост :)

                              Конечно.
                              На сайте проекта есть ссылка на SVN:
                              code.google.com/p/deezapps-widgets/source/checkout
                              Можно скачать там.
                              В этом проекте есть необходимые исходники и как раз показаны основные варианты исползьования.
                              Необходимые исходники это файлы attrs.xml и HorizontalPager.java ( может быть еще нужен PagerControl.java, но в своем проекте я обошелся без него)
                              Остальные файлы нужны для демонстрации возможностей
                            0
                            Конечно.
                            На сайте проекта есть ссылка на SVN:
                            code.google.com/p/deezapps-widgets/source/checkout
                            Можно скачать там.
                            В этом проекте есть необходимые исходники и как раз показаны основные варианты исползьования.
                            Необходимые исходники это файлы attrs.xml и HorizontalPager.java ( может быть еще нужен PagerControl.java, но в своем проекте я обошелся без него)
                            Остальные файлы нужны для демонстрации возможностей
                              0
                              Добрый день! А как можно сделать превью под ViewPager?
                              Например, есть главный LinearLayout, в нём сверху заголовок, ниже ViewPager(3 страницы). Для каждой страницы свой маленький thumb. Необходимо, чтобы под ViewPager (можно и внутри) отображались thumbs для всех 3 страниц, но подсвечена и по центру только одна(для текущей страницы).
                              Интересует 2 вещи:
                              1) Можно ли это сделать, используя только ViewPager и его проперти/методы?
                              2) Если делать отдельными View, то что лучше использовать? Ещё один ViewPager, в котором менять страницы после завершения прокрутки?
                                0
                                Здравствуйте. Если вы говорите о простой индикации, какая страница сейчас открыта — то да, это сделать можно. В 6 версии Support Package появилась возможность указывать заголовок для страниц (с помощью класса PagerTitleStrip). Кроме того в комментариях уже была ссылка на неплохую библиотеку, реализующую этот функционал — github.com/JakeWharton/Android-ViewPagerIndicator
                                Вам это нужно, или что-то другое?
                                  +1
                                  Сначала промахнулся, не привык ещё =)

                                  Да, спасибо! Сам недавно нашёл эту библиотечку, сижу разбираюсь.
                                0
                                Да, спасибо! Сам недавно нашёл эту библиотечку, сижу разбираюсь.

                                Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                                Самое читаемое