NewbieGuide

Introduction: Android 快速实现新手引导层的库,通过简洁链式调用,一行代码实现引导层的显示
More: Author   ReportBugs   
Tags:

Android 快速实现新手引导层的库

这是一款可以通过简洁链式调用,一行代码实现引导层的显示,自动判断首次显示,当然也可以通过参数配置来满足不同的显示逻辑和需求。 通过自定义 layout.xml 实现文本及 image 的添加,非常方便位置的调整,避免代码调整各种不好控制的情况:实验 5,6 次才最终确定文字等的位置。

更新日志

更新日志

效果

改变高亮 view 的尺寸,并不用调整显示引导层的代码

sample
sample

引导层的 xml 可以完全自定义,像怎样显示就怎样显示

sample

此库依赖

compileOnly 'com.android.support:appcompat-v7:25.3.1'

导入

项目的 build.gradle 添加

allprojects {
        repositories {
            ...
            maven { url 'https://jitpack.io' }
        }
    }

module 的 build.gradle 添加

 dependencies {
      compile 'com.github.huburt-Hu:NewbieGuide:v2.4.0'
    }

确保你的项目中已经依赖了 appcompat-v7

使用

简单使用

NewbieGuide.with(activity)
        .setLabel("guide1")
        .addGuidePage(GuidePage.newInstance()
            .addHighLight(btnSimple)
            .setLayoutRes(R.layout.view_guide_simple))
        .show();

通过链式调用,一行代码即可实现引导层的显示,来看下效果:

simple use

其中:

  • with方法可以传入 Activity 或者 Fragment,获取引导页的依附者。Fragment 中使用建议传入 fragment,内部会添加监听,当依附的 Fragment 销毁时,引导层自动消失。
  • setLabel方法用于设置引导页的标签,区别不同的引导页,该方法必须调用设置,否则会抛出异常。内部使用该 label 控制引导页的显示次数。
  • addGuidePage方法添加一页引导页,这里的引导层可以有多个引导页,但至少需要一页。
  • GuidePage即为引导页对象,表示一页引导页,可以通过.newInstance()创建对象。并通过addHighLight添加一个或多个需要高亮的 view,该方法有多个重载,可以设置高亮的形状,以及 padding 等(默认是矩形)。setLayoutRes方法用于引导页说明布局,就是上图的说明文字的布局。
  • show方法直接显示引导层,如果不想马上显示可以使用build方法返回一个 Controller 对象,完成构建。需要显示得时候再次调用 Controller 对象的 show 方法进行显示。

添加高亮

高亮 view

addHighLight 方法有多个重写,完整参数如下:

    /**
     * 添加需要高亮的 view
     *
     * @param view          需要高亮的 view
     * @param shape         高亮形状{@link com.app.hubert.guide.model.HighLight.Shape}
     * @param round         圆角尺寸,单位 dp,仅{@link com.app.hubert.guide.model.HighLight.Shape#ROUND_RECTANGLE}有效
     * @param padding       高亮相对 view 的 padding,单位 px
     * @param relativeGuide 相对于高亮的引导布局
     */
    public GuidePage addHighLight(View view, HighLight.Shape shape, int round, int padding, @Nullable RelativeGuide relativeGuide)

高亮区域(v2.3.0 新增)

有些情况可能不太容易获得高亮 view 的引用,那么此时可以用添加高亮区域的方式来代替, 计算出需要高亮的 view 在 anchor 中位置,将获得的 rectF 传入 addHighLight 方法

    /**
     * 添加高亮区域
     *
     * @param rectF         高亮区域,相对于 anchor view(默认是 android.R.id.content)
     * @param shape         高亮形状{@link com.app.hubert.guide.model.HighLight.Shape}
     * @param round         圆角尺寸,单位 dp,仅{@link com.app.hubert.guide.model.HighLight.Shape#ROUND_RECTANGLE}有效
     * @param relativeGuide 相对于高亮的引导布局
     */
    public GuidePage addHighLight(RectF rectF, HighLight.Shape shape, int round, @Nullable RelativeGuide relativeGuide)

高亮区域点击事件(v2.4.0 新增)

之前也是 issues 中提到需要这个功能,希望能够开放此 api,因此在 2.4 版本中增加了。 由于目前高亮 view 的相关参数过多,因此将一些新增的配置都放入了 HighlightOptions 中,HighlightOptions 可以通过内部 Builder 对象构建:

HighlightOptions options = new HighlightOptions.Builder()
         .setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                       Toast.makeText(FirstActivity.this, "highlight click", Toast.LENGTH_SHORT).show();
                }
         })
         .build();
GuidePage page = GuidePage.newInstance().addHighLightWithOptions(btnRelative, options);
NewbieGuide.with(FirstActivity.this)
         .setLabel("relative")
         .alwaysShow(true)//总是显示,调试时可以打开
         .addGuidePage(page)
         .show();

自定义高亮区域绘制内容(v2.4.0 新增)

该功能主要是为了满足issue51提出的需求。

