引导页库slidingtutorial-android介绍

zhaoqinliang 8年前

来自: http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2016/0303/4030.html

先来看看用法。

这里的用法来自于该项目在github上的demo示例。

为了便于讲解。首先看看sample项目的代码结构:

设置主界面

首先你需要自定义一个fragment。

自定义一个继承自SimplePagerFragment的fragment,这里叫 CustomPresentationPagerFragment

既然是继承于SimplePagerFragment,为什么叫CustomPresentationPagerFragment而不叫CustomSimplePagerFragment呢?

这是因为SimplePagerFragment其实也是PresentationPagerFragment的子类,不过它写死了界面上的几个东西。当然了名字怎么取还是取决于你。

package com.cleveroad.slidingtutorial.sample;    import android.graphics.Color;  import android.support.annotation.ColorInt;  import android.support.v4.content.ContextCompat;  import android.view.View;  import android.widget.Toast;    import com.cleveroad.slidingtutorial.PageFragment;  import com.cleveroad.slidingtutorial.SimplePagerFragment;    public class CustomPresentationPagerFragment extends SimplePagerFragment {       @Override     protected int getPagesCount() {        return 6;     }       @Override     protected PageFragment getPage(int position) {        position %= 3;        if (position == 0)           return new FirstCustomPageFragment();        if (position == 1)           return new SecondCustomPageFragment();        if (position == 2)           return new ThirdCustomPageFragment();        throw new IllegalArgumentException("Unknown position: " + position);     }       @ColorInt     @Override     protected int getPageColor(int position) {        if (position == 0)           return ContextCompat.getColor(getContext(), android.R.color.holo_orange_dark);        if (position == 1)           return ContextCompat.getColor(getContext(), android.R.color.holo_green_dark);        if (position == 2)           return ContextCompat.getColor(getContext(), android.R.color.holo_blue_dark);        if (position == 3)           return ContextCompat.getColor(getContext(), android.R.color.holo_red_dark);        if (position == 4)           return ContextCompat.getColor(getContext(), android.R.color.holo_purple);        if (position == 5)           return ContextCompat.getColor(getContext(), android.R.color.darker_gray);        return Color.TRANSPARENT;     }       @Override     protected boolean isInfiniteScrollEnabled() {        return true;     }       @Override     protected boolean onSkipButtonClicked(View skipButton) {        Toast.makeText(getContext(), "Skip button clicked", Toast.LENGTH_SHORT).show();        return true;     }  }

其中,getPagesCount()返回引导界面的个数。getPage()方法则用于设置每一个界面所对应的PageFragment

。在demo中我们定义了三个PageFragment,如下:

@Override  protected PageFragment getPage(int position) {     position %= 3;     if (position == 0)        return new FirstCustomPageFragment();     if (position == 1)        return new SecondCustomPageFragment();     if (position == 2)        return new ThirdCustomPageFragment();     throw new IllegalArgumentException("Unknown position: " + position);  }

因为只定义了三个引导页面,而getPagesCount返回的是6,所以这里每个页面都被用了两次。

但是,这里只是定义了每个引导页面,还需要定义每个页面的背景颜色。

@Override  protected int getPageColor(int position) {     if (position == 0)        return ContextCompat.getColor(getContext(), android.R.color.holo_orange_dark);     if (position == 1)        return ContextCompat.getColor(getContext(), android.R.color.holo_green_dark);     if (position == 2)        return ContextCompat.getColor(getContext(), android.R.color.holo_blue_dark);     if (position == 3)        return ContextCompat.getColor(getContext(), android.R.color.holo_red_dark);     if (position == 4)        return ContextCompat.getColor(getContext(), android.R.color.holo_purple);     if (position == 5)        return ContextCompat.getColor(getContext(), android.R.color.darker_gray);     return Color.TRANSPARENT;  }

当然了,每个页面本身也是可以设置背景颜色的,但是slidingtutorial的设计理念是让具体的页面尽可能的简单,所以把背景颜色分离出来了。

接下来是一个意外惊喜,通过它可以设置引导页是否无限循环:

@Override  protected boolean isInfiniteScrollEnabled() {     return true;  }

显然我们的demo里面是需要它无限循环。

CustomPresentationPagerFragment上面有一个跳过按钮,我们需要为这个按钮设置点击事件:

