Android嵌套式ViewPager,垂直ViewPager

时间:2020-02-23 14:29:06  来源:igfitidea点击:

在本教程中,我们将实现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());