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
- Android开发之高亮引导
- Android开发之高亮引导
- Android开发之高亮引导
- android高亮引导页
- Android开发之引导页
- 新手引导之控件高亮显示
- android高亮布局引导Hlight
- Android快速实现高亮引导
- Android 高亮引导 亲测可行
- [Android]新功能引导高亮显示遮罩层View
- Android app新手引导高亮提示,简单易用
- Android开发之使用ViewPager做引导页面
- android开发之自定义ViewGroup实现竖向引导界面
- Android开发之app入口引导页Viewpager
- Android开发之用ViewPager实现欢迎引导页面
- android PopupWindos之引导
- Android Map 开发之高德地图
- Android Map 开发之高德地图
- LeetCode 051 N-Queens
- LeetCode 052 N-Queens II
- RSA算法中利用欧几里得算法求d详细过程
- Java学习第一天 1.1
- 2016年全面前端面试题
- Android开发之高亮引导
- 初识asp.net
- 87-----hibernate的几种映射关系总结
- POJ-2195 Going Home(最小费用最大流)
- 第一份功能较多的安卓项目--纪念日app
- AFNetworking 3.0封装post请求body里面内容是空的
- 勾股定理一日一证连载10
- C# HTMLHelper类对Html源码处理教程与源码下载
- oracle11g在Server08R2服务器上安装部署常见问题