@Override  protected boolean onSkipButtonClicked(View skipButton) {     Toast.makeText(getContext(), "Skip button clicked", Toast.LENGTH_SHORT).show();     return true;  }

这个关于引导界面的主界面就设置完了,总结一下就是,设置页面个数,设置具体的每一页、设置每一页的背景颜色、设置是否无限循环、设置跳过按钮的点击事件。代码很少。接下来把这个Fragment放在activity中就是了。这个不再讲解。

设置引导页的单个界面

其实这里的页面内容主要是负责前景元素,不包括背景颜色。

单个引导页面对应的是PageFragment。从前面的代码中我们知道,demo总共自定义了三个PageFragment,分别是

FirstCustomPageFragment  

SecondCustomPageFragment

ThirdCustomPageFragment

这里以FirstCustomPageFragment为例,看看PageFragment(每个具体的引导页面)是如何自定义的。

public class FirstCustomPageFragment extends PageFragment {       @Override     protected int getLayoutResId() {        return R.layout.fragment_page_first;     }       @Override     protected TransformItem[] provideTransformItems() {        return new TransformItem[]{              new TransformItem(R.id.ivFirstImage, true, 20),              new TransformItem(R.id.ivSecondImage, false, 6),              new TransformItem(R.id.ivThirdImage, true, 8),              new TransformItem(R.id.ivFourthImage, false, 10),              new TransformItem(R.id.ivFifthImage, false, 3),              new TransformItem(R.id.ivSixthImage, false, 9),              new TransformItem(R.id.ivSeventhImage, false, 14),              new TransformItem(R.id.ivEighthImage, false, 7)        };     }  }

就是这么多点代码,只做两件事:

  1. 设置这个页面的布局资源

  2. 为界面上的各个元素设置移动因素,包括方向和系数。一个TransformItem就是一个界面元素,其中它的第一个参数是界面元素对应的id,第二个参数是是否反向,true表示要,false表示不,第三个参数是移动系数。系数越大移动越慢,为一个界面上的不同元素设置不同的方向和系数,就能形成视差效果。

我们来看看第一个页面对应的xml布局:

<?xml version="1.0" encoding="utf-8"?>  <android.support.percent.PercentRelativeLayout      android:id="@+id/rootFirstPage"      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:ignore="ContentDescription"      tools:background="@android:color/holo_orange_dark">        <ImageView          android:id="@+id/ivFirstImage"            android:layout_width="wrap_content"          android:layout_height="wrap_content"          android:layout_centerInParent="true"            android:src="@mipmap/s_0_1"            app:layout_heightPercent="35%"          app:layout_widthPercent="50%"/>        <ImageView          android:id="@+id/ivSecondImage"            android:layout_width="wrap_content"          android:layout_height="wrap_content"          android:layout_alignParentEnd="true"          android:layout_alignParentRight="true"            android:src="@mipmap/s_0_2"            app:layout_heightPercent="10%"          app:layout_marginRightPercent="12%"          app:layout_marginTopPercent="27%"          app:layout_widthPercent="12%"/>          <ImageView          android:id="@+id/ivThirdImage"            android:layout_width="wrap_content"          android:layout_height="wrap_content"            android:src="@mipmap/s_0_3"            app:layout_heightPercent="25%"          app:layout_marginLeftPercent="14%"          app:layout_marginTopPercent="49%"          app:layout_widthPercent="30%"/>        <ImageView          android:id="@+id/ivFourthImage"            android:layout_width="wrap_content"          android:layout_height="wrap_content"            android:src="@mipmap/s_0_4"            app:layout_heightPercent="15%"          app:layout_marginLeftPercent="14%"          app:layout_marginTopPercent="39%"          app:layout_widthPercent="20%"/>        <ImageView          android:id="@+id/ivFifthImage"            android:layout_width="wrap_content"          android:layout_height="wrap_content"          android:layout_centerHorizontal="true"            android:src="@mipmap/s_0_5"            app:layout_heightPercent="15%"          app:layout_marginTopPercent="22%"          app:layout_widthPercent="45%"/>        <ImageView          android:id="@+id/ivSixthImage"            android:layout_width="wrap_content"          android:layout_height="wrap_content"            android:src="@mipmap/s_0_6"            app:layout_heightPercent="6%"          app:layout_marginLeftPercent="4%"          app:layout_marginTopPercent="26%"          app:layout_widthPercent="6%"/>        <ImageView          android:id="@+id/ivSeventhImage"            android:layout_width="wrap_content"          android:layout_height="wrap_content"            android:src="@mipmap/s_0_7"            app:layout_heightPercent="8%"          app:layout_marginLeftPercent="14%"          app:layout_marginTopPercent="25%"          app:layout_widthPercent="9%"/>        <ImageView          android:id="@+id/ivEighthImage"            android:layout_width="wrap_content"          android:layout_height="wrap_content"            android:src="@mipmap/s_0_8"            app:layout_heightPercent="6%"          app:layout_marginLeftPercent="77%"          app:layout_marginTopPercent="38%"          app:layout_widthPercent="8%"/>        <TextView          android:layout_width="wrap_content"          android:layout_height="wrap_content"          android:layout_alignParentBottom="true"          android:layout_centerHorizontal="true"            android:gravity="center"            android:text="@string/text_web_ceo"          android:textColor="@android:color/white"          android:textSize="@dimen/text_size_large"            app:layout_heightPercent="15%"          app:layout_marginBottomPercent="11%"          app:layout_widthPercent="45%"/>  </android.support.percent.PercentRelativeLayout>

