Android开发之高亮引导

来源:互联网 发布:诺基亚n8软件专区 编辑:程序博客网 时间:2024/06/10 03:47

看下图,今天的任务就是它了,app 的高亮引导的实现,找到几个github上面已经实现的库,下载下来源码对比分析实现原理,整理自己的知识体系。下面是其中一个的效果图(我用DialogFragment实现了引导但是并没有做高亮实现,补充说明一点:该篇博客最好配合源码对比查看,不然感觉有那么点抽象,不知所云)

下面是找到的四个库的链接地址

  • ShowcaseView

  • TourGuide

  • Highlight

  • ShowTipsView

ShowcaseView

通过android studio导出shape包下uml不难发现,这些定制shape就是为高亮显示用的shape,updateTarget方法定义用于更新视图。

 /**  * Update shape bounds if necessary  */ void updateTarget(Target target);

NoShape仅仅是实现了Shape,见名知其意,使用NoShape则不需要高亮显示,RectangleShape则矩形高亮,CircleShape圆形高亮,RectangleShape内部定义adjustToTarget变量用于判断是否调整范围,至于这里的onDraw没什么好说的,略过

 @Override    public void updateTarget(Target target) {        if (adjustToTarget) {            Rect bounds = target.getBounds();            height = bounds.height();            if (fullWidth)                width = Integer.MAX_VALUE;            else width = bounds.width();            init();        }    }

CircleShape相比较于RectangleShape的区别在于一个是用rect一个是radius,一个drawRect一个drawCircle,updateTaget方法也有所不同

  @Override  public void updateTarget(Target target) {      if (adjustToTarget)         radius = getPreferredRadius(target.getBounds());  }  public static int getPreferredRadius(Rect bounds) {      return Math.max(bounds.width(), bounds.height()) / 2;  }

updateTarget方法都有用到Taget,这里的Taget只是一个范围测量目的new 出一个Rect,而MaterialShowcaseView自定义控件使用到的是Taget的实现类ViewTarget,内部实现代码比较简单,就我个人而言可以get到一点

// 获取在当前窗口内的绝对坐标View.getLocationInWindow()  // 获取在整个屏幕内的绝对坐标,注意这个值是要从屏幕顶端算起,也就是包括了通知栏的高度。View.getLocationOnScreen()     

AnimationFactory 和 IAnimationFactory是关于View动画相关的定制,主要是fadeInView 和fadeOutView效果,供MaterialShowcaseView调用,IShowcaseListener和IDetachedListener定义的接口回调函数,ShowcaseConfig提供了初始化MaterialShowcaseView的基本配置,默认遮盖层颜色dd335075,默认高亮显示图形CircleShape,PrefsManager类只是一个简单的数据缓存辅助类

MaterialShowcaseSequence类提供了addSequenceItem和start 、showNextItem显示引导View视图的方法,addSequenceItem方法通过MaterialShowcaseView的Buidler构建实例,并根据上面提到的ShowcaseConfig非空进行初始化MaterialShowcaseView配置

 public MaterialShowcaseSequence addSequenceItem(View targetView, String title, String content, String dismissText) {        MaterialShowcaseView sequenceItem = new MaterialShowcaseView.Builder(mActivity)                .setTarget(targetView)                .setTitleText(title)                .setDismissText(dismissText)                .setContentText(content)                .build();        if (mConfig != null) {            sequenceItem.setConfig(mConfig);        }        mShowcaseQueue.add(sequenceItem);        return this;    }

这个类我们要理解透彻有必要了解这个类的具体用法:Queue< E >,当我第一次看到代码内部调用下面这些方法简直了一头雾水,没搞懂他具体实现在哪里,调用这些接口定义得方法何用

 mShowcaseQueue.add(sequenceItem); mShowcaseQueue.poll();

下来看看接口Queue< E >的具体定义

public interface Queue<E> extends Collection<E> {    /**     * 将指定的元素插入到该队列中,如果由于容量限制,如法插入成功返回false,插入队列数据不能为空     */    boolean add(E e);    /**     *如果不违反容量限制的话,将指定的元素插入到该队列中当使用容量限制该方法通常是最好的add方法添加,     *能不能插入一个元素如果不能则通过抛出一个异常。     *如果添加类型不对,抛出ClassCastException,防止它被添加到该队列     *抛出NullPointerException异常如果指定元素为null,此队列不允许空元素     */    boolean offer(E e);    /**     *检索并删除该队列的头     */    E remove();    /**     *检索并删除该队列的头,如果该队列为空,则返回Null。     */    E poll();    /**     *检索,但不删除此队列的头。     *抛出NoSuchElementException,如果队列为空     */    E element();    /**     *检索,但不删除此队列的头,如果该队列为空,则返回Null     */    E peek();}

