android经验积累Android程序猿学习

android 新手引导浮层的实现

2016-06-19  本文已影响15394人  许方镇

源码:xufangzhen/NewbieGuide
转载请标明出处:http://www.jianshu.com/p/5aa96683d0dc

前言

这个模块写了很早了,在实际项目中更新了很多次,但是太懒了,博客里没有跟着更新,看到阅读人数这么多,在回头看看自己写的代码这么烂,都不好意思了,于是决定更新一下。PS:看了评价里有人说放在fragment里显示会乱,我表示不知道为什么他们会这么想,在fragment显示正常的。
更新(2016-11-22):

本来在我的项目中使用的新手引导浮层是这个TourGuide开源项目,这个新手引导项目功能比较多,一部分功能用不到,用到的地方只能大体上满足视觉的样式,不能一模一样。因此决定重构一遍,满足自己项目中的新手引导,本人项目中新手引导页的样式如下图所示:

新手引导.png

实现的功能

  1. 选中的view高亮可以有任意多个,形状有矩形,圆形,椭圆形
  2. 指示箭头或者其他图片可以在任意位置,可以有任意多个
  3. 文字和我知道了按钮可以在任意位置(默认我知道了在文字下方,两者水平居中,上下可调)
  4. 点击我知道了引导浮层消失
  5. 可设置点击任何位置引导浮层消失(默认点击消失)
  6. 浮层出现和消失可以有回调接口,可以延迟出现

实现的原理

1. 浮层的位置,放在activity的DecorView里,DecorView为FrameLayout的子类。

DecorView为整个Window界面的最顶层View。
DecorView只有一个子元素为LinearLayout,代表整个Window界面。
LinearLayout里有两个FrameLayout子元素,分别是标题栏和内容。

可通过以下代码获取,不清楚可参考这篇文章Android DecorView浅析

mParentView = (FrameLayout) mActivity.getWindow().getDecorView();

