我爱编程Android 进阶技术篇专题Android收藏集

Android 强大实用的功能引导组件

2018-08-06  本文已影响112人  SwitchLife

开篇

  利用闲暇时间撸了一个功能引导组件,此组件将在0.4.4版本里添加。小弟不才,一心只想打造一个能搞定一个应用的基础库。希望童鞋们多多支持!

效果截屏

立即体验

扫描以下二维码下载体验App(从0.2.3版本开始,体验App内嵌版本更新检测功能):


JSCKit库传送门:https://github.com/JustinRoom/JSCKit

简析源码

下面依次分享相关组件

一、GuideRippleView

一个实现循环水波纹动画的自定义view

其主要逻辑code很简单(绘制、动画控制都在onDraw()方法中)、没有什么难懂的东西,自行看代码理解:

    @Override
    protected void onDraw(Canvas canvas) {
        //只有开启动画时,才进行绘制。
        if (!isRunning)
            return;
        //这里是限制动画绘制区域
        if (clipWidth > 0 && clipHeight > 0){
            int clipLeft = (getWidth() - clipWidth) / 2;
            int clipTop = (getHeight() - clipHeight) / 2;
            canvas.clipRect(clipLeft, clipTop, clipLeft + clipWidth, clipTop + clipHeight);
        }

        //绘制圆圈
        float maxRadius = getWidth() / 2.0f;
        int alpha = (int) (startAlpha * (1 - radius / maxRadius) + .5f);
        for (int i = 0; i < circleCount; i++) {
            paint.setColor(colors[I]);
            paint.setAlpha(alpha);
            float tempRadius = radius - circleSpace * I;
            if (tempRadius <= 0)
                break;
            canvas.drawCircle(maxRadius, maxRadius, tempRadius, paint);
        }
        //speed这是控制动画速度的参数
        radius += speed;
        if (radius > maxRadius)
            radius = 0;
        invalidate();
    }
二、GuideLayout

用来控制被引导按钮的显示位置,以及添加其他的子view。

分析关键code:

    public void updateTargetLocation(@NonNull View target, int yOffset, int minRippleSize, int maxRippleSize, OnRippleViewUpdateLocationCallback callback) {
        Bitmap bitmap = Bitmap.createBitmap(target.getDrawingCache());
        int[] location = new int[2];
        target.getLocationOnScreen(location);
        targetRect.set(location[0], location[1], location[0] + target.getWidth(), location[1] + target.getHeight());
        targetRect.offset(0, -yOffset);
        ivTarget.setImageBitmap(bitmap);
        MarginLayoutParams params = (MarginLayoutParams) ivTarget.getLayoutParams();
        params.leftMargin = targetRect.left;
        params.topMargin = targetRect.top;
        ivTarget.setLayoutParams(params);
        updateRippleLocation(targetRect, minRippleSize, maxRippleSize, callback);
    }

    private void updateRippleLocation(@NonNull Rect targetRect, int minRippleSize, int maxRippleSize, OnRippleViewUpdateLocationCallback callback) {
        int size = Math.max(targetRect.width(), targetRect.height());
        int min = Math.min(minRippleSize, maxRippleSize);
        int max = minRippleSize + maxRippleSize - min;
        if (min > 0) {
            size = Math.max(size, minRippleSize);
        }
        if (max > 0) {
            size = Math.min(size, maxRippleSize);
        }
        LayoutParams params = new LayoutParams(size, 0);
        params.leftMargin = (targetRect.left + targetRect.right - size) / 2;
        params.topMargin = (targetRect.top + targetRect.bottom - size) / 2;
        if (guideRippleViewView == null) {
            guideRippleViewView = new GuideRippleView(getContext());
            addView(guideRippleViewView, params);
        } else {
            guideRippleViewView.setLayoutParams(params);
        }
        if (callback != null)
            callback.onRippleViewUpdateLocation(guideRippleViewView);
    }

target——被引导的按钮。
详细逻辑步骤:

三、4种展示功能引导方式

原理:
a、ViewGroup root = activity.findViewById(android.R.id.content)
b、root.addView(guideLayout, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT))

    /**
     * @param context       context
     * @param yOffset       offset from top
     * @param minRippleSize minimum ripple size
     * @param maxRippleSize max ripple size
     */
    public GuidePopupView(Context context, int yOffset, int minRippleSize, int maxRippleSize) {
        this.minRippleSize = Math.min(minRippleSize, maxRippleSize);
        this.maxRippleSize = Math.max(minRippleSize, maxRippleSize);
        this.yOffset = yOffset;
        guideLayout = new GuideLayout(context);
        guideLayout.setOnClickListener(null);
        guideLayout.setOnLongClickListener(null);
    }

    /**
     * Before showing action, it must had attached target.
     */
    public void show(@NonNull Activity activity) {
        if (target == null)
            throw new IllegalStateException("You need attach target first.");

        View view = activity.findViewById(android.R.id.content);
        if (view instanceof ViewGroup)
            ((ViewGroup) view).addView(guideLayout, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
    }

使用示例

    private void showGuidePopupView(final String key) {
        final int showCount = SharePreferencesUtils.getInstance().getInt(key, 0);
        if (showCount >= 3)
            return;

        ImageView imageView = new ImageView(this);
        imageView.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
        imageView.setImageResource(R.drawable.hand_o_up);
        //
        LinearLayout layout = new LinearLayout(this);
        layout.setOrientation(LinearLayout.VERTICAL);
        layout.setGravity(Gravity.CENTER_HORIZONTAL);
        //
        TextView textView = new TextView(this);
        textView.setTextColor(Color.WHITE);
        textView.setLineSpacing(0, 1.2f);
        textView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 16);
        textView.setText("点击可查看相关组件运行效果哦!");
        layout.addView(textView, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
        //
        LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
        params.topMargin = CompatResourceUtils.getDimensionPixelSize(this, R.dimen.space_16);
        Button button = new Button(this);
        button.setText("我知道了");
        layout.addView(button, params);
        new GuidePopupView(this)
                .setMinRippleSize(WindowUtils.getActionBarSize(this))
                .setMaxRippleSize(WindowUtils.getActionBarSize(this))
                .setyOffset(WindowUtils.getStatusBarHeight(this) + WindowUtils.getActionBarSize(this))
                .setBackgroundColor(0xB3000000)
                .setOnRippleViewUpdateLocationCallback(new GuideLayout.OnRippleViewUpdateLocationCallback() {
                    @Override
                    public void onRippleViewUpdateLocation(@NonNull GuideRippleView rippleView) {
                        //可以根据你自己的需求修改提示动画的相关设置
                        int color = CompatResourceUtils.getColor(rippleView.getContext(), R.color.colorAccent);
                        rippleView.initCirculars(3, new int[]{color, color, color}, 20, .7f);
                    }
                })
                .attachTarget(recyclerView.getChildAt(4))
                .removeAllCustomView()
                .addCustomView(imageView, new GuideLayout.OnAddCustomViewCallback<ImageView>() {
                    @Override
                    public void onAddCustomView(GuideLayout guideLayout, @Nullable ImageView customView, @NonNull Rect targetRect) {
                        GuideLayout.LayoutParams params = new GuideLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
                        params.gravity = Gravity.END;
                        params.rightMargin = targetRect.width() / 2 - 10;
                        params.topMargin = targetRect.bottom + 12;
                        guideLayout.addView(customView, params);

                        ObjectAnimator animator = ObjectAnimator.ofFloat(customView, View.TRANSLATION_Y, 0, 32, 0)
                                .setDuration(1200);
                        animator.setRepeatCount(-1);
                        animator.start();
                    }
                })
                .addCustomView(layout, new GuideLayout.OnAddCustomViewCallback<LinearLayout>() {
                    @Override
                    public void onAddCustomView(GuideLayout guideLayout, @Nullable LinearLayout customView, @NonNull Rect targetRect) {
                        GuideLayout.LayoutParams params = new GuideLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
                        params.gravity = Gravity.CENTER_HORIZONTAL;
                        params.topMargin = targetRect.bottom + CompatResourceUtils.getDimensionPixelSize(guideLayout, R.dimen.space_64);
                        guideLayout.addView(customView, params);
                    }
                })
                .addTargetClickListener(new OnCustomClickListener() {
                    @Override
                    public void onCustomClick(@NonNull View view) {
                        int count = SharePreferencesUtils.getInstance().getInt(key, 0) + 1;
                        SharePreferencesUtils.getInstance().saveInt(key, count);
                        Intent mIntent = new Intent();
                        mIntent.setClass(MainActivity.this, CustomToastActivity.class);
                        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                            mIntent.putExtra("transition", TransitionEnum.SLIDE.getLabel());
                            startActivity(mIntent, ActivityOptions.makeSceneTransitionAnimation(MainActivity.this).toBundle());
                        } else {
                            startActivity(mIntent);
                        }
                    }
                })
                .addCustomClickListener(button, null, true)
                .show(this);

    }

原理:看小标题就知道了,这是一个PopupWindow。

    /**
     * @param context       context
     * @param yOffset       offset from top
     * @param minRippleSize minimum ripple size
     * @param maxRippleSize max ripple size
     */
    public GuidePopupWindow(Context context, int yOffset, int minRippleSize, int maxRippleSize) {
        this.minRippleSize = Math.min(minRippleSize, maxRippleSize);
        this.maxRippleSize = Math.max(minRippleSize, maxRippleSize);
        this.yOffset = yOffset;
        DisplayMetrics metrics = context.getResources().getDisplayMetrics();
        guideLayout = new GuideLayout(context);
        guideLayout.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));

        mPopupWindow = new PopupWindow();
        mPopupWindow.setContentView(guideLayout);
        mPopupWindow.setWidth(metrics.widthPixels);
        mPopupWindow.setHeight(metrics.heightPixels);
        mPopupWindow.setFocusable(false);
        mPopupWindow.setOutsideTouchable(true);
    }

使用示例:

    private void showGuidePopupWindow(final String key) {
        final int showCount = SharePreferencesUtils.getInstance().getInt(key, 0);
        if (showCount < 3) {
            ImageView imageView = new ImageView(this);
            imageView.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
            imageView.setImageResource(R.drawable.hand_o_up);
            //
            LinearLayout layout = new LinearLayout(this);
            layout.setOrientation(LinearLayout.VERTICAL);
            layout.setGravity(Gravity.CENTER_HORIZONTAL);
            //
            TextView textView = new TextView(this);
            textView.setTextColor(Color.WHITE);
            textView.setLineSpacing(0, 1.2f);
            textView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 16);
            textView.setText("点击闪烁按钮可检测\n是否有版本更新哦!");
            layout.addView(textView, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
            //
            LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
            params.topMargin = CompatResourceUtils.getDimensionPixelSize(this, R.dimen.space_16);
            Button button = new Button(this);
            button.setText("我知道了");
            layout.addView(button, params);

            guidePopupWindow = new GuidePopupWindow(this)
                    .setMinRippleSize(WindowUtils.getActionBarSize(this))
                    .setMaxRippleSize(WindowUtils.getActionBarSize(this))
                    .setBackgroundColor(0xB3000000)
                    .setOnRippleViewUpdateLocationCallback(new GuideLayout.OnRippleViewUpdateLocationCallback() {
                        @Override
                        public void onRippleViewUpdateLocation(@NonNull GuideRippleView rippleView) {
                            //可以根据你自己的需求修改提示动画的相关设置
                            int color = CompatResourceUtils.getColor(rippleView.getContext(), R.color.colorAccent);
                            rippleView.initCirculars(3, new int[]{color, color, color}, 20, .7f);
                        }
                    })
                    .attachTarget(getActionMenuView())
                    .removeAllCustomView()
                    .addCustomView(imageView, new GuideLayout.OnAddCustomViewCallback<ImageView>() {
                        @Override
                        public void onAddCustomView(GuideLayout guideLayout, @Nullable ImageView customView, @NonNull Rect targetRect) {
                            GuideLayout.LayoutParams params = new GuideLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
                            params.gravity = Gravity.END;
                            params.rightMargin = targetRect.width() / 2 - 10;
                            params.topMargin = targetRect.bottom + 12;
                            guideLayout.addView(customView, params);

                            ObjectAnimator animator = ObjectAnimator.ofFloat(customView, View.TRANSLATION_Y, 0, 32, 0)
                                    .setDuration(1200);
                            animator.setRepeatCount(-1);
                            animator.start();
                        }
                    })
                    .addCustomView(layout, new GuideLayout.OnAddCustomViewCallback<LinearLayout>() {
                        @Override
                        public void onAddCustomView(GuideLayout guideLayout, @Nullable LinearLayout customView, @NonNull Rect targetRect) {
                            GuideLayout.LayoutParams params = new GuideLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
                            params.gravity = Gravity.CENTER;
                            guideLayout.addView(customView, params);
                        }
                    })
                    .addTargetClickListener(new OnCustomClickListener() {
                        @Override
                        public void onCustomClick(@NonNull View view) {
                            int count = SharePreferencesUtils.getInstance().getInt(key, 0) + 1;
                            SharePreferencesUtils.getInstance().saveInt(key, count);
                            loadVersionInfo();
                        }
                    })
                    .addCustomClickListener(button, null, true);
            guidePopupWindow.show();
        }
    }

原理:小标题说明这是一个Dialog。

    /**
     * @param context       context
     * @param yOffset       offset from top
     * @param minRippleSize minimum ripple size
     * @param maxRippleSize max ripple size
     */
    public GuideDialog(Context context, int yOffset, int minRippleSize, int maxRippleSize) {
        super(context);
        supportRequestWindowFeature(Window.FEATURE_NO_TITLE);
        this.minRippleSize = Math.min(minRippleSize, maxRippleSize);
        this.maxRippleSize = Math.max(minRippleSize, maxRippleSize);
        this.yOffset = yOffset;
        guideLayout = new GuideLayout(getContext());
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(guideLayout, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
        //设置window背景,默认的背景会有Padding值,不能全屏。当然不一定要是透明,你可以设置其他背景,替换默认的背景即可。
        getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
        //一定要在setContentView之后调用,否则无效
        getWindow().setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
    }

使用示例:

    private void showGuideDialog(final String key) {
        final int showCount = SharePreferencesUtils.getInstance().getInt(key, 0);
        if (showCount >= 3)
            return;

        ImageView imageView = new ImageView(this);
        imageView.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
        imageView.setImageResource(R.drawable.hand_o_up);
        //
        LinearLayout layout = new LinearLayout(this);
        layout.setOrientation(LinearLayout.VERTICAL);
        layout.setGravity(Gravity.CENTER_HORIZONTAL);
        //
        TextView textView = new TextView(this);
        textView.setTextColor(Color.WHITE);
        textView.setLineSpacing(0, 1.2f);
        textView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 16);
        textView.setText("点击可查看相关组件运行效果哦!");
        layout.addView(textView, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
        //
        LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
        params.topMargin = CompatResourceUtils.getDimensionPixelSize(this, R.dimen.space_16);
        Button button = new Button(this);
        button.setText("我知道了");
        layout.addView(button, params);
        new GuideDialog(this)
                .setCanceledOnTouchOutside1(false)
                .setCancelable1(false)
                .setMinRippleSize(WindowUtils.getActionBarSize(this))
                .setMaxRippleSize(WindowUtils.getActionBarSize(this))
//                .setBackgroundColor(0xB3000000)
                .setOnRippleViewUpdateLocationCallback(new GuideLayout.OnRippleViewUpdateLocationCallback() {
                    @Override
                    public void onRippleViewUpdateLocation(@NonNull GuideRippleView rippleView) {
                        //可以根据你自己的需求修改提示动画的相关设置
                        int color = CompatResourceUtils.getColor(rippleView.getContext(), R.color.colorAccent);
                        rippleView.initCirculars(3, new int[]{color, color, color}, 20, .7f);
                    }
                })
                .attachTarget(recyclerView.getChildAt(4))
                .removeAllCustomView()
                .addCustomView(imageView, new GuideLayout.OnAddCustomViewCallback<ImageView>() {
                    @Override
                    public void onAddCustomView(GuideLayout guideLayout, @Nullable ImageView customView, @NonNull Rect targetRect) {
                        GuideLayout.LayoutParams params = new GuideLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
                        params.gravity = Gravity.END;
                        params.rightMargin = targetRect.width() / 2 - 10;
                        params.topMargin = targetRect.bottom + 12;
                        guideLayout.addView(customView, params);

                        ObjectAnimator animator = ObjectAnimator.ofFloat(customView, View.TRANSLATION_Y, 0, 32, 0)
                                .setDuration(1200);
                        animator.setRepeatCount(-1);
                        animator.start();
                    }
                })
                .addCustomView(layout, new GuideLayout.OnAddCustomViewCallback<LinearLayout>() {
                    @Override
                    public void onAddCustomView(GuideLayout guideLayout, @Nullable LinearLayout customView, @NonNull Rect targetRect) {
                        GuideLayout.LayoutParams params = new GuideLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
                        params.gravity = Gravity.CENTER_HORIZONTAL;
                        params.topMargin = targetRect.bottom + CompatResourceUtils.getDimensionPixelSize(guideLayout, R.dimen.space_64);
                        guideLayout.addView(customView, params);
                    }
                })
                .addTargetClickListener(new OnCustomClickListener() {
                    @Override
                    public void onCustomClick(@NonNull View view) {
                        int count = SharePreferencesUtils.getInstance().getInt(key, 0) + 1;
                        SharePreferencesUtils.getInstance().saveInt(key, count);
                        Intent mIntent = new Intent();
                        mIntent.setClass(MainActivity.this, CustomToastActivity.class);
                        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                            mIntent.putExtra("transition", TransitionEnum.SLIDE.getLabel());
                            startActivity(mIntent, ActivityOptions.makeSceneTransitionAnimation(MainActivity.this).toBundle());
                        } else {
                            startActivity(mIntent);
                        }
                    }
                })
                .addCustomClickListener(button, null, true)
                .show();

    }

原理:
WindowManager manager = activity.getWindowManager();
manager.addView(View view, ViewGroup.LayoutParams params);

  童鞋们给个💕吧!用起来也很简单!童鞋们不要被我一大堆示例代码吓着了。

篇尾

  Wechat:eoy9527

在人类历史的长河中,真理因为像黄金一样重,总是沉于河底而很难被人发现,相反地,那些牛粪一样轻的谬误倒漂浮在上面到处泛滥。 —— 培根

上一篇下一篇

猜你喜欢

热点阅读