Android引导页——可跟踪定位的引导页协调器

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/lylwo317/article/details/52262408

需求

由于项目需要实现指导新用户使用App功能的引导页。

效果图(这是实现后的截图了)
这里写图片描述

分析与解决

  1. 显示引导页
    方案:只要inflate相应的layout,并添加到decorView里面。
  2. 指示的View要对齐到相应功能的按钮上
    方案:监听两个view的位置,并通过移动,将指示的View移动到目标View上

Demo效果图

my_show_case_layout.xml//引导页

MainActivity

将引导页显示在MainActivity上

代码

主要的思路就是通过监听两个VIew在屏幕中的坐标,通过计算来获取到指示View需要移动的x轴和y轴的距离。然后移动即可。具体可以看Demo:ShowcaseCoordinator

为了移动对齐,必须要有三个元素:目标View,指示View,需要移动的View。

具体实现就一个类,具体如下

ShowcaseCoordinator.java

package com.kevin.showcase;

import android.app.Activity;
import android.content.Context;
import android.os.Build;
import android.util.DisplayMetrics;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.view.ViewTreeObserver;

import java.util.ArrayList;
import java.util.List;

/**
 * ShowcaseCoordinator will inflate {@link #showcaseLayout},and move the needMoveView which contain the indicatorView,
 * let the indicatorView right above the targetView.
 * @author Kevin Xie ([email protected])
 * @since 2016/8/1.
 */
public class ShowcaseCoordinator
{

    private Context context;

    /**
     * Special which view in {@link #showcaseLayout} to dismiss showcase. Default view is {@link #showcaseLayout}
     **/
    private int dismissViewId = 0;

    private List<Showcase> showcaseList;

    private ViewGroup showcaseLayout;

    private OnDismissShowcaseLayoutListener dismissShowcaseLayoutListener = null;

    enum ViewType{

        INDICATOR_VIEW,

        TARGET_VIEW
    }
    private View.OnClickListener dismissListener = new View.OnClickListener()
    {
        @Override
        public void onClick(View v)
        {
            dismiss();
        }
    };

    private void setOnDismissShowcaseLayoutListener(OnDismissShowcaseLayoutListener listener)
    {
        dismissShowcaseLayoutListener = listener;
    }

    public ShowcaseCoordinator(Context context, int layoutResId)
    {
        this.context = context;
        LayoutInflater inflater = LayoutInflater.from(this.context);
        showcaseLayout = (ViewGroup)inflater.inflate(layoutResId, null);
    }

    /**
     * Remove {@link #showcaseLayout} from it's parent
     */
    private void dismiss()
    {
        if (dismissShowcaseLayoutListener != null)
        {
            dismissShowcaseLayoutListener.onDismiss();
        }
        ViewParent vp = showcaseLayout.getParent();
        if (vp instanceof ViewGroup)
        {
            ((ViewGroup)vp).removeView(showcaseLayout);
        }
    }

    /**
     * Get showcaseLayout
     * @return return {@link #showcaseLayout}
     */
    public ViewGroup getShowcaseLayout()
    {
        return showcaseLayout;
    }


    //////////////////////////////////////////////////dismiss showcaseLayout///////////////////////////////////////////////////

    private void bindDismissListenerToView()
    {
        if (dismissViewId == 0)
        {
            showcaseLayout.setOnClickListener(dismissListener);
        }else
        {
            View dismissShowcaseLayoutView = showcaseLayout.findViewById(dismissViewId);
            if (dismissShowcaseLayoutView != null)
            {
                dismissShowcaseLayoutView.setOnClickListener(dismissListener);
            }
        }
    }

    private void setViewToDismissShowcaseLayout(int resId)
    {
        this.dismissViewId = resId;
    }

    //\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\



    ////////////////////////////////////////////////////locations change////////////////////////////////////////////////////////////

    private void updateViewLocations(View view, ViewType viewType, NotifyReady notifyReady)
    {
        if (view.getWidth() != 0)
        {
            int[] viewLocations = new int[2];
            view.getLocationOnScreen(viewLocations);

            switch (viewType)
            {
                case INDICATOR_VIEW:
                    notifyReady.setIndicatorView(view);
                    notifyReady.setIndicatorLocations(viewLocations);
                    break;
                case TARGET_VIEW:
                    notifyReady.setTargetView(view);
                    notifyReady.setTargetLocations(viewLocations);
                    break;
            }
            notifyReady.ready();
        }
    }

    /**
     * When call {@link Builder#build()},this method will be invoked.
     */
    private void onBuild()
    {
        bindShowcaseListener();
        bindDismissListenerToView();
    }