首先构建一个 HighlightOptions,并设置 OnHighlightDrewListener:

HighlightOptions options = new HighlightOptions.Builder()
        .setOnHighlightDrewListener(new OnHighlightDrewListener() {
            @Override
            public void onHighlightDrew(Canvas canvas, RectF rectF) {
                Paint paint = new Paint();
                paint.setColor(Color.WHITE);
                paint.setStyle(Paint.Style.STROKE);
                paint.setStrokeWidth(10);
                paint.setPathEffect(new DashPathEffect(new float[]{20, 20}, 0));
                canvas.drawCircle(rectF.centerX(), rectF.centerY(), rectF.width() / 2 + 10, paint);
            }
        })
        .build();

onHighlightDrew 方法会在引导层绘制高亮之后马上回调,可通过 canvas,以及给定的高亮区域 rectF,绘制想要任何视图,例如上述示例中完成的 issue 提出的虚线。

然后与高亮区域点击事件类似,传入 highlight 以及 option 到 addHighLightWithOptions 方法中:

GuidePage page = GuidePage.newInstance().addHighLightWithOptions(btnRelative, options);

显示次数控制

通常情况下引导页只在用户首次打开 app 的时候显示,第二次进入时不显示,因此默认只显示一次。当然你也可以通过.setShowCounts(3)自定义显示的次数,调试的时候可以使用.alwaysShow(true)设置每次都显示。

NewbieGuide.with(activity)
        .setLabel("guide1")
        //.setShowCounts(3)//控制次数
        .alwaysShow(true)//总是显示,调试时可以打开
        .addGuidePage(GuidePage.newInstance()
            .addHighLight(btnSimple)
            .setLayoutRes(R.layout.view_guide_simple))
        .show();

就算设置了.alwaysShow(true),内部还是会记录显示得次数,之后改会setShowCounts(3)可能实际记录的次数早已超过限制,因此不会再次显示。使用 Controller 对象的resetLabel方法重置次数。(或者清除应用缓存也能重置次数)

引导布局

着重说明一下 setLayoutRes 方法,通常其他的类似的库都是通过代码参数来控制说明内容展示在高亮 view 相对的位置,如下方。经常需要多次运行才能找到满意的位置的参数。大多说明内容只能出现在高亮的上下左右,需要库的支持,自定义的程度不是很高。

我所采用的方式是将说明内容通过 xml 的方式,自定义摆放位置。使得说明内容高度自定义,不管你是简单的图片,还是对话框类型的都可以。

like dialog

GuidePage.newInstance()
    .addHighLight(btnDialog)
    .setEverywhereCancelable(false)//是否点击任意位置消失引导页,默认 true
    .setLayoutRes(R.layout.view_guide_dialog, R.id.btn_ok)
    .setOnLayoutInflatedListener(new OnLayoutInflatedListener() {
        @Override
        public void onLayoutInflated(View view, Controller controller) {
            TextView tv = view.findViewById(R.id.tv_text);
        }
    })

该方法还有一个可变参数setLayoutRes(@LayoutRes int resId, int... id),传入 id 数组表示在布局中点击让引导页消失或者进入下一页的 View(例如,Button ok 的 id)。 setOnLayoutInflatedListener设置布局填充完成的监听,当传入的 xml(R.layout.view_guide_dialog)填充完成时会回答调用该监听,用于初始化自定布局的元素。

相对高亮位置的引导布局(v2.3.0 新增)

鉴于好多人提出上面的方法对于箭头指向的引导控制起来比较麻烦,在不用的手机屏幕尺寸上会有位置差异。 因此 v2.3 版本新增在高亮相对位置添加引导布局的方法。扩展 addHighLight 方法的重载,新增参数 RelativeGuide:

.addGuidePage(
        GuidePage.newInstance()
                .addHighLight(btnRelative, new RelativeGuide(R.layout.view_relative_guide,
                        Gravity.RIGHT, 100))
)

RelativeGuide 构造需要 2 个必传参数:

  • 布局 layout 的 res id;
  • gravity 目前仅支持 Gravity.LEFT Gravity.TOP Gravity.RIGHT Gravity.BOTTOM

还有一个可选参数 padding,表示与高亮 view 的 padding

引导层本质是一个 FrameLayout,RelativeGuide 与 setLayoutRes 都会成为 FrameLayout 的子 view,两者可以共存, setLayoutRes 在下层,多个 RelativeGuide 按照添加顺序依次添加。

各个方向的对齐方式如下图所示:

sample

如 Gravity.LEFT 的 top 与高亮 view 的 top 对齐,如果想改变,可以通过在传入布局的根布局添加 marginTop。 或者还可以继承 RelativeGuide 并复写 offsetMargin 方法修改位置,具体细节可查看 RelativeGuide 类。

引导页控制(v2.2.1 版本新增)

v2.2.1 版本 Controller 新增两个方法用于控制引导页的回退,可以在 OnLayoutInflatedListener 接口的回调方法中获取到 controller 对象,执行相应的操作。

