Android 技术博客(3):android 悬浮窗菜单,可用于显示在 launcher 或者 activity。

DotImageView.java

package com.yw.game.floatmenu;

import android.animation.Animator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Camera;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Rect;
import android.support.annotation.Nullable;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.View;
import android.view.animation.LinearInterpolator;


/**
 * Created by wengyiming on 2017/7/21.
 */

/**
 * 00%=FF(不透明)    5%=F2    10%=E5    15%=D8    20%=CC    25%=BF    30%=B2    35%=A5    40%=99    45%=8c    50%=7F
 * 55%=72    60%=66    65%=59    70%=4c    75%=3F    80%=33    85%=21    90%=19    95%=0c    100%=00(全透明)
 */
public class DotImageView extends View {
    private static final String TAG = DotImageView.class.getSimpleName();
    public static final int NORMAL = 0;//不隐藏
    public static final int HIDE_LEFT = 1;//左边隐藏
    public static final int HIDE_RIGHT = 2;//右边隐藏
    private Paint mPaint;//用于画anything

    private Paint mPaintBg;//用于画anything
    private String dotNum = null;//红点数字
    private float mAlphValue;//透明度动画值
    private float mRolateValue = 1f;//旋转动画值
    private boolean inited = false;//标记透明动画是否执行过,防止因onreseme 切换导致重复执行


    private Bitmap mBitmap;//logo
    private final int mLogoBackgroundRadius = dip2px(25);//logo的灰色背景圆的半径
    private final int mLogoWhiteRadius = dip2px(20);//logo的白色背景的圆的半径
    private final int mRedPointRadiusWithNum = dip2px(6);//红点圆半径
    private final int mRedPointRadius = dip2px(3);//红点圆半径
    private final int mRedPointOffset = dip2px(10);//红点对logo的偏移量,比如左红点就是logo中心的 x - mRedPointOffset

    private boolean isDraging = false;//是否 绘制旋转放大动画,只有 非停靠边缘才绘制
    private float scaleOffset;//放大偏移值
    private ValueAnimator mDragingValueAnimator;//放大、旋转 属性动画
    private LinearInterpolator mLinearInterpolator = new LinearInterpolator();//通用用加速器
    public boolean mDrawDarkBg = true;//是否绘制黑色背景,当菜单关闭时,才绘制灰色背景
    private static final float hideOffset = 0.4f;//往左右隐藏多少宽度的偏移值, 隐藏宽度的0.4
    private Camera mCamera;//camera用于执行3D动画

    private boolean mDrawNum = false;//只绘制红点还是红点+白色数字

    private int mStatus = NORMAL;//0 正常,1 左,2右,3 中间方法旋转
    private int mLastStatus = mStatus;
    private Matrix mMatrix;
    private boolean mIsResetPosition;

    private int mBgColor = 0x99000000;


    public void setBgColor(int bgColor) {
        mBgColor = bgColor;
    }


    public void setDrawNum(boolean drawNum) {
        this.mDrawNum = drawNum;
    }

    public void setDrawDarkBg(boolean drawDarkBg) {
        mDrawDarkBg = drawDarkBg;
        invalidate();
    }

    public int getStatus() {
        return mStatus;
    }


    public void setStatus(int status) {
        this.mStatus = status;
        isDraging = false;
        if (this.mStatus != NORMAL) {
            setDrawNum(mDrawNum);
            this.mDrawDarkBg = true;
        }
        invalidate();

    }

    public void setBitmap(Bitmap bitmap) {
        mBitmap = bitmap;
    }

    public DotImageView(Context context, Bitmap bitmap) {
        super(context);
        this.mBitmap = bitmap;
        init();
    }


    public DotImageView(Context context) {
        super(context);
        init();
    }

    public DotImageView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public DotImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init() {
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        mPaint.setTextSize(sp2px(10));
        mPaint.setStyle(Paint.Style.FILL);

        mPaintBg = new Paint();
        mPaintBg.setAntiAlias(true);
        mPaintBg.setStyle(Paint.Style.FILL);
        mPaintBg.setColor(mBgColor);//60% 黑色背景 (透明度 40%)

        mCamera = new Camera();
        mMatrix = new Matrix();
    }

