前言:
软件开发时经常需要对某些按钮进行说明,引导用户该怎么去操作软件。这种情况大部分是一个透明的遮罩层,遮罩层上有一部分高亮区显示要说明的按钮,按钮的某个方向上加上一个图片进行操作说明。比如说小面的效果。
实现思路
- 在控件上覆盖上原有的控件截图
- 在控件上绘制基本集合图形,利用混合模式显示高亮区
编写代码
首先利用思路1的方式实现
-
编写一个自定义view,继承自view,下面直接上代码,看代码中的注释即可理解。
public class GuideView extends View { private Paint paint; //编写一个GuideBean,里面存放控件说明的信息,比如图片,要说明的控件等 private GuideBean guideBean; public GuideView(Context context, AttributeSet attrs) { super(context, attrs); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); //如果集合中不存在控件说明,直接隐藏该view。 if (guideBeans == null || guideBeans.size() == 0){ this.setVisibility(GONE); return; } //Config是此view的配置类,下面会提到。这里的OPENMORE指是否在一个屏幕内显示多个控件说明 if (Config.OPENMORE){ //一屏显示多个控件说明 canvas.drawBitmap(createDstBitmap(width,height), 0, 0, paint); for (int i=0; i<guideBeans.size(); i++){ drawBuyView(canvas,guideBeans.get(i)); } }else { //没点击一次,就播放下一个控件说明 drawBuyView(canvas,guideBean); } } //将要添加的控件说明集合设置进此view中,等待显示 public void setGuideBeans(List<GuideBean> guideBeans){ this.guideBeans = guideBeans; invalidate(); } //当数据设置好后调用此方法显示控件说明 public void showGuide(){ //将隐藏中的该view设置为显示状态 this.setVisibility(VISIBLE); if (guideBeans != null && guideBeans.size() !=0){ //将当前的说明设置为集合中的第一个对象。 guideBean = guideBeans.get(0); } invalidate(); } } -
绘制要说明控件的图片
/** * 绘制高亮区及说明图片 * @param canvas * @param guideBean */ public void drawBuyView(Canvas canvas,GuideBean guideBean){ if (guideBean == null){ return; } //如果是一屏显示多个控件说明,遮罩层在循环前绘制一边即可 if (!Config.OPENMORE){ canvas.drawBitmap(createDstBitmap(width,height), 0, 0, paint); } //绘制view的图像,即将view本身绘制成高亮区 //需要注意的是,要说明的控件背景不可设置为透明背景,否在获取控件图片时背景也 //是透明的,这样控件的颜色与遮罩层的颜色就一致了,起不到高亮的效果 if (guideBean.getViewBitmap() != null){ canvas.drawBitmap(guideBean.getViewBitmap(),guideBean.getRect().left,guideBean.getRect().top,paint); } //说明控件的中线坐标,用于计算说明图片的在x轴上的起始距离(距离屏幕左边距离) int centerLine = guideBean.getRect().left+(guideBean.getRect().right-guideBean.getRect().left)/2; //计算说明图片x轴上的起始位置 int targetCenter = centerLine - guideBean.getBitmap().getWidth()/2; if (guideBean.getBitmap() != null){ //执行绘制说明图片的方法 drawShuoMingPic(canvas,guideBean,targetCenter); } } /** * 绘制控件说明 * @param canvas * @param guideBean */ private void drawShuoMingPic(Canvas canvas, GuideBean guideBean,int targetCenter) { //根据不同的方位情况进行控件绘制。这里只写出绘制在控件底部的情况,全部的代码有点长,就不贴出来了。 if (guideBean.getPosition() == Config.BOTTOM){ canvas.drawBitmap(guideBean.getBitmap(),targetCenter+guideBean.getMarginLeft(),guideBean.getRect().bottom+guideBean.getMarginTop()+guideBean.getMarginBottom(),paint); } } -
编写一个配置类,用于配置该view的属性
/** * view的配置文件,务必在调用showGuide方法前配置完guideview */ public static class Config{ /** * 是否打开一个界面显示多个控件说明,默认是关闭的 */ public static boolean OPENMORE = false; /** * 配置遮罩层颜色 */ public static int COLOR = Color.parseColor("#99000000"); } -
编写GuideBean,这个类描述了所有的控件说明信息,比如padding,margin,控件的在屏幕上的位置等信息
public class GuideBean{
private Rect rect;
private int img;
private Bitmap bitmap;
private Bitmap viewBitmap;
private int marginTop,marginBottom,marginLeft,marginRight;
private boolean isSimpleShape;
private int position;
private byte shape;
/**
*
* @param img 要展示的说明图片
* @param act 活动界面
* @param view 想要出现高亮区的控件
*/
public GuideBean(int img, Activity act, View view) {
Rect rect = new Rect();
//获取控件在屏幕中的坐标位置,此坐标位置包括了状态栏的高度和标题栏的高度,
//所以后面矩阵rect中的top和bottom需要去除这两个高度的影响。去除方法在GuideViewUtils帮助类中
view.getGlobalVisibleRect(rect);
setRectInfo(rect,act);
this.img = img;
//获取控件说明图片
bitmap = BitmapFactory.decodeResource(act.getResources(),img);
this.rect = rect;
//获取控件本身的图片
this.viewBitmap = GuideViewUtils.loadBitmapFromView(view);
}
/**
* 设置控件矩阵的顶部坐标和底部坐标,这两个坐标与状态栏和标题栏高度有关
* @param rect 控件的矩阵
* @param act 活动
*/
public void setRectInfo(Rect rect,Activity act){
rect.top = rect.top - GuideViewUtils.getStatusBarHeight(act) - GuideViewUtils.getInstance().getActionBarHeight(act);
rect.bottom = rect.bottom - GuideViewUtils.getStatusBarHeight(act) - GuideViewUtils.getInstance().getActionBarHeight(act);
}
//下面时一堆get和set方法,不贴出来了。
}
- 获取状态栏高度,标题栏高度和view的图片(下面的方法在GuideViewUtils帮助类里面)
/** * 获取状态栏高度 * @param context * @return 状态栏高度 */ public static int getStatusBarHeight(Activity context){ //判断存不存在状态栏 WindowManager.LayoutParams params = context.getWindow().getAttributes(); if ((params.flags & WindowManager.LayoutParams.FLAG_FULLSCREEN) == WindowManager.LayoutParams.FLAG_FULLSCREEN){ //不存在状态栏直接返回0 return 0; }else { //存在则获取状态栏高度 int height = 0; int resourceId = context.getResources().getIdentifier("status_bar_height", "dimen", "android"); if (resourceId > 0) { height = context.getResources().getDimensionPixelSize(resourceId); } return height; } } /** * 获取 标题栏高度 * @param activity * @return */ public int getActionBarHeight(Activity activity){ //判断传进来的activity是不是Activity,继承自Activity的活动是不带标题栏的 //继承自AppCompatActivity的活动是带有标题栏的 if (activity instanceof AppCompatActivity){ //判断活动中是否存在标题栏,存在返回高度,不存在返回0 if (((AppCompatActivity) activity).getSupportActionBar() != null){ return ((AppCompatActivity) activity).getSupportActionBar().getHeight(); }else { return 0; } }else if (activity instanceof Activity){ if (activity.getActionBar() != null){ return activity.getActionBar().getHeight(); }else { return 0; } }else { return 0; } }
调用并显示控件说明
- 布局编写
<com.libowu.guide.view.GuideView android:layout_width="match_parent" android:layout_height="match_parent" android:id="@+id/guide"/> - 设置控件说明集合
//说明一下,只有绘制view完成后才可以设置集合,oncreate中view并没有绘制完成,所以控件的高度,在屏幕中的位置无法
//获取到,所以放到onWindowFocusChanged中执行了。不过onWindowFocusChanged是只要屏幕焦点变化时都会调用,不如锁屏
//开屏,切换前后台等都有肯能触发onWindowFocusChanged
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
//将要说明的控件添加到集合中,让后给guideview设置数据即可
guides = new ArrayList<>();
guides.add(new GuideBean(R.mipmap.guide,this,textView, true, GuideView.Config.OVAL));
guides.add(new GuideBean(R.mipmap.guide,this,textViewTwo,true, GuideView.Config.OVAL));
guides.add(new GuideBean(R.mipmap.guide,this,textViewThree,true, GuideView.Config.OVAL));
guide.setGuideBeans(guides);
}
- 显示控件说明
textViewTwo.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
//想要显示引导时,调用此方法即可显示
guide.showGuide();
}
});
引入依赖使用该view
- 项目的build.gradle中加入下面内容
allprojects {
repositories {
....
maven {url "https://jitpack.io"}
}
}
2.app的build.gradle中加入依赖
implementation 'com.gitee.libowu:guideView:v0.0.4'
总结
这个项目只说了第一种思路,第二种思路并没有说,因为代码有点多,所以先不写了,有兴趣的可以到码云上面clone项目到本地。码云地址。