.setOnLayoutInflatedListener(new OnLayoutInflatedListener() {
             @Override
             public void onLayoutInflated(View view, final Controller controller) {
                        view.findViewById(R.id.btn_ok).setOnClickListener(new View.OnClickListener() {
                              @Override
                              public void onClick(View v) {
                                  controller.showPage(0);
                              }
                        });
             }
})

背景色

引导页的背景色不要在 xml 中设置,通过GuidePage.setBackgroundColor()设置引导页的背景色,不同引导页可以有不同背景色,默认是 0xb2000000,建议设置有透明度的背景色。

anchor

默认的话引导层是添加在 DecorView 中的,我借鉴了Highlight的 anchor 概念,可以改变引导层添加到的 view,实现局部引导层的显示。通过调用.anchor(view)传入 anchorView,即为引导层的根布局。

final View anchorView = findViewById(R.id.ll_anchor);

NewbieGuide.with(FirstActivity.this)
        .setLabel("anchor")                        .anchor(anchorView)
        .alwaysShow(true)//总是显示,调试时可以打开
        .addGuidePage(GuidePage.newInstance()                                .addHighLight(btnAnchor, HighLight.Shape.CIRCLE, 5)
                .setLayoutRes(R.layout.view_guide_anchor))
        .show();

这里实现了具体显示引导层。

anchor

引导层其实是一个 FrameLayout,设置 anchor 之后,引导层的大小就与 anchor 所占的位置相同。默认是 android.R.id.content。setLayoutRes 方法设置的说明布局则会添加到引导层的 FrameLayout 中。

引导层显示隐藏监听

setOnGuideChangedListener 添加引导层的显示隐藏监听,一个 label 表示一个引导层,一个引导层可以有多个引导页,引导页切换不会触发该监听。

NewbieGuide.with(FirstActivity.this)
    .setLabel("listener")
    .alwaysShow(true)//总是显示,调试时可以打开
    .setOnGuideChangedListener(new OnGuideChangedListener() {
        @Override
        public void onShowed(Controller controller) {
            Toast.makeText(FirstActivity.this, "引导层显示", Toast.LENGTH_SHORT).show();
        }

        @Override
        public void onRemoved(Controller controller) {
            Toast.makeText(FirstActivity.this, "引导层消失", Toast.LENGTH_SHORT).show();
        }
    })
    .addGuidePage(GuidePage.newInstance().addHighLight(btnListener))
    .show();

多页引导页与监听

.setOnPageChangedListener(new OnPageChangedListener() {
    @Override
    public void onPageChanged(int page) {
         //引导页切换,page 为当前页位置,从 0 开始
        Toast.makeText(MainActivity.this, "引导页切换:" + page, Toast.LENGTH_SHORT).show();
    }
})
.addGuidePage(//添加一页引导页
    GuidePage.newInstance()//创建一个实例
       .addHighLight(button)//添加高亮的 view
        .addHighLight(tvBottom, HighLight.Shape.RECTANGLE)
        .setLayoutRes(R.layout.view_guide)//设置引导页布局
        .setOnLayoutInflatedListener(new OnLayoutInflatedListener() {
            @Override
            public void onLayoutInflated(View view, Controller controller) {
                //引导页布局填充后回调,用于初始化
                TextView tv = view.findViewById(R.id.textView2);
                tv.setText("我是动态设置的文本");
            }
        })
    .setEnterAnimation(enterAnimation)//进入动画
    .setExitAnimation(exitAnimation)//退出动画
                )
.addGuidePage(
      GuidePage.newInstance()
        .addHighLight(tvBottom, HighLight.Shape.RECTANGLE, 20)
        .setLayoutRes(R.layout.view_guide_custom, R.id.iv)//引导页布局,点击跳转下一页或者消失引导层的控件 id
       .setEverywhereCancelable(false)//是否点击任意地方跳转下一页或者消失引导层,默认 true
        .setBackgroundColor(getResources().getColor(R.color.testColor))//设置背景色,建议使用有透明度的颜色
        .setEnterAnimation(enterAnimation)//进入动画
        .setExitAnimation(exitAnimation)//退出动画
    )

引导页切换动画

如上面的例子所示,还可以添加引导页的切换动画

Animation enterAnimation = new AlphaAnimation(0f, 1f);
enterAnimation.setDuration(600);
enterAnimation.setFillAfter(true);

Animation exitAnimation = new AlphaAnimation(1f, 0f);
exitAnimation.setDuration(600);
exitAnimation.setFillAfter(true);

GuidePage.setEnterAnimation(enterAnimation)//进入动画
GuidePage.setExitAnimation(exitAnimation)//退出动画

Q&A

遇到问题可以看查看 Q&A

关于我

Github:https://github.com/huburt-Hu

CSDN:http://blog.csdn.net/Hubert_bing

简书:https://www.jianshu.com/u/002f99a0df6b

掘金:https://juejin.im/user/57bb1fdcc4c971006152d7b0/posts

License

 Copyright 2017 huburt-Hu

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Apps
About Me
GitHub: Trinea
Facebook: Dev Tools