Android 自定义FloatView实现悬浮视图

 

前言:自定义FlaotView不需要任何权限,继承FrameLayout,通过WindowManager实现悬浮。


资源文件:drawable、drawable-hdpi、layout

菜单背景(menu_bg.xml):



    
        
             

            
            
            
            
            
            
        
    

悬浮视图相关布局(float_view.xml、float_left_menu_view.xml、float_right_menu_view.xml、float_menu_view_list_item.xml):

float_view.xml:




    

float_left_menu_view.xml:




    

    

float_right_menu_view.xml:




    

    

float_menu_view_list_item.xml:




    

        

    

图片资源

Android 自定义FloatView实现悬浮视图_第1张图片


工具类

ScreenUtils.java

import android.annotation.TargetApi;
import android.app.Activity;
import android.content.Context;
import android.content.res.Resources;
import android.os.Build;
import android.util.DisplayMetrics;
import android.view.Display;
import android.view.View;
import android.view.ViewConfiguration;

import java.lang.reflect.Method;

/**
 * 屏幕管理工具类
 */

public class ScreenUtils {

    /**
     * 获取虚拟按键的高度
     * @param context
     * @return
     */
    public static int getNavigationBarHeight(Context context) {
        int result = 0;
        if (hasNavBar(context)) {
            Resources res = context.getResources();
            int resourceId = res.getIdentifier("navigation_bar_height", "dimen", "android");
            if (resourceId > 0) {
                result = res.getDimensionPixelSize(resourceId);
            }
        }
        return result;
    }

    /**
     * 检查是否存在虚拟按键栏
     * @param context
     * @return
     */
    @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
    public static boolean hasNavBar(Context context) {
        Resources res = context.getResources();
        int resourceId = res.getIdentifier("config_showNavigationBar", "bool", "android");
        if (resourceId != 0) {
            boolean hasNav = res.getBoolean(resourceId);
            // check override flag
            String sNavBarOverride = getNavBarOverride();
            if ("1".equals(sNavBarOverride)) {
                hasNav = false;
            } else if ("0".equals(sNavBarOverride)) {
                hasNav = true;
            }
            return hasNav;
        } else { // fallback
            return !ViewConfiguration.get(context).hasPermanentMenuKey();
        }
    }

