betacode

Руководство Android ViewPager2

  1. Android ViewPager2 vs ViewPager
  2. Example: ViewPager2
  3. Example: Transformation

1. Android ViewPager2 vs ViewPager

Android ViewPager это компонент интерфейса представленный в библиотеке поддержки Android, он позволяет пользователю свайпать (swipe) на лево или на право, чтобы посмтреть абсолютно новую страницу (экрана).
ViewPager/ViewPager2
Один ViewPager/ViewPager2 где каждая страница заполняет экран, пользователь может свайпать (swipe) на лево, чтобы перейти к следующей странице или на право, чтобы вернуться к предыдущей.
ViewPager/ViewPager2 может увидеть на приложении по обучению иностранного языка Duolingo.
ViewPager2 vs ViewPager
Как мы уже знаем, команда Android постоянно выпускает новые обновления и улучшения для Android Framework. Один из компонентов получивших большие обновления это ViewPager2. ViewPager2 это замена ViewPager и имеет некоторые улучшения по производительности и дополнительных функций.
Так что эе нового в ViewPager2?
  • ViewPager2 это импровизированная версия у ViewPager предоставляющая дополнительные функции и разрешает часто встречаемые проблемы в ViewPager.
  • ViewPager2 построен наRecyclerView. Поэтому у вас есть прекрасные преимущества, что есть у RecyclerView. Например: вы можете воспользоваться DiffUtils чтобыы эффектвно рассчитать разницу между наборами данных и обновлять ViewPager с анимациями.
  • ViewPager2 поддерживает вертикальное направление (Vertical Orientation). Если использовать ViewPager, вам нужно написать дополнительные кастомизации для ViewPager чтобы получить одинаковые результаты.
  • ViewPager2 поддерживает Right-to-Left (RTL) и это будет автоматически включено на основании App Locale.
  • При работе с набором Fragment. Если один из Fragment меняет свой интерфейс, вам нужно только вызвать метод notifyDatasetChanged() чтобы обновить интерфейс приложения правильным способом.
  • ViewPager2 поддерживает трансформацию (transformation) страницы, тое есть вы можете предоставить анимации при переключении между страницами. Вы так же можете написать собственный кастомизированный PageTransformer.
  • PagerAdapter заменен на RecyclerView.
  • FragmentStatePagerAdapter заменен на FragmentStateAdapter.
Library
ViewPager2 это компонент не имеющийся наготове в стандартной библиотеке Android, поэтому если вы хотите использовать его, вам нужно его установить в ваш project.
Вы можете установить ViewPager2 из Palette окна дизайна.
После установки, вы увидите библиотеку ViewPager2 объявленную в build.gradle (Module: app).
implementation 'androidx.viewpager2:viewpager2:1.0.0'

2. Example: ViewPager2

Теперь мы выполним пример ViewPager2 и используем Fragment в качестве схемы для каждой страницы. Ниже является изображение для просмотра примера:
  • Файл fragment_employee_page.xml это схема интерфейса страницы (Page).
  • Класс EmployeePageFragment содержит данные объекта Employee и отображает их на одной странице (Page).
На Android Studio создайте новый project:
  • File > New > New Project > Empty Activity
    • Name: ViewPager2Example
    • Package name: org.o7planning.viewpager2example
    • Language: Java
Как было сказано выше ViewPager2 это компонент не имеющийся в стандартной библиотеке Android, поэтому следующий шаг это вам нужно установить его в ваш project из Palette окна дизайна.
И создайте Fragment (EmployeePageFragment), он соответствует странице (Page) ViewPager2:
  • File > New > Fragment > Fragment (Blank)
  • Fragment Name: EmployeePageFragment
  • Fragment Layout Name: fragment_page_employee
  • Source Language: Java