2. 引导浮层布局及上面的元素

  1. 浮层为相对布局,除了高亮的地方和半透明的背景其余都是通过addView的方式添加进去,通过设置margin来调整添加子view的位置。
    比如箭头元素的添加,offsetX(offsetY)负数则从右边(下边)开始偏移,CENTER为居中,方便设置具体位置
    public NewbieGuide addIndicateImg(int id, int offsetX, int offsetY) {
        ImageView arrowImg = new ImageView(mActivity);
        arrowImg.setImageResource(id);
        mGuideView.addView(arrowImg, getLp(offsetX, offsetY));
        return this;
    }```
```java
    private RelativeLayout.LayoutParams getLp(int offsetX, int offsetY) {
        RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(ViewGroup
                .LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
        //水平方向
        if (offsetX == CENTER) {
            lp.addRule(RelativeLayout.CENTER_HORIZONTAL, RelativeLayout.TRUE);
        } else if (offsetX < 0) {
            lp.addRule(RelativeLayout.ALIGN_PARENT_RIGHT, RelativeLayout.TRUE);
            lp.rightMargin = -offsetX;
        } else {
            lp.leftMargin = offsetX;
        }
        //垂直方向
        if (offsetY == CENTER) {
            lp.addRule(RelativeLayout.CENTER_VERTICAL, RelativeLayout.TRUE);
        } else if (offsetY < 0) {
            lp.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM, RelativeLayout.TRUE);
            lp.bottomMargin = -offsetY;
        } else {
            lp.topMargin = offsetY;
        }
        return lp;
    }
  1. 高亮洞洞的绘制
    思路一,本人想了一个比较巧妙的方法,拿圆形的高亮洞洞来说,画一个镂空的圆形,你会发现当画笔足够粗到把屏幕都遮起来的时候,刚好中心的小圆没有画到是高亮的,这个方法只需要调整好画笔的粗度和圆形的直径就可以了,矩形也是可垟,调整好边长和画笔的粗度就可以实现了,不过最后发现椭圆是不行的, 所以总结下这个方法只能一个高亮洞洞,而且只能圆形或矩形。
    画笔粗到可以遮挡屏幕时,中间的地方就高亮的洞洞
    所以最后还是使用和TourGuide同样的方法,通过画笔的setXfermode来实现,即当两个画布上都绘制了图片是,可以控制最终显示的样式,有取重叠部分,有去除重叠部分的等等,这个有16中规则,具体下图:
    setXfermode属性
    具体用法可以参考两篇文章 Android中Xfermode简单用法详解Paint的setXfermode。简单地说,TourGuide做法就是在一个画布上画了一个屏幕大小背景,在另一个画布上画了一个圆形,因为重叠了,所以去除了重叠的部分,高亮洞洞就显示出来了。
    本人针对这个做法做了点优化,即画了一个和洞洞所需要一样大小的图片而不是屏幕一样大小,如果有两个洞洞,则画了一个能同时容下两个洞洞的矩形大小的图片,这样显示的结果最终会变成下面这样:
    只画了满足高亮洞洞大小的图片
    高亮洞洞都显示出来了,但是只画了一部分的背景,首先这样做的目的是为了用较少的内存去完成(一个1080p屏幕大小的半透明的背景图bitmap大概需要3M以上)那剩余的空白部分该怎么填充呢,一个方法是用四个view去填充上,这个做法是可行的,但是麻烦,其实一个比较简单的做法我已经在上面提到了,就是思路一中说的画一个矩形,调整好画笔的粗度和长宽即可填充完,关键代码如下。
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if(mHoleList != null && mHoleList.size() > 0) {
            mPaint.setXfermode(pdf);
            mPaint.setMaskFilter(bmf);
            mPaint.setStyle(Paint.Style.FILL);
            for (HoleBean hole : mHoleList) {
                switch (hole.getType()) {
                    case HoleBean.TYPE_CIRCLE:
                        mCanvas.drawCircle(hole.getCenterX() - mBitmapRect.left, hole
                                .getCenterY() - mBitmapRect.top, hole.getRadius(),
                                mPaint);
                        break;
                    case HoleBean.TYPE_RECTANGLE:
                        mCanvas.drawRect(modifyRect(hole.getRectF()), mPaint);
                        break;
                    case HoleBean.TYPE_OVAL:
                        mCanvas.drawOval(modifyRect(hole.getRectF()), mPaint);
                        break;
                }
            }
            canvas.drawBitmap(mBitmap, mBitmapRect.left, mBitmapRect.top, null);
            //绘制剩余空间的矩形
            mPaint.setXfermode(null);
            mPaint.setMaskFilter(null);
            mPaint.setStyle(Paint.Style.STROKE);
            mPaint.setStrokeWidth(mStrokeWidth + 0.1f);
            canvas.drawRect(fillRect(mBitmapRect), mPaint);
        }
    }

3. 注意点

    if(NewbieGuideManager.isNeverShowed(this, NewbieGuideManager.TYPE_COLLECT)) {
        new NewbieGuideManager(this, NewbieGuideManager.TYPE_COLLECT).addView
                (mCollect, HoleBean.TYPE_CIRCLE).addView(mTitleTv, HoleBean
                .TYPE_RECTANGLE).show();
    }    ```
有时候在list滚动到某个位置时,会显示出某个引导浮层,这个要注意listview快速滚动的情况,这里提供一种写法,在onScroll当滚动到position为6的item时,显示:
```java
    @Override
    public void onScroll(final AbsListView view, int firstVisibleItem, int
            visibleItemCount, int totalItemCount) {
        if(firstVisibleItem >= 6 && NewbieGuideManager.isNeverShowed(this,
                NewbieGuideManager.TYPE_LIST) && !isShow) {
            isShow = true;
            mListView.smoothScrollToPosition(6);
            mListView.postDelayed(new Runnable() {
                @Override
                public void run() {
                    mListView.setSelection(6);
                    mListView.post(new Runnable() {
                        @Override
                        public void run() {
                            new NewbieGuideManager(MainActivity.this,
                                    NewbieGuideManager.TYPE_LIST).addView(view
                                    .getChildAt(0).findViewById(R.id.logo), HoleBean
                                    .TYPE_RECTANGLE).show();
                        }
                    });
                }
            }, 200);
        }
    }```

![滚动到item6后显示引导浮层](http:https://img.haomeiwen.com/i1903970/52b6175bdf4790e3.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
上一篇下一篇

猜你喜欢

热点阅读