    /**
     * 这个方法是否有优化空间
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int wh = mLogoBackgroundRadius * 2;
        setMeasuredDimension(wh, wh);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        float centryX = getWidth() / 2;
        float centryY = getHeight() / 2;
        canvas.save();//保存一份快照,方便后面恢复
        mCamera.save();
        if (mStatus == NORMAL) {
            if (mLastStatus != NORMAL) {
                canvas.restore();//恢复画布的原始快照
                mCamera.restore();
            }

            if (isDraging) {
                //如果当前是拖动状态则放大并旋转
                canvas.scale((scaleOffset + 1f), (scaleOffset + 1f), getWidth() / 2, getHeight() / 2);
                if (mIsResetPosition) {
                    //手指拖动后离开屏幕复位时使用 x轴旋转 3d动画
                    mCamera.save();
                    mCamera.rotateX(720 * scaleOffset);//0-720度 最多转两圈
                    mCamera.getMatrix(mMatrix);

                    mMatrix.preTranslate(-getWidth() / 2, -getHeight() / 2);
                    mMatrix.postTranslate(getWidth() / 2, getHeight() / 2);
                    canvas.concat(mMatrix);
                    mCamera.restore();
                } else {
                    //手指拖动且手指未离开屏幕则使用 绕图心2d旋转动画
                    canvas.rotate(60 * mRolateValue, getWidth() / 2, getHeight() / 2);
                }
            }


        } else if (mStatus == HIDE_LEFT) {
            canvas.translate(-getWidth() * hideOffset, 0);
            canvas.rotate(-45, getWidth() / 2, getHeight() / 2);

        } else if (mStatus == HIDE_RIGHT) {
            canvas.translate(getWidth() * hideOffset, 0);
            canvas.rotate(45, getWidth() / 2, getHeight() / 2);
        }
        canvas.save();
        if (!isDraging) {
            if (mDrawDarkBg) {
                mPaintBg.setColor(mBgColor);
                canvas.drawCircle(centryX, centryY, mLogoBackgroundRadius, mPaintBg);
                // 60% 白色 (透明度 40%)
                mPaint.setColor(0x99ffffff);
            } else {
                //100% 白色背景 (透明度 0%)
                mPaint.setColor(0xFFFFFFFF);
            }
            if (mAlphValue != 0) {
                mPaint.setAlpha((int) (mAlphValue * 255));
            }
            canvas.drawCircle(centryX, centryY, mLogoWhiteRadius, mPaint);
        }

        canvas.restore();
        //100% 白色背景 (透明度 0%)
        mPaint.setColor(0xFFFFFFFF);
        int left = (int) (centryX - mBitmap.getWidth() / 2);
        int top = (int) (centryY - mBitmap.getHeight() / 2);
        canvas.drawBitmap(mBitmap, left, top, mPaint);


        if (!TextUtils.isEmpty(dotNum)) {
            int readPointRadus = (mDrawNum ? mRedPointRadiusWithNum : mRedPointRadius);
            mPaint.setColor(Color.RED);
            if (mStatus == HIDE_LEFT) {
                canvas.drawCircle(centryX + mRedPointOffset, centryY - mRedPointOffset, readPointRadus, mPaint);
                if (mDrawNum) {
                    mPaint.setColor(Color.WHITE);
                    canvas.drawText(dotNum, centryX + mRedPointOffset - getTextWidth(dotNum, mPaint) / 2, centryY - mRedPointOffset + getTextHeight(dotNum, mPaint) / 2, mPaint);
                }
            } else if (mStatus == HIDE_RIGHT) {
                canvas.drawCircle(centryX - mRedPointOffset, centryY - mRedPointOffset, readPointRadus, mPaint);
                if (mDrawNum) {
                    mPaint.setColor(Color.WHITE);
                    canvas.drawText(dotNum, centryX - mRedPointOffset - getTextWidth(dotNum, mPaint) / 2, centryY - mRedPointOffset + getTextHeight(dotNum, mPaint) / 2, mPaint);
                }
            } else {
                if (mLastStatus == HIDE_LEFT) {
                    canvas.drawCircle(centryX + mRedPointOffset, centryY - mRedPointOffset, readPointRadus, mPaint);
                    if (mDrawNum) {
                        mPaint.setColor(Color.WHITE);
                        canvas.drawText(dotNum, centryX + mRedPointOffset - getTextWidth(dotNum, mPaint) / 2, centryY - mRedPointOffset + getTextHeight(dotNum, mPaint) / 2, mPaint);
                    }
                } else if (mLastStatus == HIDE_RIGHT) {
                    canvas.drawCircle(centryX - mRedPointOffset, centryY - mRedPointOffset, readPointRadus, mPaint);
                    if (mDrawNum) {
                        mPaint.setColor(Color.WHITE);
                        canvas.drawText(dotNum, centryX - mRedPointOffset - getTextWidth(dotNum, mPaint) / 2, centryY - mRedPointOffset + getTextHeight(dotNum, mPaint) / 2, mPaint);
                    }
                }
            }
        }
        mLastStatus = mStatus;
    }


    public void setDotNum(int num, Animator.AnimatorListener l) {
        if (!inited) {
            startAnim(num, l);
        } else {
            refreshDot(num);
        }
    }

    private void refreshDot(int num) {
        if (num > 0) {
            String dotNumTmp = String.valueOf(num);
            if (!TextUtils.equals(dotNum, dotNumTmp)) {
                dotNum = dotNumTmp;
                invalidate();
            }
        } else {
            dotNum = null;
        }
    }


    public void startAnim(final int num, Animator.AnimatorListener l) {
        ValueAnimator valueAnimator = ValueAnimator.ofFloat(1.f, 0.6f, 1f, 0.6f);
        valueAnimator.setInterpolator(mLinearInterpolator);
        valueAnimator.setDuration(3000);
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                mAlphValue = (float) animation.getAnimatedValue();
                invalidate();

            }
        });
        valueAnimator.addListener(l);
        valueAnimator.addListener(new Animator.AnimatorListener() {
            @Override
            public void onAnimationStart(Animator animation) {

            }

            @Override
            public void onAnimationEnd(Animator animation) {
                inited = true;
                refreshDot(num);
                mAlphValue = 0;

            }

            @Override
            public void onAnimationCancel(Animator animation) {
                mAlphValue = 0;
            }

            @Override
            public void onAnimationRepeat(Animator animation) {

            }
        });
        valueAnimator.start();
    }

    public void setDraging(boolean draging, float offset, boolean isResetPosition) {
        isDraging = draging;
        this.mIsResetPosition = isResetPosition;
        if (offset > 0 && offset != this.scaleOffset) {
            this.scaleOffset = offset;
        }
        if (isDraging && mStatus == NORMAL) {
            if (mDragingValueAnimator != null) {
                if (mDragingValueAnimator.isRunning()) return;
            }
            mDragingValueAnimator = ValueAnimator.ofFloat(0, 6f, 12f, 0f);
            mDragingValueAnimator.setInterpolator(mLinearInterpolator);
            mDragingValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    mRolateValue = (float) animation.getAnimatedValue();
                    invalidate();
                }
            });
            mDragingValueAnimator.addListener(new Animator.AnimatorListener() {
                @Override
                public void onAnimationStart(Animator animation) {

                }

                @Override
                public void onAnimationEnd(Animator animation) {
                    isDraging = false;
                    mIsResetPosition = false;
                }

                @Override
                public void onAnimationCancel(Animator animation) {

                }

                @Override
                public void onAnimationRepeat(Animator animation) {

                }
            });
            mDragingValueAnimator.setDuration(1000);
            mDragingValueAnimator.start();
        }
    }

    private int dip2px(float dipValue) {
        final float scale = getContext().getResources().getDisplayMetrics().density;
        return (int) (dipValue * scale + 0.5f);
    }

    private int sp2px(float spValue) {
        final float fontScale = getContext().getResources().getDisplayMetrics().scaledDensity;
        return (int) (spValue * fontScale + 0.5f);
    }

    private float getTextHeight(String text, Paint paint) {
        Rect rect = new Rect();
        paint.getTextBounds(text, 0, text.length(), rect);
        return rect.height() / 1.1f;
    }

    private float getTextWidth(String text, Paint paint) {
        return paint.measureText(text);
    }
}

FloatItem.java

package com.yw.game.floatmenu;

import android.graphics.Bitmap;
import android.graphics.Color;

/**
 * Created by wengyiming on 2017/7/21.
 */

public class FloatItem {
    public String title;
    public int titleColor = Color.BLACK;
    public int bgColor = Color.WHITE;
    public Bitmap icon;
    public String dotNum = null;

    public FloatItem(String title, int titleColor, int bgColor, Bitmap icon, String dotNum) {
        this.title = title;
        this.titleColor = titleColor;
        this.bgColor = bgColor;
        this.icon = icon;
        this.dotNum = dotNum;
    }

    public String getDotNum() {
        return dotNum;
    }