Теперь вы увидите два созданных файла:
Откройте файл fragment_employee_page.xml чтобы создать дизайн интерфейса.
fragment_employee_page.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/textView_fullName"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginLeft="16dp"
        android:layout_marginTop="30dp"
        android:layout_marginEnd="16dp"
        android:layout_marginRight="16dp"
        android:background="#C4CFB9"
        android:gravity="center_horizontal"
        android:text="Fullname"
        android:textSize="22sp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/textView71"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginLeft="16dp"
        android:layout_marginTop="16dp"
        android:text="Position:"
        android:textStyle="bold"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/textView_fullName" />

    <TextView
        android:id="@+id/textView_position"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="8dp"
        android:layout_marginLeft="8dp"
        android:layout_marginTop="16dp"
        android:layout_marginEnd="16dp"
        android:layout_marginRight="16dp"
        android:text="(Position)"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toEndOf="@+id/textView71"
        app:layout_constraintTop_toBottomOf="@+id/textView_fullName" />

    <TextView
        android:id="@+id/textView72"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginLeft="16dp"
        android:layout_marginTop="16dp"
        android:text="Email:"
        android:textStyle="bold"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/textView71" />

    <TextView
        android:id="@+id/textView_email"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="8dp"
        android:layout_marginLeft="8dp"
        android:layout_marginTop="16dp"
        android:layout_marginEnd="16dp"
        android:layout_marginRight="16dp"
        android:text="(Email)"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toEndOf="@+id/textView72"
        app:layout_constraintTop_toBottomOf="@+id/textView71" />

</androidx.constraintlayout.widget.ConstraintLayout>
EmployeePageFragment.java
package org.o7planning.viewpager2example;

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

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;

public class EmployeePageFragment  extends Fragment {

    private static final String LOG_TAG = "AndroidExample";

    private Employee employee;

    private TextView textViewEmail;
    private TextView textViewPosition;
    private TextView textViewFullName;

    private static int counter = 0;

    // IMPORTANT:
    // Required default public constructor.
    // If configuration change.
    // For example: User rotate the Phone,
    //  Android will create new Fragment (EmployeePageFragment) via default Constructor
    //  so this.employee will be null.
    public EmployeePageFragment() {

    }

    public EmployeePageFragment(Employee employee) {
        this.employee = employee;
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        View view = (ViewGroup) inflater.inflate(
                R.layout.fragment_employee_page, container, false);

        counter++;
        if(counter % 2 == 0) {
            view.setBackgroundColor(Color.parseColor("#ebdef0"));
        } else  {
            view.setBackgroundColor(Color.parseColor("#e8f8f5"));
        }

        this.textViewFullName = view.findViewById(R.id.textView_fullName);
        this.textViewPosition = view.findViewById(R.id.textView_position);
        this.textViewEmail = view.findViewById(R.id.textView_email);

        return view;
    }


    // Called when configuration change.
    // For example: User rotate the Phone,
    //  Android will create new Fragment (EmployeePageFragment) object via default Constructor
    //  so this.employee will be null.
    @Override
    public void onSaveInstanceState(@NonNull Bundle outState) {
        Log.i(LOG_TAG, "onSaveInstanceState: save employee data to Bundle");
        // Convert employee object to Bundle.
        Bundle dataBundle = this.employeeToBundle(this.employee);
        outState.putAll(dataBundle);

        super.onSaveInstanceState(outState);
    }


    @Override
    public void onViewStateRestored(@Nullable Bundle savedInstanceState) {
        Log.i(LOG_TAG, "onViewStateRestored");

        super.onViewStateRestored(savedInstanceState);
    }

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        Log.i(LOG_TAG, "onViewCreated");
        super.onViewCreated(view, savedInstanceState);

        if(this.employee == null)  {
            Log.i(LOG_TAG, "Get employee data from savedInstanceState");
            // The state was saved by onSaveInstanceState(Bundle outState) method.
            this.employee = this.bundleToEmployee(savedInstanceState);
        }
        this.showInGUI(this.employee);
    }

    // Call where View ready.
    private void showInGUI(Employee employee)  {
        this.textViewFullName.setText(employee.getFullName());
        this.textViewPosition.setText(employee.getPosition());
        this.textViewEmail.setText(employee.getEmail());
    }

    private Bundle employeeToBundle(Employee employee)  {
        Bundle bundle = new Bundle();
        bundle.putString("fullName", employee.getFullName());
        bundle.putString("position", employee.getPosition());
        bundle.putString("email", employee.getEmail());

        return bundle;
    }

    private Employee bundleToEmployee(Bundle savedInstanceState) {
        String fullName = savedInstanceState.getString("fullName");
        String position = savedInstanceState.getString("position");
        String email = savedInstanceState.getString("email");
        return new Employee(fullName, email, position);
    }

}
EmployeeFragmentStateAdapter.java
package org.o7planning.viewpager2example;