LinkedList对Queue做了具体实现,所以在MaterialShowcaseSequence构造函数里面实例Queue实例的是一个LinkedList,瞬间感觉上面方法的调用不再那么抽象了吧,这种感觉有木有??好吧原谅我吧,是我java基础不牢!!

 public MaterialShowcaseSequence(Activity activity) {        mActivity = activity;        mShowcaseQueue = new LinkedList<>();    }public class LinkedList<E> extends AbstractSequentialList<E> implements        List<E>, Deque<E>, Queue<E>, Cloneable, Serializable {          //.......略,详情参考源码...........}

MaterialShowcaseSequence 调用start方法 之后poll方法干掉了队列里面当前的view,再次判断队列是否还有view有就需要上面提到的showNextItem方法,间接调用了接口回调和show方法,定制动画也在这里被调用,具体请参考源码。

最后再来一观MaterialShowcaseView,内部定义Builder构造函数初始化MaterialShowcaseView实例,提供基本属性配置,builder根据已配置属性选择shape,Builder模式的运用实在不想再说,就这样吧,这个自定义控件用到的核心有两点:addOnGlobalLayoutListener和PorterDuff.Mode,视图层级变化引起onGlobalLayout回调到setTaget,从而达到调整高亮显示位置变化,而高亮显示部分的绘制核心利用画笔Xfermode,下列是onDraw绘制核心代码

 @Override protected void onDraw(Canvas canvas) {      super.onDraw(canvas);      //........略过图片资源回收和条件判断...................      // clear canvas      mCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);      // draw solid background      mCanvas.drawColor(mMaskColour);      // Prepare eraser Paint if needed      if (mEraser == null) {          mEraser = new Paint();          mEraser.setColor(0xFFFFFFFF);          mEraser.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));          mEraser.setFlags(Paint.ANTI_ALIAS_FLAG);      }      // draw (erase) shape      mShape.draw(mCanvas, mEraser, mXPosition, mYPosition, mShapePadding);      // Draw the bitmap on our views  canvas.      canvas.drawBitmap(mBitmap, 0, 0, null);  }

还有一些方法块的定义方便我们管理ShowcaseView的状态,以便以控制下次是否还弹出高亮引导显示resetSingleUse和resetAll,不得不感叹,一个Mode可以玩出这么多花样!!!


TourGuide 、ShowTipsView

这个库的实现原理一模一样,这个库的动画效果更好,还加了一个缩放的高亮指示动画,但是就代码层次而言,仅支持圆形高亮,太单一了,需求变更还得自己修改,代码的逻辑层次感觉没ShowcaseView库的清晰,效果图就不贴了,有兴趣自己下载运行,我果断放弃该库..

ShowTipsView库相比较于TourGuide要好不少,代码层次也清晰了不少,同样的实现原理同样的builder模式,保留意见(相比较于ShowcaseView,还是更喜欢前者,人就这样对“第一次”总是有那么一种难以言语的情绪)

Hightlight

洪洋的Hightlight库支持同时高亮显示多个View,定制显示布局,simple效果用的ImageView,效果非常漂亮当然你也可以用其他布局定制开发,下面是效果图:

代码调用实例

 private void showTipMask()    {        mHightLight = new HighLight(MainActivity.this)//                .anchor(findViewById(R.id.id_container))//如果是Activity上增加引导层,不需要设置anchor        .addHighLight(R.id.id_btn_important_right,R.layout.info_gravity_right_up, new HighLight.OnPosCallback(){            @Override            public void getPos(float rightMargin, float bottomMargin, RectF rectF, HighLight.MarginInfo marginInfo) {                marginInfo.rightMargin = rightMargin;                marginInfo.topMargin = rectF.top + rectF.height();            }        })        .addHighLight(R.id.id_btn_whoami, R.layout.info_gravity_left_down, new HighLight.OnPosCallback() {            @Override            public void getPos(float rightMargin, float bottomMargin, RectF rectF, HighLight.MarginInfo marginInfo) {                marginInfo.leftMargin = rectF.right - rectF.width()/2;                marginInfo.bottomMargin = bottomMargin + rectF.height();            }        })        .setClickCallback(new HighLight.OnClickCallback() {            @Override            public void onClick() {                Toast.makeText(MainActivity.this,"clicked",Toast.LENGTH_SHORT).show();            }        });        mHightLight.show();    }

这个库的内部实现原理也有点点区别,在layout调用buildMask方法得到需要显示 的bitmap,最后ondraw直接绘制bitmap,代码层次上面来说这个做法非常地PL,Xfermode也不同其他几个库PorterDuff.Mode.DST_OUT(貌似其他库都用CLEAR)

 private void buildMask()    {        mMaskBitmap = Bitmap.createBitmap(getMeasuredWidth(), getMeasuredHeight(), Bitmap.Config.ARGB_8888);        Canvas canvas = new Canvas(mMaskBitmap);        canvas.drawColor(maskColor);        mPaint.setXfermode(MODE_DST_OUT);        mHighLight.updateInfo();        for (HighLight.ViewPosInfo viewPosInfo : mViewRects)        {            canvas.drawRoundRect(viewPosInfo.rectF, DEFAULT_RADIUS, DEFAULT_RADIUS, mPaint);        }    }

小结

看了几个库还是做个小结,如果开发使用一个界面多个高亮引导或者要定制引导视图建议用Hightlight库,如果使用纯文字相关元素高亮引导,建议用ShowcaseView库,这块的知识点涉及到两点:画笔设置Xfermode和 ViewTreeObserver.OnGlobalLayoutListener,重新了解了Queue与MessageQueue相关的知识。

参考资料

http://blog.csdn.net/imyfriend/article/details/8564781

0 0
原创粉丝点击