    public FloatItem(String title, int titleColor, int bgColor, Bitmap bitmap) {
        this.title = title;
        this.titleColor = titleColor;
        this.bgColor = bgColor;
        this.icon = bitmap;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public int getTitleColor() {
        return titleColor;
    }

    public void setTitleColor(int titleColor) {
        this.titleColor = titleColor;
    }

    public int getBgColor() {
        return bgColor;
    }

    public void setBgColor(int bgColor) {
        this.bgColor = bgColor;
    }

    public Bitmap getIcon() {
        return icon;
    }

    public void setIcon(Bitmap icon) {
        this.icon = icon;
    }


    @Override
    public boolean equals(Object obj) {
        if (obj == null) {
            return false;
        }
        if (obj == this) return true;

        if (obj instanceof FloatItem) {
            FloatItem floatItem = (FloatItem) obj;
            return floatItem.title.equals(this.title);
        } else {
            return false;
        }
    }

    @Override
    public int hashCode() {
        return title.hashCode();
    }

    @Override
    public String toString() {
        return "FloatItem{" +
                "title='" + title + '\'' +
                ", titleColor=" + titleColor +
                ", bgColor=" + bgColor +
                ", icon=" + icon +
                ", dotNum='" + dotNum + '\'' +
                '}';
    }
}

FloatLogoMenu.java

package com.yw.game.floatmenu;

import android.animation.Animator;
import android.animation.ValueAnimator;
import android.app.Activity;
import android.content.Context;
import android.content.SharedPreferences;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.PixelFormat;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Bundle;
import android.os.CountDownTimer;
import android.os.Handler;
import android.os.Looper;
import android.text.TextUtils;
import android.util.Log;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnTouchListener;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.view.animation.Interpolator;
import android.view.animation.LinearInterpolator;
import android.widget.LinearLayout;


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

/**
 * Created by wengyiming on 2017/7/20.
 */
public class FloatLogoMenu {
    /**
     * 记录 logo 停放的位置,以备下次恢复
     */
    private static final String LOCATION_X = "hintLocation";
    private static final String LOCATION_Y = "locationY";

    /**
     * 悬浮球 坐落 左 右 标记
     */
    public static final int LEFT = 0;
    public static final int RIGHT = 1;

    /**
     * 记录系统状态栏的高度
     */
    private int mStatusBarHeight;
    /**
     * 记录当前手指位置在屏幕上的横坐标值
     */
    private float mXInScreen;

    /**
     * 记录当前手指位置在屏幕上的纵坐标值
     */
    private float mYInScreen;

    /**
     * 记录手指按下时在屏幕上的横坐标的值
     */
    private float mXDownInScreen;

    /**
     * 记录手指按下时在屏幕上的纵坐标的值
     */
    private float mYDownInScreen;

    /**
     * 记录手指按下时在小悬浮窗的View上的横坐标的值
     */
    private float mXInView;

    /**
     * 记录手指按下时在小悬浮窗的View上的纵坐标的值
     */
    private float mYinview;

    /**
     * 记录屏幕的宽度
     */
    private int mScreenWidth;

    /**
     * 来自 activity 的 wManager
     */
    private WindowManager wManager;


    /**
     * 为 wManager 设置 LayoutParams
     */
    private WindowManager.LayoutParams wmParams;

    /**
     * 带透明度动画、旋转、放大的悬浮球
     */
    private DotImageView mFloatLogo;


    /**
     * 用于 定时 隐藏 logo的定时器
     */
    private CountDownTimer mHideTimer;


    /**
     * float menu的高度
     */
    private Handler mHandler = new Handler(Looper.getMainLooper());


    /**
     * 悬浮窗左右移动到默认位置 动画的 加速器
     */
    private Interpolator mLinearInterpolator = new LinearInterpolator();

    /**
     * 用于记录上次菜单打开的时间,判断时间间隔
     */
    private static double DOUBLE_CLICK_TIME = 0L;

    /**
     * 标记是否拖动中
     */
    private boolean isDraging = false;

    /**
     * 用于恢复悬浮球的location的属性动画值
     */
    private int mResetLocationValue;

    /**
     * 手指离开屏幕后 用于恢复 悬浮球的 logo 的左右位置
     */
    private Runnable updatePositionRunnable = new Runnable() {
        @Override
        public void run() {
            isDraging = true;
            checkPosition();
        }
    };

    /**
     * 这个事件不做任何事情、直接return false则 onclick 事件生效
     */
    private OnTouchListener mDefaultOnTouchListerner = new OnTouchListener() {
        @Override
        public boolean onTouch(View v, MotionEvent event) {
            isDraging = false;
            return false;
        }
    };

    /**
     * 这个事件用于处理移动、自定义点击或者其它事情,return true可以保证onclick事件失效
     */
    private OnTouchListener touchListener = new OnTouchListener() {
        @Override
        public boolean onTouch(View v, MotionEvent event) {
            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    floatEventDown(event);
                    break;
                case MotionEvent.ACTION_MOVE:
                    floatEventMove(event);
                    break;
                case MotionEvent.ACTION_UP:
                case MotionEvent.ACTION_CANCEL:
                    floatEventUp();
                    break;
            }
            return true;
        }
    };


    /**
     * 菜单背景颜色
     */
    private int mBackMenuColor = 0xffe4e3e1;

    /**
     * 是否绘制红点数字
     */
    private boolean mDrawRedPointNum;


    /**
     * 是否绘制圆形菜单项,false绘制方形
     */
    private boolean mCicleMenuBg;


    /**
     * R.drawable.yw_game_logo
     *
     * @param floatItems
     */
    private Bitmap mLogoRes;

    /**
     * 用于显示在 mActivity 上的 mActivity
     */
    private Context mActivity;

    /**
     * 菜单 点击、关闭 监听
     */
    private FloatMenuView.OnMenuClickListener mOnMenuClickListener;


    /**
     * 停靠默认位置
     */
    private int mDefaultLocation = RIGHT;


    /**
     * 悬浮窗 坐落 位置
     */
    private int mHintLocation = mDefaultLocation;


    /**
     * 用于记录菜单项内容
     */
    private List mFloatItems = new ArrayList<>();

    private LinearLayout rootViewRight;

    private LinearLayout rootView;

    private ValueAnimator valueAnimator;

    private boolean isExpaned = false;

    private Drawable mBackground;


    private FloatLogoMenu(Builder builder) {
        mBackMenuColor = builder.mBackMenuColor;
        mDrawRedPointNum = builder.mDrawRedPointNum;
        mCicleMenuBg = builder.mCicleMenuBg;
        mLogoRes = builder.mLogoRes;
        mActivity = builder.mActivity;
        mOnMenuClickListener = builder.mOnMenuClickListener;
        mDefaultLocation = builder.mDefaultLocation;
        mFloatItems = builder.mFloatItems;
        mBackground = builder.mDrawable;

//        if (mActivity == null || mActivity.isFinishing() || mActivity.getWindowManager() == null) {
//            throw new IllegalArgumentException("Activity = null, or Activity is isFinishing ,or this Activity`s  token is bad");
//        }

        if (mLogoRes == null) {
            throw new IllegalArgumentException("No logo found,you can setLogo/showWithLogo to set a FloatLogo ");
        }

        if (mFloatItems.isEmpty()) {
            throw new IllegalArgumentException("At least one menu item!");
        }

        initFloatWindow();
        initTimer();
        initFloat();

    }