import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import androidx.viewpager2.adapter.FragmentStateAdapter;

import java.util.ArrayList;
import java.util.List;

public class EmployeeFragmentStateAdapter extends FragmentStateAdapter {

    private List<Employee> employees;


    public EmployeeFragmentStateAdapter(@NonNull FragmentActivity fragmentActivity) {
        super(fragmentActivity);

        this.employees = this.intDatas();
    }

    private List<Employee> intDatas()  {
        Employee emp1 = new Employee("James Smith", "jamessmith@example.com", "Web Designer");
        Employee emp2 = new Employee("Elizabeth Johnson", "elizabethjohnson@example.com", "Project Manager");
        Employee emp3 = new Employee("Catherine Johnson", "catherinejohnson@example.com", "President of Sales");

        List<Employee> list = new ArrayList<Employee>();
        list.add(emp1);
        list.add(emp2);
        list.add(emp3);
        return list;
    }

    @NonNull
    @Override
    public Fragment createFragment(int position) {
        Employee employee = this.employees.get(position);
        return new EmployeePageFragment(employee);
    }


    @Override
    public int getItemCount() {
        return this.employees.size();
    }
}
Employee.java
package org.o7planning.viewpager2example;

public class Employee {

    private String fullName;
    private String email;
    private String position;

    public Employee(String fullName, String email, String position) {
        this.fullName = fullName;
        this.email = email;
        this.position = position;
    }

    public String getFullName() {
        return fullName;
    }

    public void setFullName(String fullName) {
        this.fullName = fullName;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public String getPosition() {
        return position;
    }

    public void setPosition(String position) {
        this.position = position;
    }

}
Главный интерфейс приложения:
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <androidx.viewpager2.widget.ViewPager2
        android:id="@+id/viewPager2_employee"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout_marginStart="8dp"
        android:layout_marginLeft="8dp"
        android:layout_marginTop="8dp"
        android:layout_marginEnd="8dp"
        android:layout_marginRight="8dp"
        android:layout_marginBottom="8dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
MainActivity.java
package org.o7planning.viewpager2example;

import androidx.appcompat.app.AppCompatActivity;
import androidx.viewpager2.widget.ViewPager2;

import android.os.Bundle;

public class MainActivity extends AppCompatActivity {

    private ViewPager2 viewPager2Employee;

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

        this.viewPager2Employee = findViewById(R.id.viewPager2_employee);

        // Employee FragmentStateAdapter.
        EmployeeFragmentStateAdapter adapter = new EmployeeFragmentStateAdapter(this);
        this.viewPager2Employee.setAdapter(adapter);
    }

}

3. Example: Transformation

Чтобы отобразить анимационный эффект, когда пользователь переключает между страницами, вам нужно написать класс, который применяет (implements) interface ViewPager2.PageTransformer и предоставляет его для объекта ViewPager2.
ViewPager2 viewPager = findViewById(R.id.pager);
...
viewPager.setPageTransformer(new YourPageTransformer());
Interface ViewPager2.PageTransformer имеется только в одном методе transformPage(), который вызывается один раз для каждого перехода (transition).
/**
 * Apply a property transformation to the given page.
 *
 * @param page Apply the transformation to this page
 * @param position Position of page relative to the current front-and-center
 *                 position of the pager. 0 is front and center. 1 is one full
 *                 page position to the right, and -2 is two pages to the left.
 *                 Minimum / maximum observed values depend on how many pages we keep
 *                 attached, which depends on offscreenPageLimit.
 *
 * @see #setOffscreenPageLimit(int)
 */
void transformPage(@NonNull View page, float position);
Прдолжаем с примером выше, мы добавим к нему анимационный эффект.
Zoom Out Page Transformer
ZoomOutPageTransformer.java
package org.o7planning.viewpager2example.transformer;

import android.view.View;

import androidx.viewpager2.widget.ViewPager2;

public class ZoomOutPageTransformer implements ViewPager2.PageTransformer {
    private static final float MIN_SCALE = 0.85f;
    private static final float MIN_ALPHA = 0.5f;

