Android嵌套式ViewPager,垂直ViewPager
在本教程中,我们将实现android嵌套的ViewPager,即ViewPager中的ViewPager。
另外,我们还将讨论和实现Vertical ViewPager。
我们将开发一个应用程序,该应用程序将复制Inshorts和Flipboard等应用程序中存在的滑动功能。
Android巢状ViewPager
如果您使用了上面提到的应用程序,您将已经意识到,只需向上或者向下滑动当前页面即可浏览不同内容。
用于创建此类功能的基础视图是ViewPager。
简而言之,ViewPagers使用PagerAdapter将页面(通常以Fragment视图的形式)添加到我们的ViewPager。
默认情况下," ViewPager"具有内置的滑动手势,可以向左或者向右滑动。
在ViewPager上滚动时会调用一个名为" PageTransformer"的接口。
" PagerTransform"接口包含以下可以覆盖和实现的公共方法。
void transformPage (View page, float position)
页面:将在其上应用转换的当前视图。
" position":页面相对于寻呼机当前当前位置的位置。
0是前面和中间。
1是右边的一整页位置,而-1是左边的一整页位置。
嵌套ViewPager只是将ViewPager软件包在另一个ViewPager周围。
这是什么意思?
这意味着我们在片段的每个页面中都有一个父ViewPager。
子ViewPager将托管在上述每个片段中。
在以下应用程序中,我们的父级View Pager将使我们垂直滑动。
子级View Pager是水平滑动/滚动的默认选项。
我们将开发一个应用程序,该应用程序在Vertical ViewPager中包含Android教程列表及其说明。
向右滑动,该教程将显示在WebView中。
Android巢状ViewPager专案
在Android Studio中创建一个新项目,然后从下面的屏幕中选择Tabbed Activity模板。
我们将看到生成的MainActivity.java类,如下所示。
package com.theitroad.swipeviewpagerinshorts;
import android.support.design.widget.FloatingActionButton;
import android.support.design.widget.Snackbar;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentPagerAdapter;
import android.support.v4.view.ViewPager;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
/**
* The {@link android.support.v4.view.PagerAdapter} that will provide
* fragments for each of the sections. We use a
* {@link FragmentPagerAdapter} derivative, which will keep every
* loaded fragment in memory. If this becomes too memory intensive, it
* Jan be best to switch to a
* {@link android.support.v4.app.FragmentStatePagerAdapter}.
*/
private SectionsPagerAdapter mSectionsPagerAdapter;
/**
* The {@link ViewPager} that will host the section contents.
*/
private ViewPager mViewPager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
//Create the adapter that will return a fragment for each of the three
//primary sections of the activity.
mSectionsPagerAdapter = new SectionsPagerAdapter(getSupportFragmentManager());
//Set up the ViewPager with the sections adapter.
mViewPager = (ViewPager) findViewById(R.id.container);
mViewPager.setAdapter(mSectionsPagerAdapter);
FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
fab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
.setAction("Action", null).show();
}
});
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
//Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_main, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
//Handle action bar item clicks here. The action bar will
//automatically handle clicks on the Home/Up button, so long
//as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
//noinspection SimplifiableIfStatement
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
/**
* A {@link FragmentPagerAdapter} that returns a fragment corresponding to
* one of the sections/tabs/pages.
*/
public class SectionsPagerAdapter extends FragmentPagerAdapter {
public SectionsPagerAdapter(FragmentManager fm) {
super(fm);
}
@Override
public Fragment getItem(int position) {
//getItem is called to instantiate the fragment for the given page.
//Return a PlaceholderFragment (defined as a static inner class below).
return PlaceholderFragment.newInstance(position + 1);
}
@Override
public int getCount() {
//Show 3 total pages.
return 3;
}
}
/**
* A placeholder fragment containing a simple view.
*/
public static class PlaceholderFragment extends Fragment {
/**
* The fragment argument representing the section number for this
* fragment.
*/
private static final String ARG_SECTION_NUMBER = "section_number";
/**
* Returns a new instance of this fragment for the given section
* number.
*/
public static PlaceholderFragment newInstance(int sectionNumber) {
PlaceholderFragment fragment = new PlaceholderFragment();
Bundle args = new Bundle();
args.putInt(ARG_SECTION_NUMBER, sectionNumber);
fragment.setArguments(args);
return fragment;
}
public PlaceholderFragment() {
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_main, container, false);
TextView textView = (TextView) rootView.findViewById(R.id.section_label);
textView.setText(getString(R.string.section_format, getArguments().getInt(ARG_SECTION_NUMBER)));
return rootView;
}
}
}
除了活动之外,该类还包含Adapter和Fragment类。
这个基本模板在ViewPager的每个页面上显示文本。
在开始实施之前,让我们看一下项目结构。
Android嵌套ViewPager项目结构
在列表标签的AndroidManifest.xml文件中添加Internet权限。
<uses-permission android:name="android.permission.INTERNET"
MainActivity-保存父ViewPager,即VerticalViewPager。
调用创建" ParentFragment"实例的" ParentViewPagerAdapter"VerticalViewPager-包含ViewPager的自定义实现,可垂直滚动。
这些页面包含一个由ParentViewPagerAdapter提供的" ParentFragment"视图。" ParentFragment" –位于" VerticalViewPager"内部。
它拥有另一个ViewPager,其适配器代码位于文件ChildViewPagerAdapter中。
片段的布局在" fragment_parent.xml"中定义。ChildViewPagerAdapter-包含嵌套ViewPager的实现。
它持有ChildFragment并将其提供给ParentFragment。ChildFragment–包含您将看到的UI。
布局文件:fragment_child.xmlDataModel-包含提供给ChildFragment的数据。
Android嵌套式ViewPager,垂直ViewPager代码
下面是VerticalViewPager.java类的代码:
package com.theitroad.swipeviewpagerinshorts;
import android.content.Context;
import android.support.v4.view.ViewPager;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
public class VerticalViewPager extends ViewPager {
public VerticalViewPager(Context context) {
super(context);
init();
}
public VerticalViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
private void init() {
setPageTransformer(true, new VerticalPageTransformer());
setOverScrollMode(OVER_SCROLL_NEVER);
}
private class VerticalPageTransformer implements ViewPager.PageTransformer {
@Override
public void transformPage(View view, float position) {
if (position < -1) { //[-Infinity,-1)
//This page is way off-screen to the left.
view.setAlpha(0);
} else if (position <= 1) { //[-1,1]
view.setAlpha(1);
//Counteract the default slide transition
view.setTranslationX(view.getWidth() * -position);
//set Y position to swipe in from top
float yPosition = position * view.getHeight();
view.setTranslationY(yPosition);
} else { //(1,+Infinity]
//This page is way off-screen to the right.
view.setAlpha(0);
}
}
}
/**
* Swaps the X and Y coordinates of your touch event.
*/
private MotionEvent swapXY(MotionEvent ev) {
float width = getWidth();
float height = getHeight();
float newX = (ev.getY()/height) * width;
float newY = (ev.getX()/width) * height;
ev.setLocation(newX, newY);
return ev;
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev){
boolean intercepted = super.onInterceptTouchEvent(swapXY(ev));
swapXY(ev);
return intercepted;
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
return super.onTouchEvent(swapXY(ev));
}
}
setOverScrollMode(OVER_SCROLL_NEVER)用于防止过度滚动。
如前所述,我们创建了一个自定义的" PageTransformer"类实现,该方法不是使用" translationX"水平翻译视图,而是使用" translationY"垂直翻译视图。
当用户在屏幕上滑动时,对运动事件也是如此。
下面给出了DataModel.java的代码。
package com.theitroad.swipeviewpagerinshorts;
public class DataModel {
public String title, description, url;
public DataModel(String title, String description, String url) {
this.title = title;
this.description = description;
this.url = url;
}
}
下面给出了" activity_main.xml"布局文件的代码。
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="https://schemas.android.com/apk/res/android"
android:id="@+id/main_content"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.theitroad.swipeviewpagerinshorts.VerticalViewPager
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/colorPrimaryDark"
</RelativeLayout>
它仅托管一个" VerticalViewPager",而后者又将托管"父片段"。
MainActivity.java类的代码如下。
package com.theitroad.swipeviewpagerinshorts;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.MotionEvent;
import android.view.View;
import java.util.ArrayList;
public class MainActivity extends AppCompatActivity implements ParentFragment.ToggleVerticalViewPagerScrolling {
private ParentViewPagerAdapter verticalPagerAdapter;
private VerticalViewPager verticalViewPager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ArrayList<DataModel> dataModels = new ArrayList<>();
dataModels.add(new DataModel("Android Volley Tutorial", getString(R.string.volley_description), getString(R.string.volley_url)));
dataModels.add(new DataModel("Android Dagger 2", getString(R.string.dagger_description), getString(R.string.dagger_url)));
dataModels.add(new DataModel("Android Geocoder Reverse Geocoding", getString(R.string.geocoder_description), getString(R.string.geocoder_url)));
dataModels.add(new DataModel("Android Notification Direct Reply", getString(R.string.notification_description), getString(R.string.notification_url)));
dataModels.add(new DataModel("RecyclerView Android with Dividers and Contextual Toolbar", getString(R.string.recyclerview_description), getString(R.string.recyclerview_url)));
verticalPagerAdapter = new ParentViewPagerAdapter(getSupportFragmentManager(), dataModels);
verticalViewPager = findViewById(R.id.container);
verticalViewPager.setAdapter(verticalPagerAdapter);
}
@Override
public void trigger(int page) {
if (page == 1) {
verticalViewPager.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
return true;
}
});
} else {
verticalViewPager.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
return false;
}
});
}
}
}
注意事项:
VerticalViewPager的适配器(即" ParentViewPagerAdapter")已加载数据。
触发方法从ParentFragment类中定义的接口覆盖。
每当页面更改时," ParentFragment"都会调用它。
它的目标是在Nested ViewPager显示第二页时(因为第二页包含WebView),从而禁止VerticalViewPager滚动/滑动。
注意:为避免在类中使用长字符串,我们在项目的strings.xml资源文件中定义了它们:
下面给出了" ParentViewPagerAdapter.java"类的代码。
package com.theitroad.swipeviewpagerinshorts;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentPagerAdapter;
import java.util.ArrayList;
public class ParentViewPagerAdapter extends FragmentPagerAdapter {
ArrayList<DataModel> dataModels = new ArrayList<>();
public ParentViewPagerAdapter(FragmentManager fm, ArrayList<DataModel> dataModels) {
super(fm);
this.dataModels = dataModels;
}
@Override
public Fragment getItem(int position) {
return ParentFragment.newInstance(dataModels.get(position));
}
@Override
public int getCount() {
return 5;
}
}
适配器为每个页面创建一个新的ParentFragment类。
每个DataModel元素共5页。
如下所示,在" fragment_parent.xml"文件中定义了ParentFragment类的布局。
<RelativeLayout xmlns:android="https://schemas.android.com/apk/res/android"
android:id="@+id/rl"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/colorPrimaryDark">
<android.support.v4.view.ViewPager
android:id="@+id/nestedViewPager"
android:layout_width="match_parent"
android:layout_height="match_parent"
</RelativeLayout>
ParentFragment类保存嵌套的ViewPager。
package com.theitroad.swipeviewpagerinshorts;
import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.view.ViewPager;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
public class ParentFragment extends Fragment {
ViewPager nestedViewPager;
Activity mActivity;
ToggleVerticalViewPagerScrolling tv;
public ParentFragment() {
}
public static ParentFragment newInstance(DataModel dataModel) {
ParentFragment fragment = new ParentFragment();
Bundle args = new Bundle();
args.putString("title", dataModel.title);
args.putString("description", dataModel.description);
args.putString("url", dataModel.url);
fragment.setArguments(args);
return fragment;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_parent, container, false);
String title = getArguments().getString("title");
String description = getArguments().getString("description");
String url = getArguments().getString("url");
DataModel model = new DataModel(title, description, url);
nestedViewPager = rootView.findViewById(R.id.nestedViewPager);
nestedViewPager.setAdapter(new ChildViewPagerAdapter(getChildFragmentManager(), model));
nestedViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
tv.trigger(position);
}
@Override
public void onPageSelected(int position) {
Log.d("API123", "onPageSelected " + position);
}
@Override
public void onPageScrollStateChanged(int state) {
}
});
return rootView;
}
public interface ToggleVerticalViewPagerScrolling {
void trigger(int page);
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
if (context instanceof Activity) {
mActivity = (Activity) context;
}
try {
tv = (ToggleVerticalViewPagerScrolling) mActivity;
} catch (ClassCastException e) {
throw new ClassCastException("Error in retrieving data. Please try again");
}
}
}
注意事项:
每个
ParentFragment.java类都包含一个嵌套的ViewPager。ChildViewPagerAdapter为每个页面创建一个嵌套的片段(ChildFragment)实例。
嵌套ViewPager将仅容纳两个页面(ChildFragment实例)。
第一个将包含本教程的标题和说明。
第二页将在WebView中显示该教程。addOnPageChangeListener回调用于确定当前页面索引。
接口ToggleVerticalViewPagerScrolling包含方法trigger。
触发方法将相关页面索引传递给MainActivity。
从片段读取通信数据如果页面索引为2,即WebView UI,则禁用VerticalViewPager滑动。
下面给出了ChildViewPagerAdapter.java类的代码。
package com.theitroad.swipeviewpagerinshorts;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentPagerAdapter;
public class ChildViewPagerAdapter extends FragmentPagerAdapter {
DataModel model;
public ChildViewPagerAdapter(FragmentManager fm, DataModel model) {
super(fm);
this.model = model;
}
@Override
public int getCount() {
return 2;
}
@Override
public Fragment getItem(int position) {
switch (position) {
case 0:
return ChildFragment.newInstance(model, false);
case 1:
return ChildFragment.newInstance(model, true);
default:
return ChildFragment.newInstance(model, true);
}
}
@Override
public CharSequence getPageTitle(int position) {
return "Child Fragment " + position;
}
}
下面给出布局fragment_child.xml的代码。
<android.support.v7.widget.CardView xmlns:android="https://schemas.android.com/apk/res/android"
xmlns:app="https://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="@dimen/activity_vertical_margin"
android:layout_marginEnd="@dimen/activity_horizontal_margin"
android:layout_marginStart="@dimen/activity_horizontal_margin"
android:layout_marginTop="@dimen/activity_vertical_margin"
app:cardCornerRadius="8dp"
app:cardElevation="8dp"
app:cardPreventCornerOverlap="true"
app:cardUseCompatPadding="true">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<WebView
android:id="@+id/webView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone"
<RelativeLayout
android:id="@+id/rl"
android:layout_width="match_parent"
android:layout_height="200dp"
android:background="#262626">
<TextView
android:id="@+id/txtTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:gravity="center"
android:textColor="#FFF"
android:textSize="26sp"
</RelativeLayout>
<TextView
android:id="@+id/txtDescription"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/rl"
android:layout_centerHorizontal="true"
android:layout_marginBottom="@dimen/activity_vertical_margin"
android:layout_marginEnd="@dimen/activity_horizontal_margin"
android:layout_marginStart="@dimen/activity_horizontal_margin"
android:layout_marginTop="@dimen/activity_vertical_margin"
android:textSize="18sp"
<Button
android:id="@+id/button"
android:background="#801f2124"
android:text="TAP/SLIDE RIGHT TO VIEW TUTORIAL"
android:textColor="#FFF"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
</RelativeLayout>
</android.support.v7.widget.CardView>
下面给出了ChildFragment.java类的代码。
package com.theitroad.swipeviewpagerinshorts;
import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.webkit.WebView;
import android.widget.Button;
import android.widget.RelativeLayout;
import android.widget.TextView;
public class ChildFragment extends Fragment {
public ChildFragment() {
}
public static ChildFragment newInstance(DataModel dataModel, boolean isWebView) {
ChildFragment fragment = new ChildFragment();
Bundle args = new Bundle();
args.putString("title", dataModel.title);
args.putString("description", dataModel.description);
args.putString("url", dataModel.url);
args.putBoolean("isWebView", isWebView);
fragment.setArguments(args);
return fragment;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_child, container, false);
RelativeLayout rl = rootView.findViewById(R.id.rl);
WebView webView = rootView.findViewById(R.id.webView);
TextView txtTitle = rootView.findViewById(R.id.txtTitle);
Button button = rootView.findViewById(R.id.button);
TextView txtDescription = rootView.findViewById(R.id.txtDescription);
boolean isWebView = getArguments().getBoolean("isWebView");
if (isWebView) {
webView.setVisibility(View.VISIBLE);
rl.setVisibility(View.GONE);
button.setVisibility(View.GONE);
txtDescription.setVisibility(View.GONE);
webView.loadUrl(getArguments().getString("url"));
} else {
webView.setVisibility(View.GONE);
rl.setVisibility(View.VISIBLE);
txtDescription.setVisibility(View.VISIBLE);
button.setVisibility(View.VISIBLE);
txtTitle.setText(getArguments().getString("title"));
txtDescription.setText(getArguments().getString("description"));
}
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
ParentFragment parentFrag = ((ParentFragment) ChildFragment.this.getParentFragment());
parentFrag.nestedViewPager.setCurrentItem(1);
}
});
return rootView;
}
}
来自newInstance方法的isWebView参数确定是否显示/隐藏WebView。
单击按钮将直接将用户带到WebView页面。
动态更改ViewPager页面。
要更改ViewPager页面,我们在ViewPager实例上调用setCurrentItem()方法。
不要忘记将CardView依赖项添加到build.gradle中。
向滑动效果添加动画
让我们通过创建另一个PageTransformer类来动画化VerticalViewPager滚动。
private class VerticalPageTransformerAnimate implements ViewPager.PageTransformer {
private static final float MIN_SCALE = 0.75f;
@Override
public void transformPage(View view, float position) {
int pageWidth = view.getWidth();
int pageHeight = view.getHeight();
float alpha = 0;
if (0 <= position && position <= 1) {
alpha = 1 - position;
} else if (-1 < position && position < 0) {
float scaleFactor = Math.max(MIN_SCALE, 1 - Math.abs(position));
float verticalMargin = pageHeight * (1 - scaleFactor)/2;
float horizontalMargin = pageWidth * (1 - scaleFactor)/2;
if (position < 0) {
view.setTranslationX(horizontalMargin - verticalMargin/2);
} else {
view.setTranslationX(-horizontalMargin + verticalMargin/2);
}
view.setScaleX(scaleFactor);
view.setScaleY(scaleFactor);
alpha = position + 1;
}
view.setAlpha(alpha);
view.setTranslationX(view.getWidth() * -position);
float yPosition = position * view.getHeight();
view.setTranslationY(yPosition);
}
}
通过以下方式在VerticalViewPager类的init方法中进行初始化。
setPageTransformer(true, new VerticalPageTransformerAnimate());

