汪汪汪,Spotlight我来啦~~~

导语

拷贝忍者汪汪卡今天嗅到了一个好玩的引导界面。由于有些产品的复杂性,很多应用当用户第一次打开的时候都是一脸懵逼的,我在哪,这是啥,我在干啥等等。这时候就需要一个强力的引导界面来一招仙人指路。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终于拿到了一个像样的冠军了 !


奈斯~

你可能感兴趣的:(汪汪汪,Spotlight我来啦~~~)