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.xml
DataModel-包含提供给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());