    public void setFloatItemList(List floatItems) {
        this.mFloatItems = floatItems;
        caculateDotNum();
    }

    /**
     * 初始化悬浮球 window
     */
    private void initFloatWindow() {
        wmParams = new WindowManager.LayoutParams();
        if (mActivity instanceof Activity) {
            Activity activity = (Activity) mActivity;
            wManager = activity.getWindowManager();
            //类似dialog,寄托在activity的windows上,activity关闭时需要关闭当前float
            wmParams.type = WindowManager.LayoutParams.TYPE_APPLICATION;
        } else {
            wManager = (WindowManager) mActivity.getSystemService(Context.WINDOW_SERVICE);
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
                if (Build.VERSION.SDK_INT > 23) {
                    //在android7.1以上系统需要使用TYPE_PHONE类型 配合运行时权限
                    wmParams.type = WindowManager.LayoutParams.TYPE_PHONE;
                } else {
                    wmParams.type = WindowManager.LayoutParams.TYPE_TOAST;
                }
            } else {
                wmParams.type = WindowManager.LayoutParams.TYPE_PHONE;
            }
        }
        mScreenWidth = wManager.getDefaultDisplay().getWidth();
        int screenHeigth = wManager.getDefaultDisplay().getHeight();

        //判断状态栏是否显示 如果不显示则statusBarHeight为0
        mStatusBarHeight = dp2Px(25, mActivity);

        wmParams.format = PixelFormat.RGBA_8888;
        wmParams.gravity = Gravity.LEFT | Gravity.TOP;
        wmParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
        mHintLocation = getSetting(LOCATION_X, mDefaultLocation);
        int defaultY = ((screenHeigth - mStatusBarHeight) / 2) / 3;
        int y = getSetting(LOCATION_Y, defaultY);
        if (mHintLocation == LEFT) {
            wmParams.x = 0;
        } else {
            wmParams.x = mScreenWidth;
        }

        if (y != 0 && y != defaultY) {
            wmParams.y = y;
        } else {
            wmParams.y = defaultY;
        }
        wmParams.alpha = 1;
        wmParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
        wmParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
    }


