16

Android working with ViewPager2, TabLayout and Page Transformers

 3 years ago
source link: https://www.androidhive.info/2020/01/viewpager2-pager-transformations-intro-slider-pager-animations-pager-transformations/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Material Design

Android working with ViewPager2, TabLayout and Page Transformers

By Ravi Tamada January 21, 2020 19 Comments

As we all know, the Android team is constantly releasing new updates and improvements to the Android Framework. One of the components that received major updates is ViewPager2. ViewPager2 is the replacement of ViewPager and got few performance improvements and additional functionalities. In order to use ViewPager2, you should consider using androidx artifacts in your project.

In my earlier tutorial, I have explained Building Intro Sliders to your App using ViewPager. In this article, we are going to try the same but using ViewPager2. We’ll also go through the additional interesting functionalities provided by ViewPager2.

DEMO

Whats’s New in ViewPager2

  • ViewPager2 is improvised version of ViewPager that provides additional functionalities and addresses common issues faced in ViewPager.
  • ViewPager2 is built on top of RecyclerView. So you will have great advantages that a RecyclerView has. For example, you can take advantage of DiffUtils to efficiently calculate the difference between data sets and update the ViewPager with animations.
  • ViewPager2 supports vertical orientation. Earlier vertical swiping is done by customizing the ViewPager.
  • ViewPager2 has right-to-left(RTL) support and this will be enabled automatically based on app locale.
  • When working with a collection of Fragments, calling notifyDatasetChanged() will update the UI properly when an underlying Fragment changes its UI.
  • ViewPager2 supports page transformations i.e you can provide animations when switching between pages. You can also write your own custom page transformation.
  • PagerAdapter is replaced by RecyclerView.Adapter as its based on RecyclerView.
  • FragmentStatePagerAdapter is replaced by FragmentStateAdapter.

To get started with ViewPager2, add the dependency to app/build.gradle.

app/build.gradle
implementation "androidx.viewpager2:viewpager2:1.0.0"

1. ViewPager with Static Views

Usually, ViewPager is used to achieve a tabbed view by combining the TabLayout and collection of Fragments. But if you want to have static views without the Fragment class, you can do it using RecyclerView adapter class.

Below is an example of implementing Intro Slides using ViewPager2. The designs are taken from my older example How to Build Intro Slider for your App but the implementation part varies due to changes in ViewPager2 and in adapter class.

Add the ViewPager2 to your layout file. You can see namespace is uses androidx package androidx.viewpager2.widget.ViewPager2.

activity_views_slider.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/view_pager"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<RelativeLayout/>
ViewsSliderActivity.java
public class ViewsSliderActivity extends AppCompatActivity {
private ViewsSliderAdapter mAdapter;
private TextView[] dots;
private int[] layouts;
private ActivityViewsSliderBinding binding;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivityViewsSliderBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
init();
}
private void init() {
// layouts of all welcome sliders
// add few more layouts if you want
layouts = new int[]{
R.layout.slide_one,
R.layout.slide_two,
R.layout.slide_three,
R.layout.slide_four};
mAdapter = new ViewsSliderAdapter();
binding.viewPager.setAdapter(mAdapter);
binding.viewPager.registerOnPageChangeCallback(pageChangeCallback);
binding.btnSkip.setOnClickListener(v -> launchHomeScreen());
binding.btnNext.setOnClickListener(v -> {
// checking for last page
// if last page home screen will be launched
int current = getItem(+1);
if (current < layouts.length) {
// move to next screen
binding.viewPager.setCurrentItem(current);
} else {
launchHomeScreen();
}
});
binding.iconMore.setOnClickListener(view -> {
showMenu(view);
});
// adding bottom dots
addBottomDots(0);
}
/*
* Adds bottom dots indicator
* */
private void addBottomDots(int currentPage) {
dots = new TextView[layouts.length];
int[] colorsActive = getResources().getIntArray(R.array.array_dot_active);
int[] colorsInactive = getResources().getIntArray(R.array.array_dot_inactive);
binding.layoutDots.removeAllViews();
for (int i = 0; i < dots.length; i++) {
dots[i] = new TextView(this);
dots[i].setText(Html.fromHtml("•"));
dots[i].setTextSize(35);
dots[i].setTextColor(colorsInactive[currentPage]);
binding.layoutDots.addView(dots[i]);
}
if (dots.length > 0)
dots[currentPage].setTextColor(colorsActive[currentPage]);
}
private int getItem(int i) {
return binding.viewPager.getCurrentItem() + i;
}
private void launchHomeScreen() {
Toast.makeText(this, R.string.slides_ended, Toast.LENGTH_LONG).show();
finish();
}
private void showMenu(View view) {
PopupMenu popup = new PopupMenu(this, view);
MenuInflater inflater = popup.getMenuInflater();
inflater.inflate(R.menu.pager_transformers, popup.getMenu());
popup.setOnMenuItemClickListener(item -> {
if (item.getItemId() == R.id.action_orientation) {
binding.viewPager.setOrientation(ViewPager2.ORIENTATION_VERTICAL);
} else {
binding.viewPager.setPageTransformer(Utils.getTransformer(item.getItemId()));
binding.viewPager.setCurrentItem(0);
binding.viewPager.getAdapter().notifyDataSetChanged();
}
return false;
});
popup.show();
}
/*
* ViewPager page change listener
*/
ViewPager2.OnPageChangeCallback pageChangeCallback = new ViewPager2.OnPageChangeCallback() {
@Override
public void onPageSelected(int position) {
super.onPageSelected(position);
addBottomDots(position);
// changing the next button text 'NEXT' / 'GOT IT'
if (position == layouts.length - 1) {
// last page. make button text to GOT IT
binding.btnNext.setText(getString(R.string.start));
binding.btnSkip.setVisibility(View.GONE);
} else {
// still pages are left
binding.btnNext.setText(getString(R.string.next));
binding.btnSkip.setVisibility(View.VISIBLE);
}
}
};
public class ViewsSliderAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
public ViewsSliderAdapter() {
}
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(viewType, parent, false);
return new SliderViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
}
@Override
public int getItemViewType(int position) {
return layouts[position];
}
@Override
public int getItemCount() {
return layouts.length;
}
public class SliderViewHolder extends RecyclerView.ViewHolder {
public TextView title, year, genre;
public SliderViewHolder(View view) {
super(view);
}
}
}
}