    /**
     * 判断虚拟按键栏是否重写
     * @return
     */
    private static String getNavBarOverride() {
        String sNavBarOverride = null;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            try {
                Class c = Class.forName("android.os.SystemProperties");
                Method m = c.getDeclaredMethod("get", String.class);
                m.setAccessible(true);
                sNavBarOverride = (String) m.invoke(null, "qemu.hw.mainkeys");
            } catch (Throwable e) {
            }
        }
        return sNavBarOverride;
    }

    /**
     * 通过反射,获取包含虚拟键的整体屏幕高度
     * @return
     */
    public static int getHasVirtualHight(Context context) {
        int dpi = 0;
        Display display = ((Activity)context).getWindowManager().getDefaultDisplay();
        DisplayMetrics dm = new DisplayMetrics();
        @SuppressWarnings("rawtypes")
        Class c;
        try {
            c = Class.forName("android.view.Display");
            @SuppressWarnings("unchecked")
            Method method = c.getMethod("getRealMetrics", DisplayMetrics.class);
            method.invoke(display, dm);
            dpi = dm.heightPixels;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return dpi;
    }

    /**
     * 通过反射,获取包含虚拟键的整体屏幕宽度
     * @return
     */
    public static int getHasVirtualWidth(Context context) {
        int dpi = 0;
        Display display = ((Activity)context).getWindowManager().getDefaultDisplay();
        DisplayMetrics dm = new DisplayMetrics();
        @SuppressWarnings("rawtypes")
        Class c;
        try {
            c = Class.forName("android.view.Display");
            @SuppressWarnings("unchecked")
            Method method = c.getMethod("getRealMetrics", DisplayMetrics.class);
            method.invoke(display, dm);
            dpi = dm.widthPixels;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return dpi;
    }

    /**
     * 隐藏虚拟按键,并且全屏
     */
    public static void hideBottomUIMenu(Context context) {
        //隐藏虚拟按键,并且全屏
        if (Build.VERSION.SDK_INT > 11 && Build.VERSION.SDK_INT < 19) { // lower api
            View v = ((Activity)context).getWindow().getDecorView();
            v.setSystemUiVisibility(View.GONE);
        } else if (Build.VERSION.SDK_INT >= 19) {
            //for new api versions.
            View decorView = ((Activity)context).getWindow().getDecorView();
            int uiOptions = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
                    | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY | View.SYSTEM_UI_FLAG_FULLSCREEN;
            decorView.setSystemUiVisibility(uiOptions);
        }
    }
}

CommonUtils.java

import android.content.Context;

/**
 * 公共工具类
 */
public class CommonUtils {

    public static int dip2px(Context context, float dipValue) {
        final float scale = context.getResources().getDisplayMetrics().density;
        return (int) (dipValue * scale + 0.5f);
    }

    public static int px2dip(Context context, float pxValue) {
        final float scale = context.getResources().getDisplayMetrics().density;
        return (int) (pxValue / scale + 0.5f);
    }
}

水平ListView:

HorizontalListView.java

import android.content.Context;
import android.database.DataSetObserver;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ListAdapter;
import android.widget.Scroller;

import java.util.LinkedList;
import java.util.Queue;

/**
 * 水平ListView
 */

public class HorizontalListView extends AdapterView {

    private Context mContext = null;
    public boolean mAlwaysOverrideTouch = true;
    protected ListAdapter mAdapter;
    private int mLeftViewIndex = -1;
    private int mRightViewIndex = 0;
    protected int mCurrentX;
    protected int mNextX;
    private int mMaxX = Integer.MAX_VALUE;
    private int mDisplayOffset = 0;
    protected Scroller mScroller;
    private GestureDetector mGesture;
    private Queue mRemovedViewQueue = new LinkedList();
    private OnItemSelectedListener mOnItemSelected;
    private OnItemClickListener mOnItemClicked;
    private OnItemLongClickListener mOnItemLongClicked;
    private boolean mDataChanged = false;
    public HorizontalListView(Context context, AttributeSet attrs) {
        super(context, attrs);
        mContext = context;
        initView();
    }

    private synchronized void initView() {
        mLeftViewIndex = -1;
        mRightViewIndex = 0;
        mDisplayOffset = 0;
        mCurrentX = 0;
        mNextX = 0;
        mMaxX = Integer.MAX_VALUE;
        mScroller = new Scroller(mContext);
        mGesture = new GestureDetector(mContext, mOnGesture);
    }

    @Override
    public void setOnItemSelectedListener(OnItemSelectedListener listener) {
        mOnItemSelected = listener;
    }

    @Override
    public void setOnItemClickListener(OnItemClickListener listener){
        mOnItemClicked = listener;
    }

    @Override
    public void setOnItemLongClickListener(OnItemLongClickListener listener) {
        mOnItemLongClicked = listener;
    }

    private DataSetObserver mDataObserver = new DataSetObserver() {

        @Override
        public void onChanged() {
            synchronized(HorizontalListView.this){
                mDataChanged = true;
            }
            invalidate();
            requestLayout();
        }

        @Override
        public void onInvalidated() {
            reset();
            invalidate();
            requestLayout();
        }

    };

    @Override
    public ListAdapter getAdapter() {
        return mAdapter;
    }

    @Override
    public View getSelectedView() {

        return null;
    }

    @Override
    public void setAdapter(ListAdapter adapter) {
        if(mAdapter != null) {
            mAdapter.unregisterDataSetObserver(mDataObserver);
        }
        mAdapter = adapter;
        mAdapter.registerDataSetObserver(mDataObserver);
        reset();
    }

    private synchronized void reset(){
        initView();
        removeAllViewsInLayout();
        requestLayout();
    }

    @Override
    public void setSelection(int position) {

    }

    private void addAndMeasureChild(final View child, int viewPos) {
        LayoutParams params = child.getLayoutParams();
        if(params == null) {
            params = new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT);
        }

        addViewInLayout(child, viewPos, params, true);
        child.measure(MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.AT_MOST),
                MeasureSpec.makeMeasureSpec(getHeight(), MeasureSpec.AT_MOST));
    }

    @Override
    protected synchronized void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);

        if(mAdapter == null){
            return;
        }

        if(mDataChanged){
            int oldCurrentX = mCurrentX;
            initView();
            removeAllViewsInLayout();
            mNextX = oldCurrentX;
            mDataChanged = false;
        }

        if(mScroller.computeScrollOffset()){
            int scrollx = mScroller.getCurrX();
            mNextX = scrollx;
        }

        if(mNextX <= 0){
            mNextX = 0;
            mScroller.forceFinished(true);
        }
        if(mNextX >= mMaxX) {
            mNextX = mMaxX;
            mScroller.forceFinished(true);
        }

        int dx = mCurrentX-mNextX;

        removeNonVisibleItems(dx);
        fillList(dx);
        positionItems(dx);

        mCurrentX = mNextX;

        if(!mScroller.isFinished()){
            post(new Runnable(){
                @Override
                public void run() {
                    requestLayout();
                }
            });

        }
    }

    private void fillList(final int dx) {
        int edge = 0;
        View child = getChildAt(getChildCount()-1);
        if(child != null) {
            edge = child.getRight();
        }
        fillListRight(edge, dx);

        edge = 0;
        child = getChildAt(0);
        if(child != null) {
            edge = child.getLeft();
        }
        fillListLeft(edge, dx);
    }

    private void fillListRight(int rightEdge, final int dx) {
        while(rightEdge + dx < getWidth() && mRightViewIndex < mAdapter.getCount()) {

            View child = mAdapter.getView(mRightViewIndex, mRemovedViewQueue.poll(), this);
            addAndMeasureChild(child, -1);
            rightEdge += child.getMeasuredWidth();

            if(mRightViewIndex == mAdapter.getCount()-1) {
                mMaxX = mCurrentX + rightEdge-getWidth();
            }

            if (mMaxX < 0) {
                mMaxX = 0;
            }
            mRightViewIndex++;
        }

    }

    private void fillListLeft(int leftEdge, final int dx) {
        while(leftEdge + dx > 0 && mLeftViewIndex >= 0) {
            View child = mAdapter.getView(mLeftViewIndex, mRemovedViewQueue.poll(), this);
            addAndMeasureChild(child, 0);
            leftEdge -= child.getMeasuredWidth();
            mLeftViewIndex--;
            mDisplayOffset -= child.getMeasuredWidth();
        }
    }

    private void removeNonVisibleItems(final int dx) {
        View child = getChildAt(0);
        while(child != null && child.getRight() + dx <= 0) {
            mDisplayOffset += child.getMeasuredWidth();
            mRemovedViewQueue.offer(child);
            removeViewInLayout(child);
            mLeftViewIndex++;
            child = getChildAt(0);

        }

        child = getChildAt(getChildCount()-1);
        while(child != null && child.getLeft() + dx >= getWidth()) {
            mRemovedViewQueue.offer(child);
            removeViewInLayout(child);
            mRightViewIndex--;
            child = getChildAt(getChildCount()-1);
        }
    }

    private void positionItems(final int dx) {
        if(getChildCount() > 0){
            mDisplayOffset += dx;
            int left = mDisplayOffset;
            for(int i=0;i

弹出菜单:

MenuViewPopupWindow.java

import android.content.Context;
import android.os.Build;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.PopupWindow;
import android.widget.RelativeLayout;

/**
 * 菜单弹出窗
 */

public class MenuViewPopupWindow extends PopupWindow implements View.OnClickListener{
    private Context context;
    private View view;
    private ImageView iv_sdk_icon;
    private HorizontalListView listView;
    private int[] ImageIconList = new int[]{R.drawable.person, R.drawable.warn};
    private MenuItemClick menuItemClick = null;
    private IconOnClick iconOnClick = null;

    private static MenuViewPopupWindow instance = null;
    public static MenuViewPopupWindow getInstance(final Context context){
        if(instance == null){
            synchronized (MenuViewPopupWindow.class) {
                if(instance == null) {
                    instance = new MenuViewPopupWindow(context);
                }
            }
        }
        return instance;
    }

    public MenuViewPopupWindow addMenuItemClick(MenuItemClick itemClick){
        menuItemClick = itemClick;
        return this;
    }

    public MenuViewPopupWindow addIconOnClick(IconOnClick click){
        iconOnClick = click;
        return this;
    }

    public MenuViewPopupWindow removeMenuItemClick(){
        menuItemClick = null;
        return this;
    }

    public MenuViewPopupWindow removeIconOnClick(){
        iconOnClick = null;
        return this;
    }

    public MenuViewPopupWindow(Context context) {
        this(context, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
    }

    public MenuViewPopupWindow(Context context, int with, int height) {
        this.context = context;
        setWidth(CommonUtils.dip2px(context, (ImageIconList.length + 1) * 58));
        setHeight(CommonUtils.dip2px(context, 58));
        //如果设置可以获得焦点,按返回键可以令菜单消失
        //setFocusable(true);
        //设置弹窗内可点击
        setTouchable(true);
        //设置弹窗外可点击
        setOutsideTouchable(true);
        //设置dismiss监听事件
        setOnDismissListener(new OnDismissListener() {
            @Override
            public void onDismiss() {
                instance = null;
            }
        });
        //允许弹出窗口超出屏幕范围。默认情况下,窗口被夹到屏幕边界。设置为false将允许Windows精确定位。
        if(Build.VERSION.SDK_INT >= 16) {
            setClippingEnabled(false);
        }
    }

    public MenuViewPopupWindow setMenuView(int viewLayout){
        view = LayoutInflater.from(context).inflate(viewLayout,null);
        setContentView(view);
        initData();
        return this;
    };

    private void initData() {
        iv_sdk_icon = (ImageView) view.findViewById(R.id.iv_sdk_icon);
        listView = (HorizontalListView) view.findViewById(R.id.lv_menu);
        ViewGroup.LayoutParams params = iv_sdk_icon.getLayoutParams();
        params.width = CommonUtils.dip2px(context,58);
        params.height = CommonUtils.dip2px(context,58);
        iv_sdk_icon.setLayoutParams(params);
        iv_sdk_icon.setImageResource(R.drawable.float_logo);
        //设置列表的适配器
        listView.setAdapter(listAdapter);
        iv_sdk_icon.setOnClickListener(this);
    }

    private BaseAdapter listAdapter = new BaseAdapter() {

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            View rootView = null;
            if (convertView == null) {
                rootView = View.inflate(context,R.layout.float_menu_view_list_item,null);
            } else {
                rootView = convertView;
            }
            RelativeLayout ll_item = (RelativeLayout) rootView.findViewById(R.id.ll_item);
            ImageView iv_item = (ImageView) rootView.findViewById(R.id.iv_item);
            ViewGroup.LayoutParams llParams = ll_item.getLayoutParams();
            llParams.width = CommonUtils.dip2px(context,58);
            llParams.height = CommonUtils.dip2px(context,58);
            ll_item.setLayoutParams(llParams);
            ViewGroup.LayoutParams ivParams = iv_item.getLayoutParams();
            ivParams.width = CommonUtils.dip2px(context,50);
            ivParams.height = CommonUtils.dip2px(context,50);
            iv_item.setLayoutParams(ivParams);
            iv_item.setImageResource(ImageIconList[position]);

            ll_item.setTag(position);
            //解决三星手机A8等部分手机在popupWindow里ListView不兼容点击事件,因此在ListView的item设置点击事件,而不是监听OnItemClickListener()
            ll_item.setOnClickListener(clickListener);

            return rootView;
        }

        @Override
        public long getItemId(int position) {
            return position;
        }

        @Override
        public Object getItem(int position) {
            return ImageIconList[position];
        }

        @Override
        public int getCount() {
            return ImageIconList.length;
        }
    };

    public MenuViewPopupWindow setViewAnimationStyle(String stype){
        //setAnimationStyle(ResourcesUtil.getStyleId(context,stype));
        return this;
    }

    public void Dismiss(){
        if(instance != null && isShowing())
            dismiss();
    }

    private View.OnClickListener clickListener = new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            int position = (int) v.getTag();
            if(menuItemClick != null)
                menuItemClick.OnItemClick(position);
        }
    };

    @Override
    public void onClick(View v) {
        if(iconOnClick != null)
            iconOnClick.OnClick();
    }

    public interface MenuItemClick{
        void OnItemClick(int position);
    }

    public interface IconOnClick{
        void OnClick();
    }
}

核心类(FloatView):

FloatView.java

import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.Context;
import android.content.SharedPreferences;
import android.graphics.Color;
import android.graphics.PixelFormat;
import android.os.Build;
import android.support.annotation.Dimension;
import android.util.DisplayMetrics;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.Toast;

import java.util.Timer;
import java.util.TimerTask;


/**
 * 游戏悬浮窗视图
 */

public class FloatView extends FrameLayout implements MenuViewPopupWindow.MenuItemClick,MenuViewPopupWindow.IconOnClick,View.OnTouchListener{

    // 悬浮栏位置
    private final static int LEFT = 0;
    private final static int RIGHT = 1;
    private final static int TOP = 3;
    private final static int BUTTOM = 4;

    private int dpi;
    private int screenHeight;
    private int screenWidth;
    private WindowManager.LayoutParams wmParams;
    private WindowManager wm;
    private float eventDownX, eventDownY;
    private float eventMoveX, eventMoveY;
    private float eventDownStartX;
    private float eventDownStartY;
    private float eventUpEndX;
    private float eventUpEndY;
    private volatile boolean hasEventDown;//判断是否有ACTION_DOWN事件触发的flag
    private volatile boolean isScroll;//判断悬浮窗口是否移动的flag
    private volatile boolean isLongClick;//判断触摸事件是否长按的flag
    private Context mContext = null;
    private int[] location = new int[2];//悬浮窗口在屏幕的x、y位置
    private ImageView mFloatIcon;//悬浮窗图片icon
    private int mFloatX,mFloatY;
    private volatile AnimatorSet animatorSet = null;//组合动画

    private int speed = 10;//移动速度
    private int timeValue = 0; //移动计步
    private volatile boolean isRunSide = false;//(开始/停止)平移悬浮窗flag
    private volatile boolean isAutoHideRun = true;//(开始/停止)隐藏悬浮窗flag
    private Timer mTimer;//平移悬浮窗定时器
    private Timer autohideTimer = null;//隐藏悬浮窗定时器
    private Toast mToast = null;

    private static FloatView instance = null;//单例模式
    public static FloatView getInstance(final Context context){
        if(instance == null) {
            synchronized (FloatView.class) {
                if(instance == null) {
                    instance = new FloatView(context);
                }
            }
        }
        return instance;
    }

    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
    public FloatView(Context context) {
        super(context);
        mContext = context;
        wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
        DisplayMetrics dm = new DisplayMetrics();
        wm.getDefaultDisplay().getMetrics(dm);
        //通过像素密度来设置按钮的大小
        dpi = dpi(dm.densityDpi);
         //屏幕宽度(包含虚拟键的整体屏幕宽度)
        screenWidth = ScreenUtils.getHasVirtualWidth(context);
        //屏幕高度(包含虚拟键的整体屏幕高度)
        screenHeight = ScreenUtils.getHasVirtualHight(context);
        //布局设置
        wmParams = new WindowManager.LayoutParams();
        // 设置window type
        wmParams.type = WindowManager.LayoutParams.TYPE_APPLICATION;
        wmParams.format = PixelFormat.RGBA_8888; // 设置图片格式,效果为背景透明
        wmParams.gravity = 51;
        // 设置Window flag   WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION:虚拟按键透明   WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS:标题栏透明
        wmParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
                        | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                        | WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION
                        | WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS;
        wmParams.width = LayoutParams.WRAP_CONTENT;
        wmParams.height = LayoutParams.WRAP_CONTENT;
        getStartPosition();
        wmParams.x = mFloatX;
        wmParams.y = mFloatY;
        addView(createView(context));
        setOnTouchListener(this);
    }

    /**
     * 获取上次保存的最后位置作为开始位置显示
     */
    private void getStartPosition(){
        try{
            SharedPreferences sharedPreferences = mContext.getSharedPreferences("location", Context.MODE_PRIVATE);
            String strPoint = sharedPreferences.getString("float_location", "");
            mFloatX = 0;
            mFloatY = screenHeight / 2;
            if(!strPoint.equals("")) {
                String[] po = strPoint.split("\\|");
                try {
                    mFloatX = Integer.parseInt(po[0]);
                    mFloatY = Integer.parseInt(po[1]);
                }catch (NumberFormatException e){
                    mFloatX = 0;
                    mFloatY = screenHeight / 2;
                }
            }
        }catch (Exception ex){
            ex.printStackTrace();
        }
    }

    /**
     * 创建Float view
     * @param context
     * @return
     */
    private View createView(final Context context) {
        LayoutInflater inflater = LayoutInflater.from(context);
        // 从布局文件获取浮动窗口视图
        View rootFloatView = inflater.inflate(R.layout.float_view, null);
        mFloatIcon = (ImageView) rootFloatView.findViewById(R.id.iv_floatview);
        //设置icon
        mFloatIcon.setImageResource(R.drawable.float_logo);
        ViewGroup.LayoutParams params = mFloatIcon.getLayoutParams();
        params.width = CommonUtils.dip2px(context, 58);
        params.height = CommonUtils.dip2px(context, 58);
        mFloatIcon.setLayoutParams(params);
        rootFloatView.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
        return rootFloatView;
    }

    /**
     * 根据密度选择控件大小
     */
    private int dpi(int densityDpi) {
        if (densityDpi <= 120) {
            return 36;
        } else if (densityDpi <= 160) {
            return 48;
        } else if (densityDpi <= 240) {
            return 72;
        } else if (densityDpi <= 320) {
            return 96;
        }
        return 108;
    }

    /**
     * 显示悬浮窗
     */
    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
    public void show() {
        if(SupportAndroidVersion()) {
            if (isShown()) {
                return;
            }
            if(wm != null)
                wm.addView(this, wmParams);
            //通过屏幕的宽度和高度的大小比较判别
            if(screenWidth < screenHeight) {//竖屏
                if (wmParams.x < screenWidth / 2 - getMeasuredWidth() / 2) {
                    autoHideViewToSide(LEFT, 3000);
                } else {
                    autoHideViewToSide(RIGHT, 3000);
                }
            }else {//横屏
                if (wmParams.x <= screenWidth / 4 || (wmParams.y >= screenHeight / 2 && wmParams.x <= screenWidth / 2)) {
                    autoHideViewToSide(LEFT, 3000);
                } else if (wmParams.x >= screenWidth * 3 / 4 || (wmParams.y >= screenHeight / 2 && wmParams.x > screenWidth / 2)) {
                    autoHideViewToSide(RIGHT, 3000);
                } else {
                    autoHideViewToSide(TOP, 3000);
                }
            }
        }
    }

    /**
     * 销毁悬浮窗
     */
    public void destory() {
        if(isShown()) {
            if (wm != null)
                wm.removeViewImmediate(this);
        }
        instance = null;
    }

    /**
     * 判断当前设备android版本是否符合要求
     * @return boolean
     */
    public boolean SupportAndroidVersion() {
        int curApiVersion = Build.VERSION.SDK_INT;
        // This work only for android 4.0+
        if (curApiVersion >= 14) {
            return true;
        }
        return false;
    }

    /**
     * 悬浮窗Touch事件
     * @param v
     * @param event
     * @return
     */
    @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
    @Override
    public boolean onTouch(View v, MotionEvent event) {
        // 获取相对屏幕的坐标, 以屏幕左上角为原点
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                hasEventDown = true;
                /*
                 * 触发ACTION_DOWN把所有的作用及效果全部取消并复原
                 */
                if(mTimer != null){
                    isRunSide = false;
                    mTimer.cancel();
                    mTimer = null;
                }
                if(autohideTimer != null){
                    isAutoHideRun = false;
                    autohideTimer.cancel();
                    autohideTimer = null;
                }
                eventDownX = event.getX();
                eventDownY = event.getY();
                eventDownStartX = event.getRawX();
                eventDownStartY = event.getRawY();

                if(animatorSet != null && (animatorSet.isStarted() || animatorSet.isRunning())) {
                    animatorSet.end();
                    animatorSet.cancel();
                    animatorSet = null;
                }
                mFloatIcon.setAlpha(1.0f);
                ObjectAnimator translationAnimator = null;
                //得到view在屏幕中的位置
                v.getLocationOnScreen(location);
                //判断位于屏幕的位置
                if(screenWidth < screenHeight) {//竖屏
                    if (location[0] < screenWidth / 2 - getMeasuredWidth() / 2) {
                        translationAnimator = new ObjectAnimator().ofFloat(mFloatIcon,"translationX",-v.getMeasuredWidth()*2/3,0);//左侧
                    } else {
                        translationAnimator = new ObjectAnimator().ofFloat(mFloatIcon,"translationX",v.getMeasuredWidth()*2/3,0);//右侧
                    }
                }else {//横屏
                    if (location[0] <= screenWidth / 4 || (location[1] >= screenHeight / 2 && location[0] <= screenWidth / 2)) {
                        translationAnimator = new ObjectAnimator().ofFloat(mFloatIcon,"translationX",-v.getMeasuredWidth()*2/3,0);//左侧
                    } else if (location[0] >= screenWidth * 3 / 4 || (location[1] >= screenHeight / 2 && location[0] > screenWidth / 2)) {
                        translationAnimator = new ObjectAnimator().ofFloat(mFloatIcon,"translationX",v.getMeasuredWidth()*2/3,0);//右侧
                    } else {
                        translationAnimator = new ObjectAnimator().ofFloat(mFloatIcon,"translationY",-v.getMeasuredWidth()*2/3,0);//上侧
                    }
                }
                animatorSet = new AnimatorSet();  //组合动画
                animatorSet.playTogether(translationAnimator); //设置动画
                animatorSet.setDuration(0);  //设置动画时间
                animatorSet.start(); //启动
                break;
            case MotionEvent.ACTION_MOVE:
                if(!hasEventDown)
                    return false;
                eventMoveX = event.getRawX();
                eventMoveY = event.getRawY();
                if(!isLongClick) {
                    isLongClick = isLongPressed(event.getDownTime(), event.getEventTime(), 1000);
                }
                if(isScroll || isLongClick || (Math.abs(eventMoveX - eventDownStartX) + Math.abs(eventMoveY - eventDownStartY)) >= (v.getMeasuredWidth()+v.getMeasuredHeight())/3) {
                    isScroll = true;
                    updateViewPosition();
                }
                break;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                if(!hasEventDown)
                    return false;
                eventUpEndX = event.getRawX();
                eventUpEndY = event.getRawY();
                v.getLocationOnScreen(location);
                if(!isScroll && ((Math.abs(eventUpEndX - eventDownStartX) + Math.abs(eventUpEndY - eventDownStartY)) < (v.getMeasuredWidth()+v.getMeasuredHeight())/3)){
                    v.getLocationOnScreen(location);
                    //判断位于屏幕的位置
                    if(screenWidth < screenHeight) {//竖屏
                        if((location[0] == 0 || location[0] == (screenWidth - getMeasuredWidth()))) {
                            if (location[0] < screenWidth / 2 - getMeasuredWidth() / 2) {//左侧
                                MenuViewPopupWindow.getInstance(mContext).setMenuView(R.layout.float_left_menu_view).setViewAnimationStyle("").
                                        addIconOnClick(this).addMenuItemClick(this).showAtLocation(v, Gravity.NO_GRAVITY, v.getLeft(), v.getTop());
                            } else {//右侧
                                MenuViewPopupWindow.getInstance(mContext).setMenuView(R.layout.float_right_menu_view).setViewAnimationStyle("").
                                        addIconOnClick(this).addMenuItemClick(this).showAtLocation(v, Gravity.NO_GRAVITY, v.getRight() - MenuViewPopupWindow.getInstance(mContext).getWidth(), v.getTop());
                            }
                        }
                    }else {//横屏
                        if ((location[0] == 0 || location[0] == (screenWidth - getMeasuredWidth())) && (location[0] <= screenWidth / 4 || (location[1] >= screenHeight / 2 && location[0] <= screenWidth / 2))) {//左侧
                            MenuViewPopupWindow.getInstance(mContext).setMenuView(R.layout.float_left_menu_view).setViewAnimationStyle("").
                                    addIconOnClick(this).addMenuItemClick(this).showAtLocation(v, Gravity.NO_GRAVITY, v.getLeft(), v.getTop());
                        } else if ((location[0] == 0 || location[0] == (screenWidth - getMeasuredWidth())) && (location[0] >= screenWidth * 3 / 4 || (location[1] >= screenHeight / 2 && location[0] > screenWidth / 2))) {//右侧
                            MenuViewPopupWindow.getInstance(mContext).setMenuView(R.layout.float_right_menu_view).setViewAnimationStyle("").
                                    addIconOnClick(this).addMenuItemClick(this).showAtLocation(v, Gravity.NO_GRAVITY, v.getRight() - MenuViewPopupWindow.getInstance(mContext).getWidth(), v.getTop());
                        } else {//上侧
                            if(location[1] == 0) {
                                if (location[0] <= screenWidth / 2) {//从右侧显示菜单
                                    MenuViewPopupWindow.getInstance(mContext).setMenuView(R.layout.float_left_menu_view).setViewAnimationStyle("").
                                            addIconOnClick(this).addMenuItemClick(this).showAtLocation(v, Gravity.NO_GRAVITY, v.getLeft(), v.getTop());
                                } else {//从左侧显示菜单
                                    MenuViewPopupWindow.getInstance(mContext).setMenuView(R.layout.float_right_menu_view).setViewAnimationStyle("").
                                            addIconOnClick(this).addMenuItemClick(this).showAtLocation(v, Gravity.NO_GRAVITY, v.getRight() - MenuViewPopupWindow.getInstance(mContext).getWidth(), v.getTop());
                                }
                            }
                        }
                    }
                    autoViewRunToSide(v,5000);
                }else{
                    autoViewRunToSide(v,3000);
                }
                isScroll = false;
                isLongClick = false;
                hasEventDown = false;
                break;
        }
        return false;
    }

    /**
     * 判断是否有长按动作发生
     * @param lastDownTime 按下时间
     * @param thisEventTime 移动时间
     * @param longPressTime 判断长按时间的阀值
    */
    private boolean isLongPressed(long lastDownTime, long thisEventTime, long longPressTime){
        long intervalTime = thisEventTime - lastDownTime;
        if(intervalTime >= longPressTime){
            return true;
        }
        return false;
    }

    /**
     * 更新悬浮窗窗口位置参数
     */
    private void updateViewPosition() {
        wmParams.x = (int) (eventMoveX - eventDownX );
        wmParams.y = (int) (eventMoveY - eventDownY );
        wm.updateViewLayout(this, wmParams);
    }

    /**
     * 自动移动位置
     */
    private void autoViewRunToSide(View v, int autohideTime) {
        if(v == null)
            return;
        //得到view在屏幕中的位置
        v.getLocationOnScreen(location);
        //判断位于屏幕的位置
        if(screenWidth < screenHeight) {//竖屏
            if (location[0] < screenWidth / 2 - v.getMeasuredWidth() / 2) {
                RunToSide(LEFT,autohideTime);//左侧
            } else {
                RunToSide(RIGHT,autohideTime);//右侧
            }
        }else {//横屏
            if (location[0] <= screenWidth / 4 || (location[1] >= screenHeight / 2 && location[0] <= screenWidth / 2)) {//左侧
                RunToSide(LEFT, autohideTime);
            } else if (location[0] >= screenWidth * 3 / 4 || (location[1] >= screenHeight / 2 && location[0] > screenWidth / 2)) {//右侧
                RunToSide(RIGHT, autohideTime);
            } else {//上侧
                RunToSide(TOP, autohideTime);
            }
        }
    }

    /**
     * 自动隐藏悬浮窗
     * @param side
     * @param time
     */
    private void autoHideViewToSide(final int side,int time){
        TimerTask autohideTimerTask = new TimerTask() {
            @Override
            public void run() {
                if(!isAutoHideRun) {
                    if(autohideTimer != null){
                        autohideTimer.cancel();
                        autohideTimer = null;
                    }
                    this.cancel();
                    return;
                }
                ((Activity)mContext).runOnUiThread(new Runnable() {
                    @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
                    @Override
                    public void run() {
                        if(!isAutoHideRun)
                            return;
                        mFloatIcon.setAlpha(0.7f);
                        if(animatorSet != null && (animatorSet.isStarted() || animatorSet.isRunning())) {
                            animatorSet.end();
                            animatorSet.cancel();
                            animatorSet = null;
                        }
                        ObjectAnimator animatorTranslation = null;
                        ObjectAnimator animatorRotation = null;
                        switch (side){
                            case LEFT:
                                animatorTranslation = new ObjectAnimator().ofFloat(mFloatIcon,"translationX",0,-mFloatIcon.getMeasuredWidth()*2/3);
                                animatorRotation = new ObjectAnimator().ofFloat(mFloatIcon,"rotation",0,-360);
                                break;
                            case RIGHT:
                                animatorTranslation = new ObjectAnimator().ofFloat(mFloatIcon,"translationX",0,mFloatIcon.getMeasuredWidth()*2/3);
                                animatorRotation = new ObjectAnimator().ofFloat(mFloatIcon,"rotation",0,360);
                                break;
                            case TOP:
                                animatorTranslation = new ObjectAnimator().ofFloat(mFloatIcon,"translationY",0,-mFloatIcon.getMeasuredWidth()*2/3);
                                animatorRotation = new ObjectAnimator().ofFloat(mFloatIcon,"rotation",0,-360);
                                break;
                            case BUTTOM:
                                break;
                        }
                        animatorSet = new AnimatorSet();  //组合动画
                        animatorSet.playTogether(animatorTranslation,animatorRotation); //设置动画
                        animatorSet.setDuration(1000);  //设置动画时间
                        animatorSet.start(); //启动
                    }
                });
            }
        };
        isAutoHideRun = true;
        autohideTimer = new Timer();
        autohideTimer.schedule(autohideTimerTask,time);//隐藏浮标:延迟时间为time(秒)
    }

    /**
     * 手指释放更新悬浮窗位置
     */
    public void RunToSide(final int side,final int autohideTime){
        if(side != TOP && screenWidth > screenHeight)//横屏且非上侧
            speed = 15;
        else
            speed = 10;
        int runLen = 0;
        switch (side){
            case LEFT:
                runLen = location[0];
                break;
            case RIGHT:
                runLen = screenWidth - location[0];
                break;
            case TOP:
                runLen = location[1];
                break;
            case BUTTOM:
                break;
        }
        timeValue = runLen/speed;

        final TimerTask task = new TimerTask() {
            @Override
            public void run() {
                if(!isRunSide) {
                    if(mTimer != null){
                        mTimer.cancel();
                        mTimer = null;
                    }
                    this.cancel();
                    return;
                }
                timeValue--;
                if (timeValue >= 0) {
                    switch (side){
                        case LEFT:
                            wmParams.x = timeValue * speed;
                            break;
                        case RIGHT:
                            wmParams.x = screenWidth - timeValue*speed;
                            break;
                        case TOP:
                            wmParams.y = timeValue * speed;
                            break;
                        case BUTTOM:
                            break;
                    }
                    ((Activity)mContext).runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            if(!isRunSide)
                                return;
                            wm.updateViewLayout(FloatView.this,wmParams);
                        }
                    });
                }else{
                    isRunSide = false;
                    if(mTimer!=null) {
                        mTimer.cancel();
                    }
                    switch (side){
                        case LEFT:
                            wmParams.x = 0;
                            break;
                        case RIGHT:
                            wmParams.x = screenWidth - getMeasuredWidth();
                            break;
                        case TOP:
                            wmParams.y = 0;
                            break;
                        case BUTTOM:
                            break;
                    }
                    ((Activity)mContext).runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            wm.updateViewLayout(FloatView.this,wmParams);
                            SharedPreferences sharedPreferences = mContext.getSharedPreferences("location", Context.MODE_PRIVATE);
                            SharedPreferences.Editor edit = sharedPreferences.edit();
                            edit.putString("float_location", wmParams.x + "|" + wmParams.y);
                            edit.commit();
                        }
                    });
                    TimerTask autohideTimerTask = new TimerTask() {
                        @Override
                        public void run() {
                            if(!isAutoHideRun) {
                                if(autohideTimer != null){
                                    autohideTimer.cancel();
                                    autohideTimer = null;
                                }
                                this.cancel();
                                return;
                            }
                            ((Activity)mContext).runOnUiThread(new Runnable() {
                                @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
                                @Override
                                public void run() {
                                    if(!isAutoHideRun)
                                        return;
                                    mFloatIcon.setAlpha(0.7f);
                                    MenuViewPopupWindow.getInstance(mContext).removeMenuItemClick().removeIconOnClick().Dismiss();
                                    if(animatorSet != null && (animatorSet.isStarted() || animatorSet.isRunning())) {
                                        animatorSet.end();
                                        animatorSet.cancel();
                                    }
                                    ObjectAnimator animatorTranslation = null;
                                    ObjectAnimator animatorRotation = null;
                                    switch (side){
                                        case LEFT:
                                            animatorTranslation = new ObjectAnimator().ofFloat(mFloatIcon,"translationX",0,-mFloatIcon.getMeasuredWidth()*2/3);
                                            animatorRotation = new ObjectAnimator().ofFloat(mFloatIcon,"rotation",0,-360);
                                            break;
                                        case RIGHT:
                                            animatorTranslation = new ObjectAnimator().ofFloat(mFloatIcon,"translationX",0,mFloatIcon.getMeasuredWidth()*2/3);
                                            animatorRotation = new ObjectAnimator().ofFloat(mFloatIcon,"rotation",0,360);
                                            break;
                                        case TOP:
                                            animatorTranslation = new ObjectAnimator().ofFloat(mFloatIcon,"translationY",0,-mFloatIcon.getMeasuredWidth()*2/3);
                                            animatorRotation = new ObjectAnimator().ofFloat(mFloatIcon,"rotation",0,-360);
                                            break;
                                        case BUTTOM:
                                            break;
                                    }
                                    animatorSet = new AnimatorSet();  //组合动画
                                    animatorSet.playTogether(animatorTranslation,animatorRotation); //设置动画
                                    animatorSet.setDuration(1000);  //设置动画时间
                                    animatorSet.start(); //启动
                                }
                            });
                        }
                    };
                    isAutoHideRun = true;
                    autohideTimer = new Timer();
                    autohideTimer.schedule(autohideTimerTask,autohideTime);//隐藏浮标:延迟autohideTime(秒)
                }
                ((Activity)mContext).runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        FloatView.this.invalidate();
                    }
                });
            }
        };
        isRunSide = true;
        mTimer = new Timer();
        mTimer.schedule(task, 0, 15); // 平移动画效果:隔15毫秒执行一次
    }

    /**
     * 菜单展示后,点击icon事件回调,implement回调接口MenuViewPopupWindow.SDKIconOnClick
     */
    @Override
    public void OnClick() {
        if(mTimer != null){
            isRunSide = false;
            mTimer.cancel();
            mTimer = null;
        }
        if(autohideTimer != null){
            isAutoHideRun = false;
            autohideTimer.cancel();
            autohideTimer = null;
        }
        MenuViewPopupWindow.getInstance(mContext).removeMenuItemClick().removeIconOnClick().Dismiss();
        getLocationOnScreen(location);
        //判断位于屏幕的位置
        if(screenWidth < screenHeight) {//竖屏
            if (location[0] < screenWidth / 2 - getMeasuredWidth() / 2) {
                autoHideViewToSide(LEFT,3000);
            }else{
                autoHideViewToSide(RIGHT,3000);
            }
        }else {//横屏
            if (location[0] <= screenWidth / 4 || (location[1] >= screenHeight / 2 && location[0] <= screenWidth / 2)) {
                autoHideViewToSide(LEFT, 3000);
            } else if (location[0] >= screenWidth * 3 / 4 || (location[1] >= screenHeight / 2 && location[0] > screenWidth / 2)) {
                autoHideViewToSide(RIGHT, 3000);
            } else {
                autoHideViewToSide(TOP, 3000);
            }
        }
    }

    /**
     * 菜单选项item的选择回调,implement回调接口MenuViewPopupWindow.SDKMenuItemClick
     * @param position
     */
    @Override
    public void OnItemClick(final int position) {
        if(mTimer != null){
            isRunSide = false;
            mTimer.cancel();
            mTimer = null;
        }
        if(autohideTimer != null){
            isAutoHideRun = false;
            autohideTimer.cancel();
            autohideTimer = null;
        }
        MenuViewPopupWindow.getInstance(mContext).removeMenuItemClick().removeIconOnClick().Dismiss();
        getLocationOnScreen(location);
        //判断位于屏幕的位置
        if (screenWidth < screenHeight) {//竖屏
            if (location[0] < screenWidth / 2 - getMeasuredWidth() / 2) {
                autoHideViewToSide(LEFT, 0);
            } else {
                autoHideViewToSide(RIGHT, 0);
            }
        } else {//横屏
            if (location[0] <= screenWidth / 4 || (location[1] >= screenHeight / 2 && location[0] <= screenWidth / 2)) {
                autoHideViewToSide(LEFT, 0);
            } else if (location[0] >= screenWidth * 3 / 4 || (location[1] >= screenHeight / 2 && location[0] > screenWidth / 2)) {
                autoHideViewToSide(RIGHT, 0);
            } else {
                autoHideViewToSide(TOP, 0);
            }
        }

        try {
            ((Activity) mContext).runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    switch (position){
                        case 0:
                            showToast(mContext, "选项一", Gravity.CENTER, 3500);
                            break;
                        case 1:
                            showToast(mContext, "选项二", Gravity.CENTER, 3500);
                            break;
                    }
                }
            });
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 关闭悬浮窗口
     */
    private void closeFloatView(){
        destory();
    }

    /**
     * 提示信息
     * @param context
     * @param msg
     * @param gravity
     * @param time    Toast.LENGTH_SHORT(2000ms) <=  time <= Toast.LENGTH_LONG(3500ms)
     */
    private void showToast(Context context, String msg, int gravity, int time){
        RelativeLayout rl = new RelativeLayout(context);
        TextView tv_toast = new TextView(context);
        tv_toast.setText(msg);
        tv_toast.setTextColor(Color.WHITE);
        tv_toast.setTextSize(Dimension.SP, 18.0f);
        tv_toast.setPadding(CommonUtils.dip2px(context, 10), CommonUtils.dip2px(context, 10), CommonUtils.dip2px(context, 10), CommonUtils.dip2px(context, 10));
        rl.addView(tv_toast);
        rl.setBackgroundColor(Color.parseColor("#b0000000"));
        mToast = new Toast(context);
        mToast.setGravity(gravity, 0, 0);
        mToast.setDuration(time);
        mToast.setView(rl);
        if(mToast != null)
            mToast.show();
    }
}

调用方法:

FloatView.getInstance(this).show();//显示
FloatView.getInstance(this).destory();//销毁

注:水平ListView和弹出菜单仅用于提供演示,主要的是核心类(FloatView),核心类的实现原理有兴趣可研究一下。

效果图:

Android 自定义FloatView实现悬浮视图_第2张图片

Android 自定义FloatView实现悬浮视图_第3张图片

GitHub项目地址:https://github.com/KingToAce/FloatView

你可能感兴趣的:(Android)