    /**
     * 初始化悬浮球
     */
    private void initFloat() {
        genarateLeftLineLayout();
        genarateRightLineLayout();
        mFloatLogo = new DotImageView(mActivity, mLogoRes);
        mFloatLogo.setLayoutParams(new WindowManager.LayoutParams(dp2Px(50, mActivity), dp2Px(50, mActivity)));
        mFloatLogo.setDrawNum(mDrawRedPointNum);
        mFloatLogo.setBgColor(mBackMenuColor);
        mFloatLogo.setDrawDarkBg(true);
        caculateDotNum();
        floatBtnEvent();
        try {
            wManager.addView(mFloatLogo, wmParams);
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    private void genarateLeftLineLayout() {
        DotImageView floatLogo = new DotImageView(mActivity, mLogoRes);
        floatLogo.setLayoutParams(new WindowManager.LayoutParams(dp2Px(50, mActivity), dp2Px(50, mActivity)));
        floatLogo.setDrawNum(mDrawRedPointNum);
        floatLogo.setDrawDarkBg(false);

        rootView = new LinearLayout(mActivity);
        rootView.setOrientation(LinearLayout.HORIZONTAL);
        rootView.setGravity(Gravity.CENTER);
        rootView.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, dp2Px(50, mActivity)));

        rootView.setBackgroundDrawable(mBackground);


        FloatMenuView mFloatMenuView = new FloatMenuView.Builder(mActivity)
                .setFloatItems(mFloatItems)
                .setBackgroundColor(Color.TRANSPARENT)
                .setCicleBg(mCicleMenuBg)
                .setStatus(FloatMenuView.STATUS_LEFT)
                .setMenuBackgroundColor(Color.TRANSPARENT)
                .drawNum(mDrawRedPointNum)
                .create();
        setMenuClickListener(mFloatMenuView);

        rootView.addView(floatLogo);
        rootView.addView(mFloatMenuView);


        floatLogo.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                if (isExpaned) {
                    try {
                        wManager.removeViewImmediate(rootView);
                        wManager.addView(FloatLogoMenu.this.mFloatLogo, wmParams);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    isExpaned = false;
                }
            }
        });
    }

    private void genarateRightLineLayout() {
        final DotImageView floatLogo = new DotImageView(mActivity, mLogoRes);
        floatLogo.setLayoutParams(new WindowManager.LayoutParams(dp2Px(50, mActivity), dp2Px(50, mActivity)));
        floatLogo.setDrawNum(mDrawRedPointNum);
        floatLogo.setDrawDarkBg(false);

        floatLogo.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                if (isExpaned) {
                    try {
                        wManager.removeViewImmediate(rootViewRight);
                        wManager.addView(FloatLogoMenu.this.mFloatLogo, wmParams);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    isExpaned = false;
                }
            }
        });

        rootViewRight = new LinearLayout(mActivity);
        rootViewRight.setOrientation(LinearLayout.HORIZONTAL);
        rootViewRight.setGravity(Gravity.CENTER);
        rootViewRight.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, dp2Px(50, mActivity)));


        rootViewRight.setBackgroundDrawable(mBackground);


        FloatMenuView mFloatMenuView = new FloatMenuView.Builder(mActivity)
                .setFloatItems(mFloatItems)
                .setBackgroundColor(Color.TRANSPARENT)
                .setCicleBg(mCicleMenuBg)
                .setStatus(FloatMenuView.STATUS_RIGHT)
                .setMenuBackgroundColor(Color.TRANSPARENT)
                .drawNum(mDrawRedPointNum)
                .create();
        setMenuClickListener(mFloatMenuView);

        rootViewRight.addView(mFloatMenuView);
        rootViewRight.addView(floatLogo);


    }

    /**
     * 初始化 隐藏悬浮球的定时器
     */
    private void initTimer() {
        mHideTimer = new CountDownTimer(2000, 10) {        //悬浮窗超过5秒没有操作的话会自动隐藏
            @Override
            public void onTick(long millisUntilFinished) {
                if (isExpaned) {
                    mHideTimer.cancel();
                }
            }

            @Override
            public void onFinish() {
                if (isExpaned) {
                    mHideTimer.cancel();
                    return;
                }
                if (!isDraging) {
                    if (mHintLocation == LEFT) {
                        mFloatLogo.setStatus(DotImageView.HIDE_LEFT);
                        mFloatLogo.setDrawDarkBg(true);
                    } else {
                        mFloatLogo.setStatus(DotImageView.HIDE_RIGHT);
                        mFloatLogo.setDrawDarkBg(true);
                    }
//                    mFloatLogo.setOnTouchListener(mDefaultOnTouchListerner);//把onClick事件分发下去,防止onclick无效
                }
            }
        };
    }


    /**
     * 用于 拦截 菜单项的 关闭事件,以方便开始 隐藏定时器
     *
     * @param mFloatMenuView
     */
    private void setMenuClickListener(FloatMenuView mFloatMenuView) {
        mFloatMenuView.setOnMenuClickListener(new FloatMenuView.OnMenuClickListener() {
            @Override
            public void onItemClick(int position, String title) {
                mOnMenuClickListener.onItemClick(position, title);
            }

            @Override
            public void dismiss() {
                mFloatLogo.setDrawDarkBg(true);
                mOnMenuClickListener.dismiss();
                mHideTimer.start();
            }
        });

    }


    /**
     * 悬浮窗的点击事件和touch事件的切换
     */
    private void floatBtnEvent() {
        //这里的onCick只有 touchListener = mDefaultOnTouchListerner 才会触发
//        mFloatLogo.setOnClickListener(new OnClickListener() {
//            @Override
//            public void onClick(View v) {
//                if (!isDraging) {
//                    if (mFloatLogo.getStatus() != DotImageView.NORMAL) {
//                        mFloatLogo.setBitmap(mLogoRes);
//                        mFloatLogo.setStatus(DotImageView.NORMAL);
//                        if (!mFloatLogo.mDrawDarkBg) {
//                            mFloatLogo.setDrawDarkBg(true);
//                        }
//                    }
//                    mFloatLogo.setOnTouchListener(touchListener);
//                    mHideTimer.start();
//                }
//            }
//        });

        mFloatLogo.setOnTouchListener(touchListener);//恢复touch事件
    }

    /**
     * 悬浮窗touch事件的 down 事件
     */
    private void floatEventDown(MotionEvent event) {
        isDraging = false;
        mHideTimer.cancel();
        if (mFloatLogo.getStatus() != DotImageView.NORMAL) {
            mFloatLogo.setStatus(DotImageView.NORMAL);
        }
        if (!mFloatLogo.mDrawDarkBg) {
            mFloatLogo.setDrawDarkBg(true);
        }
        if (mFloatLogo.getStatus() != DotImageView.NORMAL) {
            mFloatLogo.setStatus(DotImageView.NORMAL);
        }
        mXInView = event.getX();
        mYinview = event.getY();
        mXDownInScreen = event.getRawX();
        mYDownInScreen = event.getRawY();
        mXInScreen = event.getRawX();
        mYInScreen = event.getRawY();


    }

    /**
     * 悬浮窗touch事件的 move 事件
     */
    private void floatEventMove(MotionEvent event) {
        mXInScreen = event.getRawX();
        mYInScreen = event.getRawY();


        //连续移动的距离超过3则更新一次视图位置
        if (Math.abs(mXInScreen - mXDownInScreen) > mFloatLogo.getWidth() / 4 || Math.abs(mYInScreen - mYDownInScreen) > mFloatLogo.getWidth() / 4) {
            wmParams.x = (int) (mXInScreen - mXInView);
            wmParams.y = (int) (mYInScreen - mYinview) - mFloatLogo.getHeight() / 2;
            updateViewPosition(); // 手指移动的时候更新小悬浮窗的位置
            double a = mScreenWidth / 2;
            float offset = (float) ((a - (Math.abs(wmParams.x - a))) / a);
            mFloatLogo.setDraging(isDraging, offset, false);
        } else {
            isDraging = false;
            mFloatLogo.setDraging(false, 0, true);
        }
    }

    /**
     * 悬浮窗touch事件的 up 事件
     */
    private void floatEventUp() {
        if (mXInScreen < mScreenWidth / 2) {   //在左边
            mHintLocation = LEFT;
        } else {                   //在右边
            mHintLocation = RIGHT;
        }
        if (valueAnimator == null) {
            valueAnimator = ValueAnimator.ofInt(64);
            valueAnimator.setInterpolator(mLinearInterpolator);
            valueAnimator.setDuration(1000);
            valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    mResetLocationValue = (int) animation.getAnimatedValue();
                    mHandler.post(updatePositionRunnable);
                }
            });

            valueAnimator.addListener(new Animator.AnimatorListener() {
                @Override
                public void onAnimationStart(Animator animation) {

                }

                @Override
                public void onAnimationEnd(Animator animation) {
                    if (Math.abs(wmParams.x) < 0) {
                        wmParams.x = 0;
                    } else if (Math.abs(wmParams.x) > mScreenWidth) {
                        wmParams.x = mScreenWidth;
                    }
                    updateViewPosition();
                    isDraging = false;
                    mFloatLogo.setDraging(false, 0, true);
                    mHideTimer.start();
                }

                @Override
                public void onAnimationCancel(Animator animation) {
                    if (Math.abs(wmParams.x) < 0) {
                        wmParams.x = 0;
                    } else if (Math.abs(wmParams.x) > mScreenWidth) {
                        wmParams.x = mScreenWidth;
                    }

                    updateViewPosition();
                    isDraging = false;
                    mFloatLogo.setDraging(false, 0, true);
                    mHideTimer.start();

                }

                @Override
                public void onAnimationRepeat(Animator animation) {

                }
            });
        }
        if (!valueAnimator.isRunning()) {
            valueAnimator.start();
        }

