导语
拷贝忍者汪汪卡今天嗅到了一个好玩的引导界面。由于有些产品的复杂性,很多应用当用户第一次打开的时候都是一脸懵逼的,我在哪,这是啥,我在干啥等等。这时候就需要一个强力的引导界面来一招仙人指路。SO,遇到这么好玩的东西当然要看一看啦。
先上一张效果图
第一步,先看如何使用
View one = findViewById(R.id.one);
int[] oneLocation = new int[2];
one.getLocationInWindow(oneLocation);
float oneX = oneLocation[0] + one.getWidth() / 2f;
float oneY = oneLocation[1] + one.getHeight() / 2f;
// make an target
SimpleTarget firstTarget = new SimpleTarget.Builder(MainActivity.this).setPoint(oneX, oneY)
.setRadius(100f)
.setTitle("first title")
.setDescription("first description")
.build();
View two = findViewById(R.id.two);
int[] twoLocation = new int[2];
two.getLocationInWindow(twoLocation);
PointF point =
new PointF(twoLocation[0] + two.getWidth() / 2f, twoLocation[1] + two.getHeight() / 2f);
// make an target
SimpleTarget secondTarget = new SimpleTarget.Builder(MainActivity.this).setPoint(point)
.setRadius(80f)
.setTitle("second title")
.setDescription("second description")
.build();
Spotlight.with(MainActivity.this)
.setDuration(1000L)
.setAnimation(new DecelerateInterpolator(2f))
.setTargets(firstTarget, secondTarget)
.setOnSpotlightStartedListener(new OnSpotlightStartedListener() {
@Override
public void onStarted() {
Toast.makeText(MainActivity.this, "spotlight is started", Toast.LENGTH_SHORT)
.show();
}
})
.setOnSpotlightEndedListener(new OnSpotlightEndedListener() {
@Override
public void onEnded() {
Toast.makeText(MainActivity.this, "spotlight is ended", Toast.LENGTH_SHORT).show();
}
})
.start();
恩..... Build模式,看这思路很清晰,那么就开始吧。
SimpleTarget指示样式设置
先看一下build,看上去可以设置几个属性,分别是指示器圆心、半径,显示文字的标题、内容。大概就这四个,跟进去看一下。
- Target 实现了一个接口,进去没啥可看的,getPoint(),getRadius(),getView(),获取属性用的。
- 重点来啦,Builder,继承自AbstractBuilder
AbstractBuilder
abstract class AbstractBuilder, S extends Target> {
private WeakReference contextWeakReference;
protected float startX = 0f;
protected float startY = 0f;
protected float radius = 100f;
/**
* return the builder itself
*/
protected abstract T self();
/**
* return the built {@link Target}
*/
protected abstract S build();
/**
* Return context weak reference
*
* @return the activity
*/
protected Activity getContext() {
return contextWeakReference.get();
}
/**
* Constructor
*/
protected AbstractBuilder(@NonNull Activity context) {
contextWeakReference = new WeakReference<>(context);
}
/**
* Sets the initial position of spotlight
*
* @param y starting position of y where spotlight reveals
* @param x starting position of x where spotlight reveals
* @return This Builder
*/
public T setPoint(float x, float y) {
this.startX = x;
this.startY = y;
return self();
}
/**
* Sets the initial position of spotlight
*
* @param point starting position where spotlight reveals
* @return This Builder
*/
public T setPoint(@NonNull PointF point) {
return setPoint(point.x, point.y);
}
/**
* Sets the initial position of spotlight
* Make sure the view already has a fixed position
*
* @param view starting position where spotlight reveals
* @return This Builder
*/
public T setPoint(@NonNull View view) {
int[] location = new int[2];
view.getLocationInWindow(location);
int x = location[0] + view.getWidth() / 2;
int y = location[1] + view.getHeight() / 2;
return setPoint(x, y);
}
/**
* Sets the radius of spotlight
*
* @param radius radius of spotlight
* @return This Builder
*/
public T setRadius(float radius) {
if (radius <= 0) {
throw new IllegalArgumentException("radius must be greater than 0");
}
this.radius = radius;
return self();
}
}
额,set了一大堆属性,弱引用持有activity,一个self()返回自己用的。一个build,恩......build就是build,算了不bb了,都挺好理解的。
其实上面墨迹一大堆都没什么卵用,看看热闹 = = ,详情看下面代码
@Override
public SimpleTarget build() {
if (getContext() == null) {
throw new RuntimeException("context is null");
}
View view = getContext().getLayoutInflater().inflate(R.layout.layout_spotlight, null);
((TextView) view.findViewById(R.id.title)).setText(title);
((TextView) view.findViewById(R.id.description)).setText(description);
PointF point = new PointF(startX, startY);
calculatePosition(point, radius, view);
return new SimpleTarget(point, radius, view);
}
获得遮罩布局,获得标题和内容。
等等Point我是知道的,PointF是个啥?看下源码,入参是浮点数的Point,制杖了。
然后是一个方法,看上去这就是设置遮罩用的了。
private void calculatePosition(final PointF point, final float radius, View spotlightView) {
float[] areas = new float[2];
Point screenSize = new Point();
((WindowManager) spotlightView.getContext()
.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay().getSize(screenSize);
areas[ABOVE_SPOTLIGHT] = point.y / screenSize.y;
areas[BELOW_SPOTLIGHT] = (screenSize.y - point.y) / screenSize.y;
int largest;
if (areas[ABOVE_SPOTLIGHT] > areas[BELOW_SPOTLIGHT]) {
largest = ABOVE_SPOTLIGHT;
} else {
largest = BELOW_SPOTLIGHT;
}
final LinearLayout layout = ((LinearLayout) spotlightView.findViewById(R.id.container));
layout.setPadding(100, 0, 100, 0);
switch (largest) {
case ABOVE_SPOTLIGHT:
spotlightView.getViewTreeObserver()
.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
layout.setY(point.y - radius - 100 - layout.getHeight());
}
});
break;
case BELOW_SPOTLIGHT:
layout.setY((int) (point.y + radius + 100));
break;
}
}
前面一大堆是根据圆点的Y轴坐标和整个屏幕坐标做对比,判断圆点是在屏幕的上边还是在屏幕的下边,然后确定展示文字的位置,大概是这样婶的。
继续下面就是标题和内容展示的LinearLayout的位置了,两种情况,根据上面的判断,一种是在焦点上面显示,一种是在焦点下面显示,好吧,想错了,原来是展示文字的方法。两种情况大概是这样婶的。
PS:两个方法不一样是因为第一个方法上面用到了layout.getHeight();layout的高度需要在那个回调里确定。
最后经过构造方法终于构造出了SimpleTarget,蛤蛤。
Spotlight终于到展示了吧。
有了Spotlight应该可以展示了,先看看构造属性有什么。
Spotlight.with(MainActivity.this)
.setDuration(1000L)
.setAnimation(new DecelerateInterpolator(2f))
.setTargets(firstTarget, secondTarget)
.setOnSpotlightStartedListener(new OnSpotlightStartedListener() {
@Override
public void onStarted() {
Toast.makeText(MainActivity.this, "spotlight is started", Toast.LENGTH_SHORT)
.show();
}
})
.setOnSpotlightEndedListener(new OnSpotlightEndedListener() {
@Override
public void onEnded() {
Toast.makeText(MainActivity.this, "spotlight is ended", Toast.LENGTH_SHORT).show();
}
})
.start();
五个,activity,动画效果,持续时间,targets,监听。不多bb,直接看start();
private void spotlightView() {
if (getContext() == null) {
throw new RuntimeException("context is null");
}
final View decorView = ((Activity) getContext()).getWindow().getDecorView();
SpotlightView spotlightView = new SpotlightView(getContext());
spotlightViewWeakReference = new WeakReference<>(spotlightView);
spotlightView.setLayoutParams(new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT));
((ViewGroup) decorView).addView(spotlightView);
spotlightView.setOnSpotlightStateChangedListener(new SpotlightView.OnSpotlightStateChangedListener() {
@Override
public void onTargetClosed() {
if (targets != null && targets.size() > 0) {
startTarget();
} else {
finishSpotlight();
}
}
@Override
public void onTargetClicked() {
finishTarget();
}
});
startSpotlight();
}
多出来一个SpotlightView,这回应该是圆圈的view了吧。
主要代码
private void init() {
bringToFront();
setWillNotDraw(false);
setLayerType(View.LAYER_TYPE_HARDWARE, null);
spotPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (animator != null && !animator.isRunning() && (float) animator.getAnimatedValue() > 0) {
if (listener != null) listener.onTargetClicked();
}
}
});
}
- 改变viewZ轴,让其在父类前面
- 自定义onDraw的时候调用
- 硬件加速模式
- 图像混合模式
- 加入监听,执行动画用的
绘制背景和圆,执行动画效果
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
paint.setColor(ContextCompat.getColor(getContext(), R.color.background));
canvas.drawRect(0, 0, canvas.getWidth(), canvas.getHeight(), paint);
if (animator != null) {
canvas.drawCircle(point.x, point.y, (float) animator.getAnimatedValue(), spotPaint);
}
}
其他动画效果并不是重点就不分析了。
CustomTarget自定义展示效果
可以灵活的设置展示样式,原理和上面的一样,就不错解释了。
OVER , 第八年,LPL终于拿到了一个像样的冠军了 !