先看看效果吧
看完图可以更直接的知道这个是干啥的,不过只实现了几种情况,其他的情况可以自己去计算去写。
实现的情况如下图(根据我的需求只写了这几个):
1、自定义View
package com.example.a_0102.mylearn.demo;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.DashPathEffect;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.widget.RelativeLayout;
/**
* Created by ws on 2018/8/21.
* 必须添加到RelativeLayout上,否则不能出现盖住的效果
*/
public class MyView extends RelativeLayout {
private Context context;
private int mBackgroundColor;//蒙版的背景色
private Paint mPaint;//画高亮区域的画笔
private Paint mLinePaint;//画线的画笔
private View mShowView;//高亮的View
private View mengbanView;//添加的蒙版View
private View mIndexView;//需要和高亮的View连线的View
private float startX, startY, stopX, stopY, centerX, centerY;//划线的,起点、中间点、结束点
private int height;//状态栏的高度
private int type;//画线的类型
private int offest;//不是从中点画的偏差
private int mIndexResId;//说明的View的资源ID
public MyView(Context context) {
this(context, null);
}
public MyView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public MyView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
this.context = context;
this.mBackgroundColor = Color.parseColor("#cc000000");
this.init();
}
private void init() {
this.mPaint = new Paint();
//设置画虚线的画笔
this.mLinePaint = new Paint();
mLinePaint.setColor(Color.parseColor("#ffffff"));
mLinePaint.setStrokeWidth(3);
mLinePaint.setPathEffect(new DashPathEffect(new float[]{5, 5}, 0));
this.mPaint.setAntiAlias(true);
PorterDuffXfermode xfermode = new PorterDuffXfermode(PorterDuff.Mode.CLEAR);
this.mPaint.setXfermode(xfermode);
// this.mPaint.setMaskFilter(new BlurMaskFilter(10.0F, BlurMaskFilter.Blur.INNER));//模糊
this.setLayerType(1, (Paint) null);//关闭单个view的硬件加速:
this.setClickable(true);
//ViewGroup默认情况下,出于性能考虑,会被设置成WILL_NOT_DROW,这样,ondraw就不会被执行了。
// 如果我们想重写一个viewgroup的ondraw方法,有两种方法:
// 1,构造函数中,给viewgroup设置一个颜色。
// 2,构造函数中,调用setWillNotDraw(false),去掉其WILL_NOT_DRAW flag。
// 在viewgroup初始化的时候,它调用了一个私有方法:initViewGroup,它里面会有一句setFlags(WILLL_NOT_DRAW,DRAW_MASK);
// 相当于调用了setWillNotDraw(true),所以说,对于ViewGroup,他就认为是透明的了,如果我们想要重写onDraw,就要调用setWillNotDraw(false)。
this.setWillNotDraw(false);
//状态栏的高度
height = getStatusBarHeight(context);
}
protected void onDraw(Canvas canvas) {
canvas.drawColor(this.mBackgroundColor);
int[] location = new int[2];
//获取View在屏幕的位置
mShowView.getLocationOnScreen(location);
RectF showRectF = new RectF();
showRectF.left = (float) location[0];
showRectF.top = (float) location[1] - height;
showRectF.right = (float) (location[0] + mShowView.getWidth());
showRectF.bottom = (float) (location[1] + mShowView.getHeight()) - height;
RectF indexRectF = new RectF();
if (mengbanView != null) {
mIndexView = mengbanView.findViewById(mIndexResId);
mIndexView.getLocationOnScreen(location);
indexRectF.left = (float) location[0];
indexRectF.top = (float) location[1] - height;
}
//画高亮的矩形
canvas.drawRect(showRectF, this.mPaint);
//画线
switch (type) {
case 0://高亮区域在右上,向左向下画线(从中点画)
startY = showRectF.top + mShowView.getHeight() / 2;
stopX = indexRectF.left + mIndexView.getWidth() / 2;
stopY = indexRectF.top;
startX = showRectF.left;
centerX = stopX;
centerY = startY;
canvas.drawLine(startX, startY, centerX, centerY, mLinePaint);
canvas.drawLine(centerX, centerY, stopX, stopY, mLinePaint);
break;
case 1://画一条线,高亮区域在上面(和2的雷同)
startX = showRectF.left + mShowView.getWidth() / 2;
stopX = startX;
startY = showRectF.top + mShowView.getHeight();
stopY = indexRectF.top;
canvas.drawLine(startX, startY, stopX, stopY, mLinePaint);
break;
case 2://画一条线,高亮区域在下面
startX = showRectF.left + mIndexView.getWidth() / 2;
stopX = startX;
startY = showRectF.top;
stopY = indexRectF.top + mIndexView.getHeight();
canvas.drawLine(startX, startY, stopX, stopY, mLinePaint);
break;
case 3://高亮区域在下面,两条线,不再高亮区域的中间开始画-|(和4的是左右相反的)
startX = showRectF.right - offest;
startY = showRectF.top;
stopX = indexRectF.left + mIndexView.getWidth();
stopY = indexRectF.top + mIndexView.getHeight() / 2;
centerX = startX;
centerY = stopY;
Log.e("--------->","3333");
canvas.drawLine(startX, startY, centerX, centerY, mLinePaint);
canvas.drawLine(centerX, centerY, stopX, stopY, mLinePaint);
break;
case 4://高亮区域在下面,不是从中间连线的|-
startX = showRectF.left + offest;
startY = showRectF.top;
stopX = indexRectF.left;
stopY = indexRectF.top + mIndexView.getHeight() / 2;
centerX = startX;
centerY = stopY;
canvas.drawLine(startX, startY, centerX, centerY, mLinePaint);
canvas.drawLine(centerX, centerY, stopX, stopY, mLinePaint);
break;
case 5://一条线,高亮的在下面,不是从中间开始画的(1,2是中间开始画的)
startX = showRectF.left + offest;
stopX = startX;
startY = showRectF.top;
stopY = indexRectF.top + mIndexView.getHeight();
canvas.drawLine(startX, startY, stopX, stopY, mLinePaint);
break;
case 6:
startX = showRectF.left;
startY = showRectF.top + mShowView.getHeight() / 2;
stopX = showRectF.left-offest;
stopY = indexRectF.top + mIndexView.getHeight();
centerX = stopX;
centerY = startY;
canvas.drawLine(startX, startY, centerX, centerY, mLinePaint);
canvas.drawLine(centerX, centerY, stopX, stopY, mLinePaint);
break;
}
}
public MyView setOffest(int offest) {
this.offest = offest;
return this;
}
//修改高亮的View,同一个蒙版下不同的高亮部分
public MyView changeView(View changeView){
this.mShowView = changeView;
invalidate();//执行onDraw方法
return this;
}
//修改蒙版View
public MyView changeMengbanView(View mengbanView,int mIndexResId){
removeAllViews();
this.mengbanView = mengbanView;
this.mIndexResId = mIndexResId;
invalidate();
return this;
}
public MyView setShowView(View showView) {
this.mShowView = showView;
return this;
}
public MyView setmBackgroundColor(int mBackgroundColor) {
this.mBackgroundColor = mBackgroundColor;
return this;
}
/**
* 设置蒙版View,并指定哪个是说明作用的View
* @param mengbanView
* @param mIndexResId
* @return
*/
public MyView setMengbanView(View mengbanView,int mIndexResId) {
this.mengbanView = mengbanView;
this.mIndexResId = mIndexResId;
return this;
}
public MyView setType(int type) {
this.type = type;
return this;
}
public void dismiss(){
removeAllViews();
}
public int getStatusBarHeight(Context context) {
int height = 38;
int resourceId = context.getResources().getIdentifier("status_bar_height", "dimen", "android");
if (resourceId > 0) {
height = context.getResources().getDimensionPixelSize(resourceId);
}
Log.e("-------->", "状态栏的高度:" + height);
return height;
}
}
2、使用
package com.example.a_0102.mylearn.demo;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.ScrollView;
import android.widget.TextView;
import com.example.a_0102.mylearn.R;
public class ViewActivity extends AppCompatActivity {
LinearLayout layout;
RelativeLayout rl_top;
RelativeLayout rl;
ScrollView scrollView;
TextView tv_text;
TextView tv_scroll;
private View view;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_view2);
layout = findViewById(R.id.ll_kuang);
rl_top = findViewById(R.id.rl_top);
rl = findViewById(R.id.rl);
tv_text = findViewById(R.id.tv_text);
tv_scroll = findViewById(R.id.tv_scroll);
scrollView = findViewById(R.id.scrollview);
//创建蒙版层
view = LayoutInflater.from(this).inflate(R.layout.layout_mengban, null, false);
final MyView myView = new MyView(this)
.setMengbanView(view, R.id.tv_index)//蒙版层的View
.setShowView(tv_text)//需要高亮的View,需要指引的View
.setType(0)//设置画线的类型
.setOffest(30);//设置画线的偏移量
//设置宽高
final RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(-1, -1);
myView.addView(view, params);
//将蒙版的View添加到View上
rl.addView(myView);
//同一个蒙版上的另一个高亮区域
final TextView tv_next = view.findViewById(R.id.tv_next);
tv_next.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
myView.setType(1);
myView.changeView(layout);//需要高亮的View,需要指引的View
}
});
//取消蒙版
final TextView tv_cancel = view.findViewById(R.id.tv_know);
tv_cancel.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
myView.dismiss();
}
});
//给蒙版上的View设置监听事件,换另一种样式的蒙版
TextView tv_click = view.findViewById(R.id.tv_click);
tv_click.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
int local[] = new int[2];
tv_scroll.getLocationOnScreen(local);
scrollView.scrollBy(0, local[1]);
view = LayoutInflater.from(ViewActivity.this).inflate(R.layout.layout_mengban2, null, false);
TextView tv_know = view.findViewById(R.id.tv_index);
myView.changeMengbanView(view, R.id.tv_index);//蒙版层的View
myView.setShowView(tv_scroll)//需要高亮的View,需要指引的View
.setType(5);//设置画线的类型
tv_know.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
rl.removeView(myView);
}
});
TextView tv_cancel = view.findViewById(R.id.tv_cancel);
tv_cancel.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
myView.dismiss();
}
});
myView.addView(view, params);
}
});
}
}
3、涉及到的布局
activity_view2.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/rl"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ScrollView
android:id="@+id/scrollview"
android:layout_width="match_parent"
android:layout_height="match_parent">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="1000dp">
<LinearLayout
android:id="@+id/ll_kuang"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_marginTop="100dp">
<ImageView
android:layout_width="73dp"
android:layout_height="32dp"
android:background="@mipmap/ic_launcher" />
</LinearLayout>
<TextView
android:id="@+id/tv_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_marginRight="30dp"
android:layout_marginTop="300dp"
android:background="#ddff00"
android:padding="20dp"
android:text="对比" />
<TextView
android:id="@+id/tv_scroll"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_marginTop="800dp"
android:gravity="center"
android:text="滑动到这了么"
android:textColor="#ff00ff"
android:textSize="20sp" />
</RelativeLayout>
</ScrollView>
</RelativeLayout>
layout_mengban.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="@+id/tv_click"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_marginTop="60dp"
android:text="点这里试试"
android:textColor="#ffffff" />
<TextView
android:id="@+id/tv_index"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:layout_marginBottom="120dp"
android:text="说明这个View是干啥的"
android:textColor="#ffffff" />
<TextView
android:id="@+id/tv_next"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:layout_marginBottom="80dp"
android:text="下一步"
android:textColor="#ffffff" />
<TextView
android:id="@+id/tv_know"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:layout_marginBottom="40dp"
android:text="知道了"
android:textColor="#ffffff" />
</RelativeLayout>
layout_mengban2.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/tv_index"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="这个是告诉你怎么弄"
android:textColor="#ffffff"
android:layout_marginTop="30dp"
android:layout_centerHorizontal="true"/>
<TextView
android:id="@+id/tv_cancel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_below="@id/tv_index"
android:layout_marginTop="20dp"
android:textColor="#ffffff"
android:text="知道了"/>
</RelativeLayout>
4、参考
Android Paint Xfermode 学习小结
主要思路来源自下面的这个类库
推荐一个好用小巧的Android引导蒙版(浮层)库