SlidingTutorial-Android实现APP引导页
最近了解到一个十分酷炫的app引导页开源库SlidingTutorial-Android,至于到底有多酷炫,能不能引起你学习的兴趣先来看看效果吧。
github地址
https://github.com/Cleveroad/slidingtutorial-android
官方教程地址
https://www.cleveroad.com/blog/case-study-sliding-tutorial-for-android-by-cleveroad
官方Demo下载地址
https://download.****.net/download/huangxin388/10747932
在笔者自己研究的过程中遇到了很多的错误,主要就是github上给的是1.0+的实现教程,而官方教程给的是0.9+的实现教程。0.9+和1.0+的实现方式差别又挺大,这两个地方又都没有明确说明,因此学习的过程中两种方法混用,搞了一整天都没有搞明白。各种百度后发现虽然有人在介绍这个开源库但并没有人详细说明如何使用,顶多也就是把github上的教程用机器翻译了一遍。笔者经过一天辛苦的研究,踩过无数次坑之后终于搞明白了如何让这么酷炫的效果为我所用,下面分享给大家。
希望小伙伴们先将官方的Demo下载下来,运行之后看看效果。然后跟着教程一步一步做,需要什么资源直接去Demo中复制,只看此教程没有Demo中的各种资源很难学会的。当然也推荐大家搞明白版本问题后去官方看教程。
零.准备工作
由于官方Demo中使用的是PercentRelativeLayout而且人家的界面设计得又这么好看,因此我们就不自己设计布局了。不过就是要多引入一个support库。打开build.gradle,在dependencies中加入下面这句话
implementation 'com.android.support:percent:28.0.0'
就像下面这样
注意,这里加入的support:percent库要和你的support:appcompat库的版本号一致,不然android studio会给出警告。
点击Sync Now如果没有错误,恭喜你继续进行下一步,如果有错误请翻到文章最后。
一.将gradle依赖加入到我们的build.gradle中
点击Sync Now进行同步。虽然我们在导入percent依赖库的时候已经保证了版本一致,但是Android Studio依然给出了警告。这是因为slidingtutorial:1.0.8在底层实现的时候com.android.support-media-compat:25.3.1,总不能去改人家的底层吧,让我们将版本都改成25.3.1也不现实,毕竟这个版本2018年底就要被弃用了。不管了,小小警告还不至于让程序崩溃。
注意:我导入的是1.0.8,你导入的时候可能会出新的版本,但是无论如何一定要注意你导入的版本。因为0.9+版本和1.0+版本的实现方法差别还是挺大的,咱们今天讲解的是1.0+版本(讲老版本没意义)
二.入正题
建立三个fragment,继承PageFragment.三个layout
FirstGuidePageFragment
package com.example.myapplication;
import android.support.annotation.NonNull;
import com.cleveroad.slidingtutorial.Direction;
import com.cleveroad.slidingtutorial.PageFragment;
import com.cleveroad.slidingtutorial.TransformItem;
public class FirstGuidePageFragment extends PageFragment {
@Override
protected int getLayoutResId() {
return R.layout.fragment_page_first;//返回此gragment的布局
}
@NonNull
@Override
protected TransformItem[] getTransformItems() {
//return new TransformItem[0];
return new TransformItem[]{
/**
* 第一个参数图片资源ID
* 第二个参数图片转换方向
* 第三个参数shiftCoefficient
* 这个fragment中有8张小图片,当视图滑动切换时八张小图片分别以不同的速度,不同的方向滑动,从而产生动画效果
*/
TransformItem.create(R.id.ivFirstImage, Direction.LEFT_TO_RIGHT, 0.2f),
TransformItem.create(R.id.ivSecondImage, Direction.RIGHT_TO_LEFT, 0.06f),
TransformItem.create(R.id.ivThirdImage, Direction.LEFT_TO_RIGHT, 0.08f),
TransformItem.create(R.id.ivFourthImage, Direction.RIGHT_TO_LEFT, 0.1f),
TransformItem.create(R.id.ivFifthImage, Direction.RIGHT_TO_LEFT, 0.03f),
TransformItem.create(R.id.ivSixthImage, Direction.RIGHT_TO_LEFT, 0.09f),
TransformItem.create(R.id.ivSeventhImage, Direction.RIGHT_TO_LEFT, 0.14f),
TransformItem.create(R.id.ivEighthImage, Direction.RIGHT_TO_LEFT, 0.07f)
};
}
}
fragment_page_first
<?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>
篇幅问题,我就不一一展示了,大家可以去官方的Demo中去找。
三.实现MainActivity
首先实现OnSkipClickListener,用于捕捉引导页中SKIP按钮的点击动作。
private static final class OnSkipClickListener implements View.OnClickListener {
@NonNull
private final Context mContext;
OnSkipClickListener(@NonNull Context context) {
mContext = context.getApplicationContext();
}
@Override
public void onClick(View v) {
Toast.makeText(mContext, "Skip button clicked", Toast.LENGTH_SHORT).show();
}
}
其次实现IndicatorOptions用于设置引导页下方指示器的一些参数,具体参照
https://github.com/Cleveroad/SlidingTutorial-Android/wiki/Configure-Page-Indicator
我们就用默认的
final IndicatorOptions indicatorOptions = IndicatorOptions.newBuilder(this)
.build();
再次实现TutorialPagesProvider
private static final class TutorialPagesProvider implements TutorialPageOptionsProvider {
@NonNull
@Override
public PageOptions provide(int position) {
@LayoutRes int pageLayoutResId;
TransformItem[] tutorialItems;
position %= ACTUAL_PAGES_COUNT;
switch (position) {
case 0: {
pageLayoutResId = R.layout.fragment_page_first;
tutorialItems = new TransformItem[]{
TransformItem.create(R.id.ivFirstImage, Direction.LEFT_TO_RIGHT, 0.20f),
TransformItem.create(R.id.ivSecondImage, Direction.RIGHT_TO_LEFT, 0.06f),
TransformItem.create(R.id.ivThirdImage, Direction.LEFT_TO_RIGHT, 0.08f),
TransformItem.create(R.id.ivFourthImage, Direction.RIGHT_TO_LEFT, 0.1f),
TransformItem.create(R.id.ivFifthImage, Direction.RIGHT_TO_LEFT, 0.03f),
TransformItem.create(R.id.ivSixthImage, Direction.RIGHT_TO_LEFT, 0.09f),
TransformItem.create(R.id.ivSeventhImage, Direction.RIGHT_TO_LEFT, 0.14f),
TransformItem.create(R.id.ivEighthImage, Direction.RIGHT_TO_LEFT, 0.07f)
};
break;
}
case 1: {
pageLayoutResId = R.layout.fragment_page_third;
tutorialItems = new TransformItem[]{
TransformItem.create(R.id.ivFirstImage, Direction.RIGHT_TO_LEFT, 0.20f),
TransformItem.create(R.id.ivSecondImage, Direction.LEFT_TO_RIGHT, 0.06f),
TransformItem.create(R.id.ivThirdImage, Direction.RIGHT_TO_LEFT, 0.08f),
TransformItem.create(R.id.ivFourthImage, Direction.LEFT_TO_RIGHT, 0.1f),
TransformItem.create(R.id.ivFifthImage, Direction.LEFT_TO_RIGHT, 0.03f),
TransformItem.create(R.id.ivSixthImage, Direction.LEFT_TO_RIGHT, 0.09f),
TransformItem.create(R.id.ivSeventhImage, Direction.LEFT_TO_RIGHT, 0.14f)
};
break;
}
case 2: {
pageLayoutResId = R.layout.fragment_page_second;
tutorialItems = new TransformItem[]{
TransformItem.create(R.id.ivFirstImage, Direction.RIGHT_TO_LEFT, 0.2f),
TransformItem.create(R.id.ivSecondImage, Direction.LEFT_TO_RIGHT, 0.06f),
TransformItem.create(R.id.ivThirdImage, Direction.RIGHT_TO_LEFT, 0.08f),
TransformItem.create(R.id.ivFourthImage, Direction.LEFT_TO_RIGHT, 0.1f),
TransformItem.create(R.id.ivFifthImage, Direction.LEFT_TO_RIGHT, 0.03f),
TransformItem.create(R.id.ivSixthImage, Direction.LEFT_TO_RIGHT, 0.09f),
TransformItem.create(R.id.ivSeventhImage, Direction.LEFT_TO_RIGHT, 0.14f),
TransformItem.create(R.id.ivEighthImage, Direction.LEFT_TO_RIGHT, 0.07f)
};
break;
}
default: {
throw new IllegalArgumentException("Unknown position: " + position);
}
}
return PageOptions.create(pageLayoutResId, position, tutorialItems);
}
}
其具体使用请参照
我们用下面的设置
final TutorialOptions tutorialOptions = TutorialFragment.newTutorialOptionsBuilder(this)
.setUseAutoRemoveTutorialFragment(true)//如果你true作为一个参数传递,将会发生两件事:
// 1.一个空片段将添加到用户的引导页中。
//2.一旦用户导航到此片段,整个TutorialFragment将从屏幕中删除。
//这使用户可以将其内容平滑地更改为引导页后面显示的应用程序内容。
.setUseInfiniteScroll(true)//如果传入true引导页将无限循环,并忽略setUseAutoRemoveTutorialFragment()方法
.setPagesColors(mPagesColors)//为每个页面设置一组颜色。数组的大小必须等于或大于页数。
.setPagesCount(TOTAL_PAGES)//设置页数
.setIndicatorOptions(indicatorOptions)//设置自定义指标选项。
.setTutorialPageProvider(new TutorialPagesProvider())//设置自定义教程页面提供程序。
.setOnSkipClickListener(new OnSkipClickListener(this))//为Skip按钮设置单击侦听器。
.build();
最后 在MainActivity的布局文件中加入FrameLayout
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout 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:context=".MainActivity">
<FrameLayout
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</android.support.constraint.ConstraintLayout>
得到TutorialFragment的实例
final TutorialFragment tutorialFragment = TutorialFragment.newInstance(tutorialOptions);
并通过FragmentManager动态引入
getFragmentManager()
.beginTransaction()
.replace(R.id.container, tutorialFragment)
.commit();
整个MainActivity中的代码如下所示
package com.example.myapplication;
import android.content.Context;
import android.support.annotation.LayoutRes;
import android.support.annotation.NonNull;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Toast;
import com.cleveroad.slidingtutorial.Direction;
import com.cleveroad.slidingtutorial.IndicatorOptions;
import com.cleveroad.slidingtutorial.PageOptions;
import com.cleveroad.slidingtutorial.TransformItem;
import com.cleveroad.slidingtutorial.TutorialFragment;
import com.cleveroad.slidingtutorial.TutorialOptions;
import com.cleveroad.slidingtutorial.TutorialPageOptionsProvider;
public class MainActivity extends AppCompatActivity {
private static final int TOTAL_PAGES = 6;
private static final int ACTUAL_PAGES_COUNT = 3;
private int[] mPagesColors;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mPagesColors = new int[]{
ContextCompat.getColor(this, android.R.color.darker_gray),
ContextCompat.getColor(this, android.R.color.holo_green_dark),
ContextCompat.getColor(this, android.R.color.holo_red_dark),
ContextCompat.getColor(this, android.R.color.holo_blue_dark),
ContextCompat.getColor(this, android.R.color.holo_purple),
ContextCompat.getColor(this, android.R.color.holo_orange_dark),
};
if (savedInstanceState == null) {
replaceTutorialFragment();
}
}
public void replaceTutorialFragment() {
final IndicatorOptions indicatorOptions = IndicatorOptions.newBuilder(this)
.build();
final TutorialOptions tutorialOptions = TutorialFragment.newTutorialOptionsBuilder(this)
.setUseAutoRemoveTutorialFragment(true)//如果你true作为一个参数传递,将会发生两件事:
// 1.一个空片段将添加到用户的引导页中。
//2.一旦用户导航到此片段,整个TutorialFragment将从屏幕中删除。
//这使用户可以将其内容平滑地更改为引导页后面显示的应用程序内容。
.setUseInfiniteScroll(true)//如果传入true引导页将无限循环,并忽略setUseAutoRemoveTutorialFragment()方法
.setPagesColors(mPagesColors)//为每个页面设置一组颜色。数组的大小必须等于或大于页数。
.setPagesCount(TOTAL_PAGES)//设置页数
.setIndicatorOptions(indicatorOptions)//设置自定义指标选项。
.setTutorialPageProvider(new TutorialPagesProvider())//设置自定义教程页面提供程序。
.setOnSkipClickListener(new OnSkipClickListener(this))//为Skip按钮设置单击侦听器。
.build();
final TutorialFragment tutorialFragment = TutorialFragment.newInstance(tutorialOptions);
getFragmentManager()
.beginTransaction()
.replace(R.id.container, tutorialFragment)
.commit();
}
private static final class TutorialPagesProvider implements TutorialPageOptionsProvider {
@NonNull
@Override
public PageOptions provide(int position) {
@LayoutRes int pageLayoutResId;
TransformItem[] tutorialItems;
position %= ACTUAL_PAGES_COUNT;
switch (position) {
case 0: {
pageLayoutResId = R.layout.fragment_page_first;
tutorialItems = new TransformItem[]{
TransformItem.create(R.id.ivFirstImage, Direction.LEFT_TO_RIGHT, 0.20f),
TransformItem.create(R.id.ivSecondImage, Direction.RIGHT_TO_LEFT, 0.06f),
TransformItem.create(R.id.ivThirdImage, Direction.LEFT_TO_RIGHT, 0.08f),
TransformItem.create(R.id.ivFourthImage, Direction.RIGHT_TO_LEFT, 0.1f),
TransformItem.create(R.id.ivFifthImage, Direction.RIGHT_TO_LEFT, 0.03f),
TransformItem.create(R.id.ivSixthImage, Direction.RIGHT_TO_LEFT, 0.09f),
TransformItem.create(R.id.ivSeventhImage, Direction.RIGHT_TO_LEFT, 0.14f),
TransformItem.create(R.id.ivEighthImage, Direction.RIGHT_TO_LEFT, 0.07f)
};
break;
}
case 1: {
pageLayoutResId = R.layout.fragment_page_third;
tutorialItems = new TransformItem[]{
TransformItem.create(R.id.ivFirstImage, Direction.RIGHT_TO_LEFT, 0.20f),
TransformItem.create(R.id.ivSecondImage, Direction.LEFT_TO_RIGHT, 0.06f),
TransformItem.create(R.id.ivThirdImage, Direction.RIGHT_TO_LEFT, 0.08f),
TransformItem.create(R.id.ivFourthImage, Direction.LEFT_TO_RIGHT, 0.1f),
TransformItem.create(R.id.ivFifthImage, Direction.LEFT_TO_RIGHT, 0.03f),
TransformItem.create(R.id.ivSixthImage, Direction.LEFT_TO_RIGHT, 0.09f),
TransformItem.create(R.id.ivSeventhImage, Direction.LEFT_TO_RIGHT, 0.14f)
};
break;
}
case 2: {
pageLayoutResId = R.layout.fragment_page_second;
tutorialItems = new TransformItem[]{
TransformItem.create(R.id.ivFirstImage, Direction.RIGHT_TO_LEFT, 0.2f),
TransformItem.create(R.id.ivSecondImage, Direction.LEFT_TO_RIGHT, 0.06f),
TransformItem.create(R.id.ivThirdImage, Direction.RIGHT_TO_LEFT, 0.08f),
TransformItem.create(R.id.ivFourthImage, Direction.LEFT_TO_RIGHT, 0.1f),
TransformItem.create(R.id.ivFifthImage, Direction.LEFT_TO_RIGHT, 0.03f),
TransformItem.create(R.id.ivSixthImage, Direction.LEFT_TO_RIGHT, 0.09f),
TransformItem.create(R.id.ivSeventhImage, Direction.LEFT_TO_RIGHT, 0.14f),
TransformItem.create(R.id.ivEighthImage, Direction.LEFT_TO_RIGHT, 0.07f)
};
break;
}
default: {
throw new IllegalArgumentException("Unknown position: " + position);
}
}
return PageOptions.create(pageLayoutResId, position, tutorialItems);
}
}
/**
* 用于捕捉引导页中SKIP按钮的点击动作
*/
private static final class OnSkipClickListener implements View.OnClickListener {
@NonNull
private final Context mContext;
OnSkipClickListener(@NonNull Context context) {
mContext = context.getApplicationContext();
}
@Override
public void onClick(View v) {
Toast.makeText(mContext, "Skip button clicked", Toast.LENGTH_SHORT).show();
}
}
}
四.成功
运行之后你会发现三个fragment却有6个界面,这是因为我们在设置TutorialOptions时有一句.setPagesCount(6)//设置页数,而在
TutorialPagesProvider的provide()方法中又有position %= 3;position虽然原来是0-5但是模3之后就只能是0-2了,因此三个fragment,三种动画效果,6个界面。
好了,这么酷炫的开源库使用方法就介绍到这里了,至于如何让你的实现只在第一次安装app的时候运行,大家就另行参考吧。
错误:
笔者第一次用的compileSdkVersion和targetSdkVersion都是27。Sync后表示无法导入(resolve)percent库
因此我将 compileSdkVersion和targetSdkVersion都是27都改为28,又升级了一下插件。重新建了一个新的项目
此时support:appcompat的默认版本是这样的
implementation 'com.android.support:appcompat-v7:28.0.0'
因此我也添加了一个28.0.0版本的依赖
implementation 'com.android.support:percent:28.0.0'
点击Sync Now问题解决。其实不用新建项目,直接更改support:appcompat和support:percent版本号就行,我之所以新建项目是想看看默认的是哪个版本,省的引入莫名其妙的错误。
如果上面的方法不行,请到本机SDK中找到一下路径,看一下本地都有哪些版本的percent库
我的是26.0.0-alpha1,因此将 support:appcompat和support:percent的版本号改为26.0.0-alpha1并且将 compileSdkVersion和targetSdkVersion改为26也能解决问题。
如果上面的方法还不行,你就自己建立一个布局吧,虽然不知道这个开源库中的方法对其他布局有没有效果。