This creates horizontally swipeable views that are created using static XML layouts. The full code of this example can be found here.

2. ViewPager with Tabs & Fragments

If you want to create swipeable views with Tabs, you can combine ViewPager2, TabLayout and Fragments.

To use TabLayout, add Material Components to your package. This makes the material TabLayout available in your project.

app/build.gradle
implementation 'com.google.android.material:material:1.2.0-alpha01'

Add TabLayout and ViewPage2 to your layout.

activity_fragment_view_pager.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".fragments.FragmentViewPagerActivity">
<com.google.android.material.tabs.TabLayout
android:id="@+id/tab_layout"
style="@style/Widget.MaterialComponents.TabLayout.Colored"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/view_pager"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
</LinearLayout>

Create required test Fragment classes MoviesFragment, EventsFragment and TicketsFragment.

MoviesFragment.java
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.fragment.app.Fragment;
import info.androidhive.viewpager2.R;
public class MoviesFragment extends Fragment {
@Override
public View onCreateView(
LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState
) {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_movies, container, false);
}
}

Finally, create an adapter class that provides Fragments to ViewPager. Here we can see ViewPagerFragmentAdapter extends FragmentStateAdapter.

FragmentViewPagerActivity.java
package info.androidhive.viewpager2.fragments;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import androidx.viewpager2.adapter.FragmentStateAdapter;
import com.google.android.material.tabs.TabLayoutMediator;
import info.androidhive.viewpager2.databinding.ActivityFragmentViewPagerBinding;
public class FragmentViewPagerActivity extends AppCompatActivity {
ActivityFragmentViewPagerBinding binding;
// tab titles
private String[] titles = new String[]{"Movies", "Events", "Tickets"};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivityFragmentViewPagerBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
init();
}
private void init() {
// removing toolbar elevation
getSupportActionBar().setElevation(0);
binding.viewPager.setAdapter(new ViewPagerFragmentAdapter(this));
// attaching tab mediator
new TabLayoutMediator(binding.tabLayout, binding.viewPager,
(tab, position) -> tab.setText(titles[position])).attach();
}
private class ViewPagerFragmentAdapter extends FragmentStateAdapter {
public ViewPagerFragmentAdapter(@NonNull FragmentActivity fragmentActivity) {
super(fragmentActivity);
}
@NonNull
@Override
public Fragment createFragment(int position) {
switch (position) {
case 0:
return new MoviesFragment();
case 1:
return new EventsFragment();
case 2:
return new TicketsFragment();
}
return new MoviesFragment();
}
@Override
public int getItemCount() {
return titles.length;
}
}
}

3. ViewPager2 Orientation

In a few scenarios, you might want to provide vertical swiping instead of traditional horizontal swiping. To enable vertical swiping, add android:orientation to ViewPager2 element.

<androidx.viewpager2.widget.ViewPager2
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content" />

Vertical orientation can also be enabled programmatically by calling setOrientation() method.

viewPager.setOrientation(ViewPager2.ORIENTATION_VERTICAL);

4. ViewPager Transformers

Another great feature of ViewPager2 is, page transformations i.e the page transition animation from one page to another. Traditionally, we see a sliding animation between two screens. This animation can be customized by providing page transformers to ViewPage2.

To set a custom transformation, use setPageTransformer() method. Below example creates Flip animation between pages.

HorizontalFlipTransformation.java
package info.androidhive.viewpager2.transformers;
import android.view.View;
import androidx.viewpager2.widget.ViewPager2;
public class HorizontalFlipTransformation implements ViewPager2.PageTransformer {
@Override
public void transformPage(View page, float position) {
page.setTranslationX(-position * page.getWidth());
page.setCameraDistance(12000);
if (position < 0.5 && position > -0.5) {
page.setVisibility(View.VISIBLE);
} else {
page.setVisibility(View.INVISIBLE);
}
if (position < -1) {     // [-Infinity,-1)
page.setAlpha(0);
} else if (position <= 0) {    // [-1,0]
page.setAlpha(1);
page.setRotationY(180 * (1 - Math.abs(position) + 1));
} else if (position <= 1) {    // (0,1]
page.setAlpha(1);
page.setRotationY(-180 * (1 - Math.abs(position) + 1));
} else {
page.setAlpha(0);
}
}
}

This creates beautiful vertical flip animation when swiping the pages.

Below are a set of ViewPager transitions that I have collected from different sources (All the credits goes to original authors).

Transformations References:
Viewpager-Transformation
Loginworks

Hi there! I am Founder at androidhive and programming enthusiast. My skills includes Android, iOS, PHP, Ruby on Rails and lot more. If you have any idea that you would want me to develop? Let’s talk: [email protected]


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK