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

Posted Android编程精选

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android 强大实用的功能引导组件相关的知识,希望对你有一定的参考价值。


作者丨JustinRoom
https://www.jianshu.com/p/c1aaddd93245


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


每一个新上线App的功能引导很重要,没有功能引导,我们往往需要花费大量的时间以及人力去培训客户,从公司层面上这无疑是增加了很大的开销。


利用闲暇时间封装了一个功能引导组件,使用方便。次组件已经发布到jCenter。希望童鞋们多多支持!


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

功能盘点


  • 使用简洁方便

  • 带有引导波纹动画(可个性化配置,如颜色、动画速度、大小等)

  • 引导画面的上按钮与需被引导的按钮同等样式(如大小、颜色、形状、内容...)

  • 支持动态添加各种需要显示的view.

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

效果截屏


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


GuidanceRippleView


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


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

https://github.com/JustinRoom/WheelViewDemo


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

简析源码


下面依次分享相关组件


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


https://github.com/JustinRoom/GuidanceDemo/blob/master/guidanceLibrary/src/main/java/jsc/kit/guidance/GuidanceRippleView.java


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


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


protected void onDraw(Canvas canvas)


@Override
    protected void onDraw(Canvas canvas) {
        if (speed <= 0) {
            speed = space / frameCountPerSecond;
        }

        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) (0xFF * (1 - radius / maxRadius) + .5f);
        for (int i = 0; i < count; i++) {
            paint.setColor(colors[i]);
            paint.setAlpha(alpha);
            float tempRadius = radius - space * i;
            if (tempRadius > 0)
                canvas.drawCircle(maxRadius, maxRadius, tempRadius, paint);
        }
        radius += speed;
        if (radius > maxRadius)
            radius = 0;
        if (isRunning)
            invalidate();
    }


控制动画相关方法:


public void start() {
        if (isRunning)
            return;
        radius = 0;
        isRunning = true;
        invalidate();
    }

    public void stop() {
        radius = 0;
        isRunning = false;
        invalidate();
    }

    public void pause() {
        isRunning = false;
    }

    public void resume() {
        isRunning = true;
        invalidate();
    }

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


https://github.com/JustinRoom/GuidanceDemo/blob/master/guidanceLibrary/src/main/java/jsc/kit/guidance/GuidanceLayout.java


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


分析关键code:


/**
     * Update the target view's location.
     *
     * @param targetView         target
     * @param l                  the left margin
     * @param t                  the top margin
     * @param rippleViewSize     the size of {@link #rippleViewView}
     * @param rippleClipToTarget true, clip {@link #rippleViewView} to {@link #targetRect} area.
     */

    public void updateTargetViewLocation(@NonNull View targetView, int l, int t, int rippleViewSize, boolean rippleClipToTarget, OnRippleViewLocationUpdatedCallback callback) {
        Bitmap bitmap = ViewDrawingCacheUtils.getDrawingCache(targetView);
        updateTargetViewLocation(bitmap, l, t, rippleViewSize, rippleClipToTarget, callback);
    }

    /**
     * Update the target view's location.
     *
     * @param targetView         target
     * @param l                  the left margin
     * @param t                  the top margin
     * @param listener           listener for initializing {@link #rippleViewView}'s size
     * @param rippleClipToTarget true, clip {@link #rippleViewView} to {@link #targetRect} area.
     */

    public void updateTargetViewLocation(@NonNull View targetView, int l, int t, OnInitRippleViewSizeListener listener, boolean rippleClipToTarget, OnRippleViewLocationUpdatedCallback callback) {
        Bitmap bitmap = ViewDrawingCacheUtils.getDrawingCache(targetView);
        int size = listener == null ? getResources().getDimensionPixelSize(R.dimen.guidance_default_ripple_size) : listener.onInitializeRippleViewSize(bitmap);
        updateTargetViewLocation(bitmap, l, t, size, rippleClipToTarget, callback);
    }

    public void updateTargetViewLocation(Bitmap bitmap, int l, int t, int rippleViewSize, boolean rippleClipToTarget, OnRippleViewLocationUpdatedCallback callback) {
        curStepIndex++;
        if (bitmap == null)
            return;
        targetRect.set(l, t, l + bitmap.getWidth(), t + bitmap.getHeight());
        ViewGroup.LayoutParams params = targetView.getLayoutParams();
        params.width = targetRect.width();
        params.height = targetRect.height();
        if (params instanceof MarginLayoutParams) {
            ((MarginLayoutParams) params).leftMargin = targetRect.left;
            ((MarginLayoutParams) params).topMargin = targetRect.top;
        }
        targetView.setLayoutParams(params);
        targetView.setImageBitmap(bitmap);
        updateRippleViewLocation(rippleViewSize, callback);
        if (rippleClipToTarget)
            rippleViewView.setClip(targetRect.width(), targetRect.height());
        else
            rippleViewView.setClip(-1, -1);
    }

    /**
     * Update ripple view's location.
     *
     * @param size     size
     * @param callback call back when the ripple view's location was updated.
     */

    private void updateRippleViewLocation(int size, OnRippleViewLocationUpdatedCallback callback) {
        if (size < 0)
            throw new IllegalArgumentException("Bad params:size is less than zero.");
        ViewGroup.LayoutParams params = rippleViewView.getLayoutParams();
        params.width = size;
        params.height = size;
        if (params instanceof MarginLayoutParams) {
            ((MarginLayoutParams) params).leftMargin = (targetRect.left + targetRect.right - size) / 2;
            ((MarginLayoutParams) params).topMargin = (targetRect.top + targetRect.bottom - size) / 2;
        }
        rippleViewView.setLayoutParams(params);
        if (callback != null)
            callback.onRippleViewLocationUpdated(rippleViewView, targetRect);
    }