//        //这里需要判断如果如果手指所在位置和logo所在位置在一个宽度内则不移动,
        if (Math.abs(mXInScreen - mXDownInScreen) > mFloatLogo.getWidth() / 5 || Math.abs(mYInScreen - mYDownInScreen) > mFloatLogo.getHeight() / 5) {
            isDraging = false;
        } else {
            openMenu();
        }

    }


    /**
     * 用于检查并更新悬浮球的位置
     */
    private void checkPosition() {
        if (wmParams.x > 0 && wmParams.x < mScreenWidth) {
            if (mHintLocation == LEFT) {
                wmParams.x = wmParams.x - mResetLocationValue;
            } else {
                wmParams.x = wmParams.x + mResetLocationValue;
            }
            updateViewPosition();
            double a = mScreenWidth / 2;
            float offset = (float) ((a - (Math.abs(wmParams.x - a))) / a);
            mFloatLogo.setDraging(isDraging, offset, true);
            return;
        }


        if (Math.abs(wmParams.x) < 0) {
            wmParams.x = 0;
        } else if (Math.abs(wmParams.x) > mScreenWidth) {
            wmParams.x = mScreenWidth;
        }
        if (valueAnimator.isRunning()) {
            valueAnimator.cancel();
        }


        updateViewPosition();
        isDraging = false;


    }


    /**
     * 打开菜单
     */
    private void openMenu() {
        if (isDraging) return;

        if (!isExpaned) {
            mFloatLogo.setDrawDarkBg(false);
            try {
                wManager.removeViewImmediate(mFloatLogo);
                if (mHintLocation == RIGHT) {
                    wManager.addView(rootViewRight, wmParams);
                } else {
                    wManager.addView(rootView, wmParams);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }

            isExpaned = true;
            mHideTimer.cancel();
        } else {
            mFloatLogo.setDrawDarkBg(true);
            if (isExpaned) {
                try {
                    wManager.removeViewImmediate(mHintLocation == LEFT ? rootView : rootViewRight);
                    wManager.addView(mFloatLogo, wmParams);
                } catch (Exception e) {
                    e.printStackTrace();
                }

                isExpaned = false;
            }
            mHideTimer.start();
        }

    }


    /**
     * 更新悬浮窗在屏幕中的位置。
     */
    private void updateViewPosition() {
        isDraging = true;
        try {
            if (!isExpaned) {
                if (wmParams.y - mFloatLogo.getHeight() / 2 <= 0) {
                    wmParams.y = mStatusBarHeight;
                    isDraging = true;
                }
                wManager.updateViewLayout(mFloatLogo, wmParams);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public void show() {
        try {
            if (wManager != null && wmParams != null && mFloatLogo != null) {
                wManager.addView(mFloatLogo, wmParams);
            }
            if (mHideTimer != null) {
                mHideTimer.start();
            } else {
                initTimer();
                mHideTimer.start();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 关闭菜单
     */
    public void hide() {
        destoryFloat();
    }


    /**
     * 移除所有悬浮窗 释放资源
     */
    public void destoryFloat() {
        //记录上次的位置logo的停放位置,以备下次恢复
        saveSetting(LOCATION_X, mHintLocation);
        saveSetting(LOCATION_Y, wmParams.y);
        mFloatLogo.clearAnimation();
        try {
            mHideTimer.cancel();
            if (isExpaned) {
                wManager.removeViewImmediate(mHintLocation == LEFT ? rootView : rootViewRight);
            } else {
                wManager.removeViewImmediate(mFloatLogo);
            }
            isExpaned = false;
            isDraging = false;
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 计算总红点数
     */
    private void caculateDotNum() {
        int dotNum = 0;
        for (FloatItem floatItem : mFloatItems) {
            if (!TextUtils.isEmpty(floatItem.getDotNum())) {
                int num = Integer.parseInt(floatItem.getDotNum());
                dotNum = dotNum + num;
            }
        }
        mFloatLogo.setDrawNum(mDrawRedPointNum);
        setDotNum(dotNum);
    }

    /**
     * 绘制悬浮球的红点
     *
     * @param dotNum d
     */
    private void setDotNum(int dotNum) {
        mFloatLogo.setDotNum(dotNum, new Animator.AnimatorListener() {
            @Override
            public void onAnimationStart(Animator animation) {

            }

            @Override
            public void onAnimationEnd(Animator animation) {
                if (!isDraging) {
                    mHideTimer.start();
                }
            }

            @Override
            public void onAnimationCancel(Animator animation) {

            }

            @Override
            public void onAnimationRepeat(Animator animation) {

            }
        });
    }

    /**
     * 用于暴露给外部判断是否包含某个菜单项
     *
     * @param menuLabel string
     * @return boolean
     */
    public boolean hasMenu(String menuLabel) {
        for (FloatItem menuItem : mFloatItems) {
            if (TextUtils.equals(menuItem.getTitle(), menuLabel)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 用于保存悬浮球的位置记录
     *
     * @param key          String
     * @param defaultValue int
     * @return int
     */
    private int getSetting(String key, int defaultValue) {
        try {
            SharedPreferences sharedata = mActivity.getSharedPreferences("floatLogo", 0);
            return sharedata.getInt(key, defaultValue);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return defaultValue;
    }

    /**
     * 用于保存悬浮球的位置记录
     *
     * @param key   String
     * @param value int
     */
    public void saveSetting(String key, int value) {
        try {
            SharedPreferences.Editor sharedata = mActivity.getSharedPreferences("floatLogo", 0).edit();
            sharedata.putInt(key, value);
            sharedata.apply();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static int dp2Px(float dp, Context mContext) {
        return (int) TypedValue.applyDimension(
                TypedValue.COMPLEX_UNIT_DIP,
                dp,
                mContext.getResources().getDisplayMetrics());
    }


    public interface OnMenuClickListener {
        void onMenuExpended(boolean isExpened);
    }


    public void setValueAnimator() {

    }

    public static final class Builder {
        private int mBackMenuColor;
        private boolean mDrawRedPointNum;
        private boolean mCicleMenuBg;
        private Bitmap mLogoRes;
        private int mDefaultLocation;
        private List mFloatItems = new ArrayList<>();
        private Context mActivity;
        private FloatMenuView.OnMenuClickListener mOnMenuClickListener;
        private Drawable mDrawable;


        public Builder setBgDrawable(Drawable drawable) {
            mDrawable = drawable;
            return this;
        }

        public Builder() {
        }

        public Builder setFloatItems(List mFloatItems) {
            this.mFloatItems = mFloatItems;
            return this;
        }

        public Builder addFloatItem(FloatItem floatItem) {
            this.mFloatItems.add(floatItem);
            return this;
        }

        public Builder backMenuColor(int val) {
            mBackMenuColor = val;
            return this;
        }

        public Builder drawRedPointNum(boolean val) {
            mDrawRedPointNum = val;
            return this;
        }

        public Builder drawCicleMenuBg(boolean val) {
            mCicleMenuBg = val;
            return this;
        }

        public Builder logo(Bitmap val) {
            mLogoRes = val;
            return this;
        }

        public Builder withActivity(Activity val) {
            mActivity = val;
            return this;
        }

        public Builder withContext(Context val) {
            mActivity = val;
            return this;
        }

        public Builder setOnMenuItemClickListener(FloatMenuView.OnMenuClickListener val) {
            mOnMenuClickListener = val;
            return this;
        }

        public Builder defaultLocation(int val) {
            mDefaultLocation = val;
            return this;
        }

        public FloatLogoMenu showWithListener(FloatMenuView.OnMenuClickListener val) {
            mOnMenuClickListener = val;
            return new FloatLogoMenu(this);
        }

        public FloatLogoMenu showWithLogo(Bitmap val) {
            mLogoRes = val;
            return new FloatLogoMenu(this);
        }

        public FloatLogoMenu show() {
            return new FloatLogoMenu(this);
        }
    }


}

FloatMenuView.java

package com.yw.game.floatmenu;

import android.animation.Animator;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.RectF;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;


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

/**
 * Created by wengyiming on 2017/7/21.
 */

public class FloatMenuView extends View {
    public static final int STATUS_LEFT = 3;//展开左边菜单
    public static final int STATUS_RIGHT = 4;//展开右边菜单

    private int mStatus = STATUS_RIGHT;//默认右边

    private Paint mPaint;//画笔
    private int mBackgroundColor = 0x00FFFFFF;//默认背景颜色 完全透明的白色

    private int mMenuBackgroundColor = -1;//菜单的背景颜色

    private RectF mBgRect;//菜单的背景矩阵
    private int mItemWidth = dip2px(50);//菜单项的宽度
    private int mItemHeight = dip2px(50);//菜单项的高度
    private int mItemLeft = 0;//菜单项左边的默认偏移值,这里是0
    private int mCorner = dip2px(2);//菜单背景的圆角多出的宽度


    private int mRadius = dip2px(4);//红点消息半径
    private final int mRedPointRadiuWithNoNum = dip2px(3);//红点圆半径

    private int mFontSizePointNum = sp2px(10);//红点消息数字的文字大小

    private int mFontSizeTitle = sp2px(12);//菜单项的title的文字大小
    private float mFirstItemTop;//菜单项的最小y值,上面起始那条线
    private boolean mDrawNum = false;//是否绘制数字,false则只绘制红点
    private boolean cicleBg = false;//菜单项背景是否绘制成圆形,false则绘制矩阵

    private List mItemList = new ArrayList<>();//菜单项的内容
    private List mItemRectList = new ArrayList<>();//菜单项所占用位置的记录,用于判断点击事件

    private OnMenuClickListener mOnMenuClickListener;//菜单项的点击事件回调

    private ObjectAnimator mAlphaAnim;//消失关闭动画的透明值

    //设置菜单内容集合
    public void setItemList(List itemList) {
        mItemList = itemList;
    }

    //设置是否绘制红点数字
    public void drawNum(boolean drawNum) {
        mDrawNum = drawNum;
    }

    //设置是否绘制圆形菜单,否则矩阵
    public void setCicleBg(boolean cicleBg) {
        this.cicleBg = cicleBg;
    }

    //用于标记所依赖的view的screen的坐标,实际view的坐标是window坐标,所以这里后面会减去状态栏的高度


    //设置菜单的背景颜色
    public void setMenuBackgroundColor(int mMenuBackgroundColor) {
        this.mMenuBackgroundColor = mMenuBackgroundColor;
    }

    //设置这个view(整个屏幕)的背景,这里默认透明
    public void setBackgroundColor(int BackgroundColor) {
        this.mBackgroundColor = BackgroundColor;
    }


    //下面开始的注释我写不动了,看不懂的话请自行领悟吧
    public FloatMenuView(Context context) {
        super(context);
    }

    public FloatMenuView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public FloatMenuView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    public FloatMenuView(Context baseContext,  int status) {
        super(baseContext);
        mStatus = status;
        int screenWidth = getResources().getDisplayMetrics().widthPixels;
        int screenHeight = getResources().getDisplayMetrics().heightPixels;
        mBgRect = new RectF(0, 0, screenWidth, screenHeight);
        initView();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        setMeasuredDimension(mItemWidth * mItemList.size(), mItemHeight);
    }

    private void initView( ) {
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        mPaint.setStyle(Paint.Style.FILL);
        mPaint.setTextSize(sp2px(12));

        mAlphaAnim = ObjectAnimator.ofFloat(this, "alpha", 1.0f, 0f);
        mAlphaAnim.setDuration(50);
        mAlphaAnim.addListener(new MyAnimListener() {
            @Override
            public void onAnimationEnd(Animator animation) {
                if (mOnMenuClickListener != null) {
                    removeView();
                    mOnMenuClickListener.dismiss();
                }
            }
        });

        mFirstItemTop = 0;
        if (mStatus == STATUS_LEFT) {
            mItemLeft = 0;
        } else {
            mItemLeft = 0;
        }

    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        switch (mStatus) {
            case STATUS_LEFT:
                drawBackground(canvas);
                drawFloatLeftItem(canvas);
                break;
            case STATUS_RIGHT:
                drawBackground(canvas);
                drawFloatLeftItem(canvas);

                break;
        }
    }

    private void drawBackground(Canvas canvas) {
        mPaint.setColor(mBackgroundColor);
        canvas.drawRect(mBgRect, mPaint);

    }

    private void drawFloatLeftItem(Canvas canvas) {
        mItemRectList.clear();
        for (int i = 0; i < mItemList.size(); i++) {
            canvas.save();
            mPaint.setColor(mMenuBackgroundColor);
            if (cicleBg) {
                float cx = (mItemLeft + i * mItemWidth) + mItemWidth / 2;//x中心点
                float cy = mFirstItemTop + mItemHeight / 2;//y中心点
                float radius = mItemWidth / 2;//半径
                canvas.drawCircle(cx, cy, radius, mPaint);
            } else {
                mPaint.setColor(mItemList.get(i).bgColor);
                canvas.drawRect(mItemLeft + i * mItemWidth, mFirstItemTop, mItemLeft + mItemWidth + i * mItemWidth, mFirstItemTop + mItemHeight, mPaint);
            }

            mItemRectList.add(new RectF(mItemLeft + i * mItemWidth, mFirstItemTop, mItemLeft + mItemWidth + i * mItemWidth, mFirstItemTop + mItemHeight));
            mPaint.setColor(mItemList.get(i).bgColor);
            drawIconTitleDot(canvas, i);
        }
        canvas.restore();
    }


    private void drawIconTitleDot(Canvas canvas, int position) {
        FloatItem floatItem = mItemList.get(position);

        if (floatItem.icon != null) {
            float centryX = mItemLeft + mItemWidth / 2 + (mItemWidth) * position;//计算每一个item的中心点x的坐标值
            float centryY = mFirstItemTop + mItemHeight / 2;//计算每一个item的中心点的y坐标值

            float left = centryX - mItemWidth / 4;//计算icon的左坐标值 中心点往左移宽度的四分之一
            float right = centryX + mItemWidth / 4;

            float iconH = mItemHeight * 0.5f;//计算出icon的宽度 = icon的高度

            float textH = getTextHeight(floatItem.getTitle(), mPaint);
            float paddH = (mItemHeight - iconH - textH - mRadius) / 2;//总高度减去文字的高度,减去icon高度,再除以2就是上下的间距剩余

            float top = centryY - mItemHeight / 2 + paddH;//计算icon的上坐标值
            float bottom = top + iconH;//剩下的高度空间用于画文字

            //画icon
            mPaint.setColor(floatItem.titleColor);
            canvas.drawBitmap(floatItem.icon, null, new RectF(left, top, right, bottom), mPaint);
            if (!TextUtils.isEmpty(floatItem.dotNum) && !floatItem.dotNum.equals("0")) {
                float dotLeft = centryX + mItemWidth / 5;
                float cx = dotLeft + mCorner;//x中心点
                float cy = top + mCorner;//y中心点

                int radiu = mDrawNum ? mRadius : mRedPointRadiuWithNoNum;
                //画红点
                mPaint.setColor(Color.RED);
                canvas.drawCircle(cx, cy, radiu, mPaint);
                if (mDrawNum) {
                    mPaint.setColor(Color.WHITE);
                    mPaint.setTextSize(mFontSizePointNum);
                    //画红点消息数
                    canvas.drawText(floatItem.dotNum, cx - getTextWidth(floatItem.getDotNum(), mPaint) / 2, cy + getTextHeight(floatItem.getDotNum(), mPaint) / 2, mPaint);
                }
            }
            mPaint.setColor(floatItem.titleColor);
            mPaint.setTextSize(mFontSizeTitle);
            //画menu title
            canvas.drawText(floatItem.title, centryX - getTextWidth(floatItem.getTitle(), mPaint) / 2, centryY + iconH / 2 + getTextHeight(floatItem.getTitle(), mPaint) / 2, mPaint);
        }
    }


    public void startAnim() {
        if (mItemList.size() == 0) {
            return;
        }
        invalidate();
    }


    public void dismiss() {
        if (!mAlphaAnim.isRunning()) {
            mAlphaAnim.start();
        }
    }

    private void removeView() {
        ViewGroup vg = (ViewGroup) this.getParent();
        if (vg != null) {
            vg.removeView(this);
        }
    }

    @Override
    protected void onWindowVisibilityChanged(int visibility) {
        if (visibility == GONE) {
            if (mOnMenuClickListener != null) {
                mOnMenuClickListener.dismiss();
            }
        }
        super.onWindowVisibilityChanged(visibility);


    }

    public void setOnMenuClickListener(OnMenuClickListener onMenuClickListener) {
        this.mOnMenuClickListener = onMenuClickListener;
    }

    public interface OnMenuClickListener {
        void onItemClick(int position, String title);

        void dismiss();

    }

    private abstract class MyAnimListener implements Animator.AnimatorListener {
        @Override
        public void onAnimationStart(Animator animation) {

        }

        @Override
        public void onAnimationCancel(Animator animation) {

        }

        @Override
        public void onAnimationRepeat(Animator animation) {

        }
    }


    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                for (int i = 0; i < mItemRectList.size(); i++) {
                    if (mOnMenuClickListener != null && isPointInRect(new PointF(event.getX(), event.getY()), mItemRectList.get(i))) {
                        mOnMenuClickListener.onItemClick(i, mItemList.get(i).title);
                        return true;
                    }
                }
                dismiss();
        }
        return false;
    }

    private boolean isPointInRect(PointF pointF, RectF targetRect) {
        return pointF.x >= targetRect.left && pointF.x <= targetRect.right && pointF.y >= targetRect.top && pointF.y <= targetRect.bottom;
    }


    public static class Builder {

        private Context mActivity;
        private List mFloatItems = new ArrayList<>();
        private int mBgColor = Color.TRANSPARENT;
        private int mStatus = STATUS_LEFT;
        private boolean cicleBg = false;
        private int mMenuBackgroundColor = -1;
        private boolean mDrawNum = false;


        public Builder drawNum(boolean drawNum) {
            mDrawNum = drawNum;
            return this;
        }


        public Builder setMenuBackgroundColor(int mMenuBackgroundColor) {
            this.mMenuBackgroundColor = mMenuBackgroundColor;
            return this;
        }


        public Builder setCicleBg(boolean cicleBg) {
            this.cicleBg = cicleBg;
            return this;
        }

        public Builder setStatus(int status) {
            mStatus = status;
            return this;
        }

        public Builder setFloatItems(List floatItems) {
            this.mFloatItems = floatItems;
            return this;
        }


        public Builder(Context activity ) {
            mActivity = activity;
        }

        public Builder addItem(FloatItem floatItem) {
            mFloatItems.add(floatItem);
            return this;
        }

        public Builder addItems(List list) {
            mFloatItems.addAll(list);
            return this;
        }

        public Builder setBackgroundColor(int color) {
            mBgColor = color;
            return this;
        }

        public FloatMenuView create() {
            FloatMenuView floatMenuView = new FloatMenuView(mActivity, mStatus);
            floatMenuView.setItemList(mFloatItems);
            floatMenuView.setBackgroundColor(mBgColor);
            floatMenuView.setCicleBg(cicleBg);
            floatMenuView.startAnim();
            floatMenuView.drawNum(mDrawNum);
            floatMenuView.setMenuBackgroundColor(mMenuBackgroundColor);
            return floatMenuView;
        }

    }


    private int dip2px(float dipValue) {
        final float scale = getContext().getResources().getDisplayMetrics().density;
        return (int) (dipValue * scale + 0.5f);
    }

    private int sp2px(float spValue) {
        final float fontScale = getContext().getResources().getDisplayMetrics().scaledDensity;
        return (int) (spValue * fontScale + 0.5f);
    }

    private float getTextHeight(String text, Paint paint) {
        Rect rect = new Rect();
        paint.getTextBounds(text, 0, text.length(), rect);
        return rect.height() / 1.1f;
    }

    private float getTextWidth(String text, Paint paint) {
        return paint.measureText(text);
    }
}

Utils.java

package com.yw.game.floatmenu;


import android.content.Context;
import android.util.TypedValue;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
import android.view.animation.AnimationSet;
import android.view.animation.LinearInterpolator;
import android.view.animation.ScaleAnimation;
import android.widget.FrameLayout;

import java.lang.reflect.Field;

public class Utils {

    private static int statusBarHeight;

    /**
     * 用于获取状态栏的高度。
     *
     * @return 返回状态栏高度的像素值。
     */
    public static int getStatusBarHeight(Context context) {
        if (statusBarHeight == 0) {
            try {
                Class c = Class.forName("com.android.internal.R$dimen");
                Object o = c.newInstance();
                Field field = c.getField("status_bar_height");
                int x = (Integer) field.get(o);
                statusBarHeight = context.getResources().getDimensionPixelSize(x);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return statusBarHeight;
    }
}

Android 技术博客(3):android 悬浮窗菜单,可用于显示在 launcher 或者 activity。_第1张图片

你可能感兴趣的:(Android 技术博客(3):android 悬浮窗菜单,可用于显示在 launcher 或者 activity。)