    /**
     * Listen targetView and indicatorView locations.
     */
    private void bindShowcaseListener()
    {
        if (showcaseList != null)
        {
            for (Showcase viewIdRecord : showcaseList)
            {
                NotifyReady ready = new NotifyReady(context);

                View indicatorView = showcaseLayout.findViewById(viewIdRecord.getIndicatorViewId());
                View targetView = viewIdRecord.getTargetView();
                View needMoveView = showcaseLayout.findViewById(viewIdRecord.getNeedMoveViewId());

                if (indicatorView != null && targetView != null && needMoveView != null)
                {
                    listenIndicatorViewLocations(indicatorView, needMoveView, ready);
                    listenTargetViewLocations(targetView,ready);
                }
            }
        }
    }

    /**
     * Listen indicatorView locations.
     */
    private void listenIndicatorViewLocations(final View indicatorView, final View needMoveView, final NotifyReady notifyReady)
    {

        indicatorView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener()
        {
            @Override
            public void onGlobalLayout()
            {
                if (indicatorView.getWidth() != 0)
                {
                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN)
                    {
                        indicatorView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
                    }
                    else
                    {
                        indicatorView.getViewTreeObserver().removeGlobalOnLayoutListener(this);
                    }
                    updateViewLocations(indicatorView, ViewType.INDICATOR_VIEW, notifyReady);
                }
            }
        });

        notifyReady.setNeedMoveView(needMoveView);
    }

    /**
     * Listen targetView locations.
     */
    private void listenTargetViewLocations(final View targetView, final NotifyReady notifyReady)
    {

        targetView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener()
        {
            @Override
            public void onGlobalLayout()
            {
                if (targetView.getWidth() != 0)
                {
                    updateViewLocations(targetView, ViewType.TARGET_VIEW, notifyReady);
                }
            }
        });
        if (targetView.getWidth()!=0)
        {
            updateViewLocations(targetView,ViewType.TARGET_VIEW,notifyReady);
        }
    }

    /**
     * Add showcase list to {@link #showcaseList}
     *
     * @param showcaseList showcase collections
     */
    private void addShowcaseList(List<Showcase> showcaseList)
    {
        if (this.showcaseList == null)
        {
            this.showcaseList = new ArrayList<>();
        }
        this.showcaseList.addAll(showcaseList);
    }

    /**
     * Add showcase to {@link #showcaseList}
     *
     * @param showcase Showcase instance
     */
    private void addShowcase(Showcase showcase)
    {
        if (showcaseList == null)
        {
            showcaseList = new ArrayList<>();
        }
        this.showcaseList.add(showcase);
    }

    //\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\

    /**
     * When the positions of targetView and indicatorView are set. {@link NotifyReady#ready()} will move the {@link #needMoveView} position right above
     * {@link #targetView}
     */
    private static class NotifyReady
    {
        private int[] indicatorLocations;
        private int[] targetLocations;
        private View needMoveView;

        private View targetView;

        private View indicatorView;

        private float oldX;
        private float oldY;

        private DisplayMetrics displayMetrics;

        public void setTargetView(View targetView)
        {
            this.targetView = targetView;
        }

        public void setNeedMoveView(View needChangeView)
        {
            this.needMoveView = needChangeView;
        }

        public NotifyReady(Context context)
        {
            displayMetrics = context.getResources().getDisplayMetrics();
        }

        public void setIndicatorLocations(int[] indicatorLocations)
        {
            this.indicatorLocations = indicatorLocations;
        }

        public void setTargetLocations(int[] targetLocations)
        {
            this.targetLocations = targetLocations;
        }

        /**
         * Move the {@link #needMoveView} position right above
         */
        public void ready()
        {
            if (indicatorLocations != null && targetLocations != null && needMoveView != null && indicatorView != null && targetView != null)
            {
                float x = targetLocations[0]%displayMetrics.widthPixels-indicatorLocations[0]%displayMetrics.widthPixels + (targetView.getWidth() - indicatorView.getWidth()) / 2f;
                float y = targetLocations[1] - indicatorLocations[1] + (targetView.getHeight() - indicatorView.getHeight()) / 2f;

                //if position no change,don't move it.
                if (x == oldX && y == oldY)
                {
                    return;
                }else
                {
                    oldX = x;
                    oldY = y;
                }
                needMoveView.setTranslationX(x);
                needMoveView.setTranslationY(y);
            }
        }

        public void setIndicatorView(View indicatorView)
        {
            this.indicatorView = indicatorView;
        }
    }

    /**
     * To build {@link ShowcaseCoordinator} instance.
     */
    public static class Builder {
        private ShowcaseCoordinator showcaseCoordinator;

        private ViewGroup parent;

        private int parentIndex;

        /**
         * ShowcaseCoordinator Builder.
         * @param activity activity
         * @param layoutResId showcaseLayout resource id
         */
        public Builder(Activity activity,int layoutResId)
        {
            if (activity == null)
            {
                throw new NullPointerException("Activity can not be Null!");
            }
            showcaseCoordinator = new ShowcaseCoordinator(activity,layoutResId);
            parent = (ViewGroup)(activity.getWindow().getDecorView());
            parentIndex = parent.getChildCount();
        }

        /**
         * Build ShowcaseCoordinator
         * @return showcaseCoordinator
         */
        public ShowcaseCoordinator build()
        {
            showcaseCoordinator.onBuild();
            parent.addView(showcaseCoordinator.getShowcaseLayout(), parentIndex);
            return showcaseCoordinator;
        }

        /**
         * Add Showcase by list
         * @param showcaseList Showcase's list
         * @return builder
         */
        public Builder addShowcaseList(List<Showcase> showcaseList)
        {
            showcaseCoordinator.addShowcaseList(showcaseList);
            return this;
        }

        /**
         * Add Showcase
         * @param targetView which view that you want indicatorView align at.
         * @param indicatorViewId which view you want to right above targetView.
         * @param needMoveViewId which view you want to move actually.
         * @return builder
         */
        public Builder addShowcase(View targetView, int indicatorViewId, int needMoveViewId)
        {

            showcaseCoordinator.addShowcase(new Showcase(targetView, indicatorViewId, needMoveViewId));
            return this;
        }

        /**
         * Set view to dismiss showcaseLayout.
         * @param viewId which you want to dismiss showcaseLayout.
         * @return builder
         */
        public Builder setViewToDismissShowcaseLayout(int viewId)
        {
            showcaseCoordinator.setViewToDismissShowcaseLayout(viewId);
            return this;
        }

        /**
         * Listen ShowcaseLayout dismiss event.
         * @param listener ShowcaseLayout dismiss listener.
         * @return builder
         */
        public Builder setOnDismissShowcaseLayoutListener(OnDismissShowcaseLayoutListener listener)
        {
            showcaseCoordinator.setOnDismissShowcaseLayoutListener(listener);
            return this;
        }
    }

    /**
     * This is a collection that what you want to coordinate views.
     */
    public static class Showcase
    {
        private View targetView;
        private int indicatorViewId;
        private int needMoveViewId;

        /**
         * Constructing Showcase instance.
         * @param targetView which view that you want {@link #indicatorViewId} align at.
         * @param indicatorViewId which view you want to right above {@link #targetView}.
         * @param needMoveViewId which view you want to move actually.
         */
        public Showcase(View targetView, int indicatorViewId, int needMoveViewId)
        {
            this.targetView = targetView;
            this.indicatorViewId = indicatorViewId;
            this.needMoveViewId = needMoveViewId;
        }

        public View getTargetView()
        {
            return targetView;
        }

        public int getIndicatorViewId()
        {
            return indicatorViewId;
        }

        public int getNeedMoveViewId()
        {
            return needMoveViewId;
        }
    }

    /**
     * Listen showcaseLayout dismiss event.
     */
    public interface OnDismissShowcaseLayoutListener
    {
        void onDismiss();
    }

}

使用非常简单

MainActivity.java

package com.kevin.showcase;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity
{
    private TextView tvHelloFirst;
    private TextView tvHelloSecond;
    private TextView tvHelloThird;

    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        tvHelloFirst = (TextView) findViewById(R.id.tv_hello_first);
        tvHelloSecond = (TextView)findViewById(R.id.tv_hello_second);
        tvHelloThird = (TextView)findViewById(R.id.tv_hello_third);

        new ShowcaseCoordinator.Builder(this, R.layout.my_show_case_layout)
        //addShowcase(目标View,指示View,需要移动的View)
                .addShowcase(tvHelloFirst, R.id.imageView_first, R.id.llyt_move_first)
                .addShowcase(tvHelloSecond, R.id.imageView_second, R.id.llyt_move_second)
                .addShowcase(tvHelloThird, R.id.imageView_third, R.id.llyt_move_third)
                .build();
    }
}

Demo Github地址:ShowcaseCoordinator

猜你喜欢

转载自blog.csdn.net/lylwo317/article/details/52262408