使用的居然是百分比布局。不过用什么布局没有关系。

小结

整个用法就是这么简单,你把主Fragment放到activity里就可以了。使用这个库的好处是,我们不用为每个页面专门写逻辑,只要指定好页面元素的id ,方向和系数就好了。

技巧

这个库的亮点在于对抽象方法的使用。就拿PageFragment来说,有两个抽象方法:provideTransformItems()以及getLayoutResId(),要建立一个新的页面,只需实现这两个方法就可以了。

不过我仔细想了下,这两个方法的作用就是提供两个(或者说是两组)变量的值。为什么不用set的形式呢,那样的话都不用去自定义三个类。就新建一个类,然后调用各自的set方法。各位可以尝试下。但是可以肯定的是,使用set的方式代码不会太好看。

还有一个东西就是 transformItem ,它是抽象出来的一个东西,在界面上并没有对应的实体。代表一个被移动元素的引用以及相关数据,比如系数和移动方向。我们看看 slidingtutorial-android 是如何利用它来变换页面上的元素的。

首先,为了单独对页面的元素做错落有致的移动,在PresentationPagerFragment中定义了一个FragmentTransformer,它继承自PageTransformer:

/**   * Implementation of {@link android.support.v4.view.ViewPager.PageTransformer} that dispatch   * transform page event whenever a visible/attached page is scrolled.   */  private class FragmentTransformer implements ViewPager.PageTransformer {       public void transformPage(View view, float position) {        Object obj = view.getTag(R.id.page_fragment);        if (obj instanceof PageFragment) {           ((PageFragment) obj).transformPage(view, position);        }     }  }

对ViewPager熟悉的同学应该对PageTransformer很熟悉,它可以利用自己transformPage(View view, float position)方法中的position参数的值去设置view参数的属性,常见的是设置透明度,x/y信息等。可以看到在FragmentTransformer把transformPage里的参数信息传递给了PageFragment的transformPage()方法。

PageFragment的transformPage()方法定义如下:

void transformPage(View view, float position) {     holder.transformPage(view.getWidth(), position);  }

再一次的,PageFragment的transformPage()方法又把这些信息传递给holder.transformPage()

这个holder是LayersHolder对象,LayersHolder的定义如下:

class LayersHolder {     TransformItem[] transformItems;       public LayersHolder(View view, TransformItem[] transformItems) {        this.transformItems = transformItems;          for (TransformItem transformItem : transformItems) {           transformItem.setView(view.findViewById(transformItem.getViewResId()));        }     }       /**      * Method that apply a custom transformation to the page views      *      * @param pageWidth pageWidth      * @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 -1 is one page position to the left.      */     public void transformPage(int pageWidth, float position) {        for (TransformItem transformItem : transformItems) {           float translationX = (position) * (pageWidth / transformItem.getShiftCoefficient());             transformItem.getView().setTranslationX(transformItem.isReverseShift() ? -translationX : translationX);        }     }  }

在LayersHolder的transformPage里面,它直接遍历了所有的transformItem数组。然后根据传递进来的数据,结合自身的系数和移动方向,设置了自己的x信息。

总结

在所有的引导页库中, slidingtutorial-android 算是功能比较完善,用法也和简单的一个库。