先显示简易的效果
网上看了一些引导 发现自定义文本 和箭头指向都不够方便,就写了比较符合自己习惯的
//内容更新
视觉优化 将下面代码分别添加到两个addView之后
ObjectAnimator.ofFloat(高亮的view, "scaleY", 0.5f, 1.0f).setDuration(800).start();
ObjectAnimator.ofFloat(显示文本, "scaleX", 0.5f, 1.0f).setDuration(800).start();
ObjectAnimator.ofFloat(显示文本, "scaleY", 0.5f, 1.0f).setDuration(800).start();
效果如下
1.已经实现的有3种简易功能
一种是连接两个高亮的view 提示语句在线(View)的中间
二是指向某一条文件
三是多个高亮区域的连接
核心思路就是遮盖一层。我选择直接放在根布局下面。当然你想 直接动态添加的方式也行。目前只是探讨一下思路。
通过传入的view` Region region = new Region(new Region(0, 0, getWidth(), getHeight()));
for (int i = 0; i < rects.size(); i++) {
Region region0 = new Region(rects.get(i));
region.op(region0, Region.Op.DIFFERENCE);
}
RegionIterator iterator = new RegionIterator(region);
Rect rectNew = new Rect();
while (iterator.next(rectNew)) {
canvas.drawRect(rectNew, paint);
}`
扣出高亮区域然后连线添加文本 剩下的就是具体坐标和角度的计算。
@Override
protected void dispatchDraw(Canvas canvas) {
//核心顺序
if (rects.size() != 0) {
drawRect(canvas);//绘制高亮区域
super.dispatchDraw(canvas);//绘制子view -->如提示文字 箭头
}
}
为了大家方便自定义自己的 指向箭头。我没有画这条线 而是留了一个简易的view。可以用自己ui设计的箭头去替换 那么你可能就需要加上 图片宽度带来的影响
使用方法简易示例
//盖在根布局最上层 可指定其他任意层级的view高亮 链接 提示
gcv = (GuideCoverView) findViewById(R.id.gcv);
//后期可改进为 动态添加
// gcv.doubleViewContact(view, view2, 0, "这是一个提示??", false);
gcv.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.i("rex", "v");
int c = index++ % 5;
//第三处参数为 链接线的 自定义view 此处仅传图片资源id 传0即默认黄线 如需要其他自定义或者网络图片 可以去内部自定义addArr
if (c == 0) {
gcv.doubleViewContact(view, view2, 0, "这是一个提示~~", false);
} else if (c == 1) {
gcv.doubleViewContact(view, view3, 0, "这又是一个提示??", false);
} else if (c == 2) {
gcv.doubleViewContact(view2, view3, 0, "这又他妈是一个提示??", false);
} else if (c == 3) {
gcv.doubleViewContact(view, view4, 0, "熄火区域", true);
} else {
//第二种多连接示例
gcv.manyViewContact(
asList(view, view2, view2, view3, view3, view4, view4,view),
asList("多连接示例1", "多连接示例2", "多连接示例3", "多连接示例4"),
asList(false, false, false, true)
);
}
}
});
核心自定义遮盖引导层
package com.rex;
import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.Region;
import android.graphics.RegionIterator;
import android.os.Build;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Gravity;
import android.view.View;
import android.view.ViewTreeObserver;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.TextView;
import org.w3c.dom.Text;
import java.util.ArrayList;
import java.util.List;
/**
* Created by Rex on 2017/1/4.
* 比较开放性便于自定义文字 联系线的高亮引导容器
*/
public class GuideCoverView extends FrameLayout {
private List<Rect> rects = new ArrayList<>();
private Paint paint;
private int padding = 8;
private int index;
private View lastView;
private int statusBarHeight;
private boolean isOnlyOneViewToText;
private int mResourId = 0;
private Canvas mCanvas;
public GuideCoverView(Context context) {
this(context, null);
}
public GuideCoverView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public GuideCoverView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
statusBarHeight = getStatusBarHeight();
paint = new Paint();
paint.setColor(Color.parseColor("#8c000000"));
}
public View addArr(int w, int h) {
FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(w, h);
View view = new View(getContext());
if (mResourId > 0)
view.setBackgroundResource(mResourId);
else
view.setBackgroundColor(Color.GREEN);
addView(view, lp);
return view;
}
/**
* @param viewC1
* @param viewC2
* @param id 这里示例为 两个view高亮 自定义图片作为连接示意 线中心作为 提示语
* @param isText 是否为单高亮 末端指向文字 此时另一个view决定了文本显示的位置
*/
public void doubleViewContact(final View viewC1, final View viewC2, final int id, final String msg, final boolean isText) {
clear();
HighlightView(viewC1, msg, isText);
HighlightView(viewC2, msg, isText);
invalidate();
}
public void manyViewContact(ArrayList<View> views, ArrayList<String> msg, ArrayList<Boolean> isText) {
clear();
for (int i = 0; i < views.size(); i++) {
if (i + 1 >= views.size())
return;
int j = i / 2;
Log.i("rex", "j-->" + j + "msg-->" + msg.get(j) + "boolean--->" + isText.get(j));
HighlightView(views.get(i), msg.get(j), isText.get(j));
++i;
HighlightView(views.get(i), msg.get(j), isText.get(j));
invalidate();
}
}
@Override
protected void dispatchDraw(Canvas canvas) {
//核心顺序
if (rects.size() != 0) {
drawRect(canvas);//绘制高亮区域
super.dispatchDraw(canvas);//绘制子view -->如提示文字 箭头
}
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
}
public void clear() {
rects.clear();
lastView = null;
removeAllViews();
invalidate();
}
private void drawRect(Canvas canvas) {
Region region = new Region(new Region(0, 0, getWidth(), getHeight()));
for (int i = 0; i < rects.size(); i++) {
Region region0 = new Region(rects.get(i));
region.op(region0, Region.Op.DIFFERENCE);
}
RegionIterator iterator = new RegionIterator(region);
Rect rectNew = new Rect();
while (iterator.next(rectNew)) {
canvas.drawRect(rectNew, paint);
}
}
public void HighlightView(final View viewH, final String msg, final boolean isText) {
ViewTreeObserver vto = viewH.getViewTreeObserver();
vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
//监听一次马上结束
if (Build.VERSION.SDK_INT < 16) {
viewH.getViewTreeObserver().removeGlobalOnLayoutListener(this);
} else {
viewH.getViewTreeObserver().removeOnGlobalLayoutListener(this);
}
int[] locationXy = getLocationXy(viewH);
Rect rect = new Rect(locationXy[0] - padding,
locationXy[1] - statusBarHeight - padding,
locationXy[0] + viewH.getMeasuredWidth() + padding,
locationXy[1] + viewH.getMeasuredHeight() - statusBarHeight + padding);
if (lastView == null) {
rects.add(rect);
lastView = viewH;
} else {
if (!isText) {
rects.add(rect);//是否高亮
}
ContactDouble(lastView, viewH, msg, isText);
lastView = null;
}
}
});
}
/**
* 高亮view指向文本
*
* @param view
*/
private void ContactText(View view) {
}
/**
* 用自定义的view连接两个
*
* @param viewC1
* @param viewC2
* @param isText
*/
private void ContactDouble(View viewC1, View viewC2, final String msg, final boolean isText) {
int[] locationXy = getLocationXy(viewC1);
int[] locationXy2 = getLocationXy(viewC2);
final int x1 = locationXy[0] + viewC1.getWidth() / 2 - 2;
final int x2 = locationXy2[0] + viewC2.getWidth() / 2;
final int y1 = locationXy[1];
final int y2 = locationXy2[1];
final int lenth = (int) Math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2)) - 20 * padding;
final View view = addArr(3, lenth);
float tan = (x1 - x2) * 1.0f / (y1 - y2);
final float du = -(float) Math.toDegrees(Math.atan(tan));
ViewTreeObserver vto = view.getViewTreeObserver();
vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
//监听一次马上结束
if (Build.VERSION.SDK_INT < 16) {
view.getViewTreeObserver().removeGlobalOnLayoutListener(this);
} else {
view.getViewTreeObserver().removeOnGlobalLayoutListener(this);
}
final int centerX = (x1 + x2) / 2 - 4 * padding;
final int centerY = (y1 + y2) / 2 - view.getHeight() / 2 - 4 * padding;
view.setX(centerX);
view.setY(centerY);
view.setRotation(du);
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
final TextView tv = new TextView(getContext());
tv.setText(msg);
tv.setGravity(Gravity.CENTER);
if (isText) {
//线顶端
MeasureWH(tv, new measureOk() {
@Override
public void ok(View view, int w, int h) {
view.setX(centerX + w / 2);
view.setY(centerY);
}
});
} else {
//线中间
tv.setX((x1 + x2) / 2 + 2 * padding);
tv.setY((y1 + y2) / 2 + 2 * padding);
}
tv.setTextColor(Color.WHITE);
addView(tv, params);
}
});
}
/**
* 获取屏幕的坐标(包括状态栏等)
*/
public int[] getLocationXy(View viewL) {
int[] lXy = new int[2];
viewL.getLocationOnScreen(lXy);
LogI("getLocationXy", lXy.toString());
return lXy;
}
/**
* 监听绘制完成后测量宽高
*/
public interface measureOk {
void ok(View view, int w, int h);
}
public static void MeasureWH(final View viewL, final measureOk impl) {
ViewTreeObserver vto = viewL.getViewTreeObserver();
vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
//监听一次马上结束
if (Build.VERSION.SDK_INT < 16) {
viewL.getViewTreeObserver().removeGlobalOnLayoutListener(this);
} else {
viewL.getViewTreeObserver().removeOnGlobalLayoutListener(this);
}
if (impl != null) {
impl.ok(viewL, viewL.getWidth(), viewL.getHeight());
}
}
});
}
private void LogI(String msg1, String msg2) {
LogI(msg1 + " ---- >" + msg2);
}
private void LogI(String msg) {
Log.i("rex", msg);
}
public int getStatusBarHeight() {
int result = 0;
int resourceId = getResources().getIdentifier("status_bar_height", "dimen", "android");
if (resourceId > 0) {
result = getResources().getDimensionPixelSize(resourceId);
}
return result;
}
}