    public void transformPage(View view, float position) {
        int pageWidth = view.getWidth();
        int pageHeight = view.getHeight();

        if (position < -1) { // [-Infinity,-1)
            // This page is way off-screen to the left.
            view.setAlpha(0f);

        } else if (position <= 1) { // [-1,1]
            // Modify the default slide transition to shrink the page as well
            float scaleFactor = Math.max(MIN_SCALE, 1 - Math.abs(position));
            float vertMargin = pageHeight * (1 - scaleFactor) / 2;
            float horzMargin = pageWidth * (1 - scaleFactor) / 2;
            if (position < 0) {
                view.setTranslationX(horzMargin - vertMargin / 2);
            } else {
                view.setTranslationX(-horzMargin + vertMargin / 2);
            }

            // Scale the page down (between MIN_SCALE and 1)
            view.setScaleX(scaleFactor);
            view.setScaleY(scaleFactor);

            // Fade the page relative to its size.
            view.setAlpha(MIN_ALPHA +
                    (scaleFactor - MIN_SCALE) /
                            (1 - MIN_SCALE) * (1 - MIN_ALPHA));

        } else { // (1,+Infinity]
            // This page is way off-screen to the right.
            view.setAlpha(0f);
        }
    }
}
MainActivity.java (Zoom Out Page Transformer)
package org.o7planning.viewpager2example;

import androidx.appcompat.app.AppCompatActivity;
import androidx.viewpager2.widget.ViewPager2;

import android.os.Bundle;
import org.o7planning.viewpager2example.transformer.ZoomOutPageTransformer;

public class MainActivity extends AppCompatActivity {

    private ViewPager2 viewPager2Employee;

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

        this.viewPager2Employee = findViewById(R.id.viewPager2_employee);

        // Employee FragmentStateAdapter.
        EmployeeFragmentStateAdapter adapter = new EmployeeFragmentStateAdapter(this);
        this.viewPager2Employee.setAdapter(adapter);

        // PageTransformer
        this.viewPager2Employee.setPageTransformer(new ZoomOutPageTransformer());

    }
}
Depth Page Transformer
DepthPageTransformer.java
package org.o7planning.viewpager2example.transformer;

import android.view.View;

import androidx.annotation.RequiresApi;
import androidx.viewpager2.widget.ViewPager2;

@RequiresApi(21)
public class DepthPageTransformer implements ViewPager2.PageTransformer {
    private static final float MIN_SCALE = 0.75f;

    public void transformPage(View view, float position) {
        int pageWidth = view.getWidth();

        if (position < -1) { // [-Infinity,-1)
            // This page is way off-screen to the left.
            view.setAlpha(0f);

        } else if (position <= 0) { // [-1,0]
            // Use the default slide transition when moving to the left page
            view.setAlpha(1f);
            view.setTranslationX(0f);
            view.setTranslationZ(0f);
            view.setScaleX(1f);
            view.setScaleY(1f);

        } else if (position <= 1) { // (0,1]
            // Fade the page out.
            view.setAlpha(1 - position);

            // Counteract the default slide transition
            view.setTranslationX(pageWidth * -position);
            // Move it behind the left page
            view.setTranslationZ(-1f);

            // Scale the page down (between MIN_SCALE and 1)
            float scaleFactor = MIN_SCALE
                    + (1 - MIN_SCALE) * (1 - Math.abs(position));
            view.setScaleX(scaleFactor);
            view.setScaleY(scaleFactor);

        } else { // (1,+Infinity]
            // This page is way off-screen to the right.
            view.setAlpha(0f);
        }
    }
}
MainActivity.java (Depth Page Transformer)
package org.o7planning.viewpager2example;

import androidx.appcompat.app.AppCompatActivity;
import androidx.viewpager2.widget.ViewPager2;

import android.os.Build;
import android.os.Bundle;

import org.o7planning.viewpager2example.transformer.DepthPageTransformer;

public class MainActivity extends AppCompatActivity {

    private ViewPager2 viewPager2Employee;

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

        this.viewPager2Employee = findViewById(R.id.viewPager2_employee);

        // Employee FragmentStateAdapter.
        EmployeeFragmentStateAdapter adapter = new EmployeeFragmentStateAdapter(this);
        this.viewPager2Employee.setAdapter(adapter);

        // PageTransformer  
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { // Api 21+.
           this.viewPager2Employee.setPageTransformer(new DepthPageTransformer());
        }
    }

}

Pуководства Android

Show More