targetView——需要被引导的view。


详细逻辑步骤:


  • 1、获取targetView在屏幕中的坐标位置

  • 2、获取targetView的drawingCache。

方法一:获取targetView的drawingCache。


public static Bitmap getDrawingCache(@NonNull View view) {
        view.setDrawingCacheEnabled(true);
        Bitmap bitmap = view.getDrawingCache();
        view.setDrawingCacheEnabled(false);
        return bitmap;
    }


方法二:让targetView在我们自己创建的画布上画一遍。


public static Bitmap getDrawingCache(@NonNull View view) {
        int width = view.getWidth();
        int height = view.getHeight();
        if (width + height == 0) {
            view.measure(View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
            width = view.getMeasuredWidth();
            height = view.getMeasuredHeight();
        }
        Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(bitmap);
        view.draw(canvas);
        return bitmap;
    }


  • 3、新建一个ImageView并加载第2部中获取到的Bitmap。

  • 4、添加水波纹动画view。

Android 强大实用的功能引导组件三、3种展示功能引导方式


1、普通view方式GuidancePopupWindow

https://github.com/JustinRoom/GuidanceDemo/blob/master/guidanceLibrary/src/main/java/jsc/kit/guidance/GuidancePopupWindow.java


原理:


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


public GuidancePopupWindow(@NonNull Activity activity{
        this(activity, 0x99000000);
    }

    public GuidancePopupWindow(@NonNull Activity activity, @ColorInt int backgroundColor{
        this.activity = activity;
        guidanceLayout = new GuidanceLayout(activity);
        guidanceLayout.setId(R.id.guidance_default_layout_id);
        guidanceLayout.setBackgroundColor(backgroundColor);
        guidanceLayout.setTargetClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v
{
                if (listener == null || !listener.onTargetClick(guidanceLayout))
                    dismiss();
            }
        });
    }

    public void show() {
        show(SHOW_IN_CONTENT);
    }

    public void show(@ShowType int showType{
        this.curShowType = showType;
        switch (curShowType) {
            case SHOW_IN_CONTENT:
                //找到根布局中id为android.R.id.content的ViewGroup
                //添加GuidanceLayout控件
                FrameLayout contentLayout = activity.findViewById(android.R.id.content);
                if (!isGuidanceLayoutAdded(guidanceLayout)) {
                    contentLayout.addView(guidanceLayout);
                }
                break;
            case SHOW_IN_WINDOW:
                //此模式下很多权限方面的坑,不建议使用
                WindowManager.LayoutParams params = new WindowManager.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                    params.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
                } else {
                    params.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
                }
                activity.getWindow().getWindowManager().addView(guidanceLayout, params);
                break;
        }
    }


TYPE_APPLICATION_OVERLAY、TYPE_SYSTEM_ALERT需要android.permission.SYSTEM_ALERT_WINDOW权限,在6.0系统及以下,只要在AndroidManifest.xml文件中声名即可;在6.0以上系统中,我们需要主动申请权限,申请方法如下:


public static boolean checkOverlayPermission(@NonNull FragmentActivity activity, int requestCode){
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M)
            return true;
        if (!Settings.canDrawOverlays(activity)) {
            Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION);
            intent.setData(Uri.parse("package:" + activity.getPackageName()));
            activity.startActivityForResult(intent, requestCode);
        }
        return true;
    }


使用示例


private void showContentGuidance() {
        final GuidancePopupWindow popupWindow = new GuidancePopupWindow(getActivity());
        popupWindow.setTargetClickListener(new OnTargetClickListener() {
            @Override
            public boolean onTargetClick(GuidanceLayout layout) {
                Toast.makeText(layout.getContext(), "clicked me", Toast.LENGTH_SHORT).show();
                switch (layout.getCurStepIndex()) {
                    case 0:
                        layout.removeAllCustomViews();
                        showStep(layout, R.id.item_layout_1);
                        return true;
                    case 1:
                        layout.removeAllCustomViews();
                        showStep(layout, R.id.item_layout_2);
                        return true;
                    case 2:
                        layout.removeAllCustomViews();
                        showStep(layout, R.id.item_layout_3);
                        return true;
                    default:
                        return false;
                }
            }
        });
        popupWindow.show();
        GuidanceLayout guidanceLayout = popupWindow.getGuidanceLayout();
        showStep(guidanceLayout, R.id.item_layout_0);
    }


Android 强大实用的功能引导组件2、Dialog方式GuidanceDialog。


https://github.com/JustinRoom/GuidanceDemo/blob/master/guidanceLibrary/src/main/java/jsc/kit/guidance/GuidanceDialog.java


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


super(context);
        setCancelable(false);
        setCanceledOnTouchOutside(false);
    }

    public GuidanceDialog(@NonNull Context context, int themeResId) {
        super(context, themeResId);
        setCancelable(false);
        setCanceledOnTouchOutside(false);
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        supportRequestWindowFeature(Window.FEATURE_NO_TITLE);
        guidanceLayout = new GuidanceLayout(getContext());
        guidanceLayout.setTargetClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (listener == null || !listener.onTargetClick(guidanceLayout))
                    dismiss();
            }
        });
        setContentView(guidanceLayout, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
        if (getWindow() != null) {
            //设置window背景,默认的背景会有Padding值,不能全屏。当然不一定要是透明,你可以设置其他背景,替换默认的背景即可。
            getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
            //一定要在setContentView之后调用,否则无效
            getWindow().setLayout(width, height);
        }
    }


使用示例:


private void showGuidanceDialog() {
        final GuidanceDialog dialog = new GuidanceDialog(getContext());
        dialog.setTargetClickListener(new OnTargetClickListener() {
            @Override
            public boolean onTargetClick(GuidanceLayout layout) {
                Toast.makeText(layout.getContext(), "clicked me", Toast.LENGTH_SHORT).show();
                switch (layout.getCurStepIndex()) {
                    case 0:
                        showStep(layout, R.id.item_layout_1);
                        return true;
                    case 1:
                        showStep(layout, R.id.item_layout_2);
                        return true;
                    case 2:
                        showStep(layout, R.id.item_layout_3);
                        return true;
                    default:
                        return false;
                }
            }
        });
        dialog.show();
        GuidanceLayout guidanceLayout = dialog.getGuidanceLayout();
        if (guidanceLayout == null)
            return;
        showStep(guidanceLayout, R.id.item_layout_0);
    }


公共方法:


private void showStep(GuidanceLayout layout, int targetViewId) {
        layout.removeAllCustomViews();
        showStep(layout, getView().findViewById(targetViewId));
    }

    private void showStep(GuidanceLayout guidanceLayout, View target) {
        Context context = guidanceLayout.getContext();
        int statusBarHeight = ViewDrawingCacheUtils.getStatusBarHeight(context);
        int actionBarHeight = ViewDrawingCacheUtils.getActionBarSize(context);
        int[] location = ViewDrawingCacheUtils.getWindowLocation(target);
        guidanceLayout.updateTargetViewLocation(
                target, location[0],
                location[1] - statusBarHeight,
                new GuidanceLayout.OnInitRippleViewSizeListener() {
                    @Override
                    public int onInitializeRippleViewSize(@NonNull Bitmap bitmap) {
                        return bitmap.getHeight();
                    }
                },
                true,
                new GuidanceLayout.OnRippleViewLocationUpdatedCallback() {
                    @Override
                    public void onRippleViewLocationUpdated(@NonNull GuidanceRippleView rippleView, @NonNull Rect targetRect) {

                    }
                });

        ImageView imageView = new ImageView(guidanceLayout.getContext());
        imageView.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
        imageView.setImageResource(R.drawable.hand_o_up);
        guidanceLayout.addCustomView(imageView, new GuidanceLayout.OnCustomViewAddListener<ImageView>() {

            @Override
            public void onViewInit(@NonNull ImageView customView, @NonNull FrameLayout.LayoutParams params, @NonNull Rect targetRect) {
                customView.measure(00);
                params.topMargin = targetRect.bottom + 12;
                params.leftMargin = targetRect.left - (customView.getMeasuredWidth() - targetRect.width()) / 2;
            }

            @Override
            public void onViewAdded(@NonNull ImageView customView, @NonNull Rect targetRect) {
                ObjectAnimator animator = ObjectAnimator.ofFloat(customView, View.TRANSLATION_Y, 0320)
                        .setDuration(1200);
                animator.setRepeatCount(-1);
                animator.start();
            }
        }, null);
    }


Android 强大实用的功能引导组件3、WindowManager添加View方式WindowManager。


https://www.jianshu.com/p/c1aaddd93245


此种方式有很多坑,不建议用此方式。


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


童鞋们给个

以上是关于Android 强大实用的功能引导组件的主要内容,如果未能解决你的问题,请参考以下文章

Android 实用代码片段

译文:18个实用的JavaScript代码片段,助你快速处理日常编程任务

JavaScript实用功能代码片段

Android零基础入门第69节:ViewPager快速实现引导页

前端开发:美观功能强大基于Bootstrap的上传组件(FileInput)

小程序各种功能代码片段整理---持续更新