Android 悬浮窗--无需权限

作者: 颤抖的闪电 | 来源:发表于2019-12-17 17:34 被阅读0次
    image.png

    无需一切权限,不受各种国产ROM限制,默认可以显示的应用内悬浮窗。

    • 应用内显示,无需申请任何权限
    • 应用内显示,所有机型都可以默认显示悬浮窗,无需引导用户做更多设置
    • 支持拖拽
    • 超出屏幕限制移动
    • 可自动吸附到屏幕边缘
    • 可向左右边缘隐藏一半
      核心类共三个:
      CFloatingManager.java
    
    import android.app.Activity;
    import android.os.Handler;
    import android.os.Looper;
    import android.support.v4.view.ViewCompat;
    import android.view.Gravity;
    import android.view.ViewGroup;
    import android.widget.FrameLayout;
    import android.widget.RelativeLayout;
    
    import com.imuxuan.floatingview.utils.EnContext;
    
    
    /**
     * @ClassName CFloatingManager
     * @Description 悬浮窗管理器, 建造者模式
     */
    public class CFloatingManager {
    
        public static FloatingImp build() {
            return new FloatingImp();
        }
    
        public static class FloatingImp {
            private Handler mHandler;
            private CFloatingView mCFloatingView;
            private FrameLayout mContainer;
            int layoutId = 0; //布局id
            ViewGroup.LayoutParams params; //布局初始参数(位置,大小等)
            CFloatingView.MagnetViewListener magnetViewListener;//监听
            CFloatingView.IFloatingViews iFloatingViews;//监听
            boolean isMovable = true;//是否可移动
            boolean isHideEdge = true;//是否隐藏边缘
    
            public FloatingImp create() {
                mHandler = new Handler(Looper.getMainLooper());
                synchronized (this) {
                    if (mCFloatingView != null) {
                        return this;
                    }
                    mCFloatingView = new CFloatingView(EnContext.get().getApplicationContext());
                    mCFloatingView.setLayout(EnContext.get().getApplicationContext(), layoutId);
                    if (null != iFloatingViews) {
                        iFloatingViews.onInitViews(mCFloatingView);
                    }
                    mCFloatingView.setLayoutParams(null == params ? defaultParams() : params);
                    mCFloatingView.setIsMovable(isMovable);
                    mCFloatingView.setIsHideEdge(isHideEdge);
                    if (mCFloatingView != null) {
                        mCFloatingView.setMagnetViewListener(magnetViewListener);
                    }
                    addViewToWindow(mCFloatingView);
                }
                return this;
            }
    
            /**
             * 设置布局控件
             */
            public FloatingImp setLayout(int layoutId) {
                this.layoutId = layoutId;
                return this;
            }
    
            /**
             * 设置布局初始参数
             */
            public FloatingImp setLayoutParams(ViewGroup.LayoutParams params) {
                this.params = params;
                return this;
            }
    
            /**
             * 监听事件
             */
            public FloatingImp setListener(CFloatingView.MagnetViewListener magnetViewListener) {
                this.magnetViewListener = magnetViewListener;
                return this;
            }
    
            public FloatingImp setIsMovable(boolean isMovable) {
                this.isMovable = isMovable;
                return this;
            }
    
            public FloatingImp setIsHideEdge(boolean isHideEdge) {
                this.isHideEdge = isHideEdge;
                return this;
            }
    
            public FloatingImp setInitViews(CFloatingView.IFloatingViews iFloatingViews) {
                this.iFloatingViews = iFloatingViews;
                return this;
            }
    
            public FloatingImp remove() {
                new Handler(Looper.getMainLooper()).post(new Runnable() {
                    @Override
                    public void run() {
                        if (mCFloatingView == null) {
                            return;
                        }
                        if (ViewCompat.isAttachedToWindow(mCFloatingView) && mContainer != null) {
                            mContainer.removeView(mCFloatingView);
                        }
                        mCFloatingView = null;
                    }
                });
                return this;
            }
    
            /**
             * 将view绑定到activity的布局中
             */
            public FloatingImp attach(Activity activity) {
                attach(getActivityRoot(activity));
                return this;
            }
    
            /**
             * 将view绑定到布局中
             */
            public FloatingImp attach(FrameLayout container) {
                if (container == null || mCFloatingView == null) {
                    mContainer = container;
                    return this;
                }
                if (mCFloatingView.getParent() == container) {
                    return this;
                }
                if (mContainer != null && mCFloatingView.getParent() == mContainer) {
                    mContainer.removeView(mCFloatingView);
                }
                mContainer = container;
                container.addView(mCFloatingView);
                mCFloatingView.onAttach();
                return this;
            }
    
            /**
             * 将view从activity的布局中解绑
             */
            public FloatingImp detach(Activity activity) {
                detach(getActivityRoot(activity));
                return this;
            }
    
            /**
             * 将view从布局中解绑
             */
            public FloatingImp detach(FrameLayout container) {
                if (mCFloatingView != null && container != null && ViewCompat.isAttachedToWindow(mCFloatingView)) {
                    container.removeView(mCFloatingView);
                }
                if (mContainer == container) {
                    mContainer = null;
                }
                mCFloatingView.onDetach();
                return this;
            }
    
            /**
             * 将view添加到当前窗口中
             */
            private void addViewToWindow(final CFloatingView view) {
                if (mContainer == null) {
                    return;
                }
                mContainer.addView(view);
            }
    
            /**
             * 默認Params,用于设置最开始位置
             */
            private FrameLayout.LayoutParams defaultParams() {
                FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
                        RelativeLayout.LayoutParams.WRAP_CONTENT,
                        RelativeLayout.LayoutParams.WRAP_CONTENT);
                params.gravity = Gravity.BOTTOM | Gravity.START;
                params.setMargins(13, params.topMargin, params.rightMargin, 56);
                return params;
            }
    
            /**
             * 获取activity所绑定的布局
             */
            private FrameLayout getActivityRoot(Activity activity) {
                if (activity == null) {
                    return null;
                }
                try {
                    return (FrameLayout) activity.getWindow().getDecorView().findViewById(android.R.id.content);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                return null;
            }
        }
    
    }
    

    CFloatingView.java

    
    import android.content.Context;
    import android.support.annotation.NonNull;
    import android.view.MotionEvent;
    import android.view.View;
    
    /**
     * @ClassName CFloatingView
     * @Description 悬浮窗,继承磁力吸附悬浮窗,封装点击事件
     */
    public class CFloatingView extends FloatingMagnetView {
        private MagnetViewListener mMagnetViewListener;
        private long mLastTouchDownTime;//用于判断点击
        private static final int TOUCH_TIME_THRESHOLD = 150;
        public View view;
    
        /**
         * 悬浮窗点击接口
         */
        public interface MagnetViewListener {
    
            void onRemove(CFloatingView cFloatingView);
    
            void onClick(CFloatingView cFloatingView);
    
            void onEndAppear(CFloatingView cFloatingView);
    
            void onEndHide(CFloatingView cFloatingView);
        }
    
        public interface IFloatingViews {
            void onInitViews(CFloatingView cFloatingView);
        }
    
        public CFloatingView(@NonNull Context context) {
            super(context, null);
        }
    
        public View setLayout(@NonNull Context context, int en_floating_view_id) {
            return view = inflate(context, en_floating_view_id, this);
        }
    
        public void setMagnetViewListener(MagnetViewListener magnetViewListener) {
            this.mMagnetViewListener = magnetViewListener;
        }
    
        @Override
        public boolean onTouchEvent(MotionEvent event) {
            super.onTouchEvent(event);
            if (event != null) {
                switch (event.getAction()) {
                    case MotionEvent.ACTION_DOWN:
                        mLastTouchDownTime = System.currentTimeMillis();
                        clearAnimation();
                        break;
                    case MotionEvent.ACTION_UP:
                        if (isOnClickEvent()) {
                            dealClickEvent();
                        }
                        break;
                }
            }
            return true;
        }
    
        protected void dealClickEvent() {
            if (mMagnetViewListener != null) {
                mMagnetViewListener.onClick(this);
            }
        }
    
        public void onRemove() {
            if (mMagnetViewListener != null) {
                mMagnetViewListener.onRemove(this);
            }
        }
    
        protected boolean isOnClickEvent() {
            return System.currentTimeMillis() - mLastTouchDownTime < TOUCH_TIME_THRESHOLD;
        }
    
        /**
         * 隐藏,点击又出现之后,由子类进行具体实现
         */
        @Override
        protected void onEndAppear() {
            super.onEndAppear();
            if (mMagnetViewListener != null) {
                mMagnetViewListener.onEndAppear(this);
            }
        }
    
        /**
         * 隐藏半边之后
         */
        @Override
        protected void onEndHide() {
            super.onEndHide();
            if (mMagnetViewListener != null) {
                mMagnetViewListener.onEndHide(this);
            }
        }
    
        @Override
        public void onAttach() {
            super.onAttach();
            handler.removeCallbacksAndMessages(null);
            handler.postDelayed(new Runnable() {
                @Override
                public void run() {
                    hideEdgeAnima();
                }
            }, 2000);
        }
    
    }
    
    

    FloatingMagnetView.java

    
    import android.content.Context;
    import android.os.Handler;
    import android.os.Looper;
    import android.os.Message;
    import android.util.AttributeSet;
    import android.view.MotionEvent;
    import android.view.animation.Animation;
    import android.view.animation.AnimationSet;
    import android.view.animation.TranslateAnimation;
    import android.widget.FrameLayout;
    
    import com.imuxuan.floatingview.utils.SystemUtils;
    
    /**
     * @ClassName FloatingMagnetView
     * @Description 磁力吸附悬浮窗
     * @Author Yunpeng Li
     * @Creation 2018/3/15 下午5:02
     * @Mender Yunpeng Li
     * @Modification 2018/3/15 下午5:02
     */
    public class FloatingMagnetView extends FrameLayout {
    
        public static final int MARGIN_EDGE = 13;
        private float mOriginalRawX;
        private float mOriginalRawY;
        private float mOriginalX;
        private float mOriginalY;
        protected MoveAnimator mMoveAnimator;
        protected int mScreenWidth;
        private int mScreenHeight;
        private int mStatusBarHeight;
        boolean isMovable = true;//是否可移动
    
        public FloatingMagnetView(Context context) {
            this(context, null);
        }
    
        public FloatingMagnetView(Context context, AttributeSet attrs) {
            this(context, attrs, 0);
        }
    
        public FloatingMagnetView(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
            init();
        }
    
        private void init() {
            mMoveAnimator = new MoveAnimator();
            mStatusBarHeight = SystemUtils.getStatusBarHeight(getContext());
            setClickable(true);
            updateSize();
        }
    
        /**
         * 设置是否可移动
         */
        public void setIsMovable(boolean isMovable) {
            this.isMovable = isMovable;
        }
    
        @Override
        public boolean onTouchEvent(MotionEvent event) {
            if (event == null) {
                return false;
            }
            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    changeOriginalTouchParams(event);
                    updateSize();
                    mMoveAnimator.stop();
                    removeRunable();
                    onEndAppear();
                    break;
                case MotionEvent.ACTION_MOVE:
                    if (isMovable) {
                        updateViewPosition(event);
                    }
                    break;
                case MotionEvent.ACTION_UP:
                    moveToEdge();
                    break;
            }
            return true;
        }
    
        /**
         * 更新view的坐标位置
         */
        private void updateViewPosition(MotionEvent event) {
            setX(mOriginalX + event.getRawX() - mOriginalRawX);
            // 限制不可超出屏幕高度
            float desY = mOriginalY + event.getRawY() - mOriginalRawY;
            if (desY < 0) {
                desY = 0;
            }
            if (desY > mScreenHeight - getHeight() * 2) {
                desY = mScreenHeight - getHeight() * 2;
            }
            setY(desY);
        }
    
        private void changeOriginalTouchParams(MotionEvent event) {
            mOriginalX = getX();
            mOriginalY = getY();
            mOriginalRawX = event.getRawX();
            mOriginalRawY = event.getRawY();
        }
    
        protected void updateSize() {
            mScreenWidth = (SystemUtils.getScreenWidth(getContext()) - this.getWidth());
            mScreenHeight = SystemUtils.getScreenHeight(getContext());
        }
    
        public void moveToEdge() {
            float moveDistance = isNearestLeft() ? MARGIN_EDGE : mScreenWidth - MARGIN_EDGE;
            mMoveAnimator.start(moveDistance, getY());
        }
    
        protected boolean isNearestLeft() {
            int middle = mScreenWidth / 2;
            return getX() < middle;
        }
    
        protected class MoveAnimator implements Runnable {
    
            private Handler handler = new Handler(Looper.getMainLooper());
            private float destinationX;
            private float destinationY;
            private long startingTime;
    
            void start(float x, float y) {
                this.destinationX = x;
                this.destinationY = y;
                startingTime = System.currentTimeMillis();
                handler.post(this);
            }
    
            @Override
            public void run() {
                if (getRootView() == null || getRootView().getParent() == null) {
                    return;
                }
                float progress = Math.min(1, (System.currentTimeMillis() - startingTime) / 400f);
                float deltaX = (destinationX - getX()) * progress;
                float deltaY = (destinationY - getY()) * progress;
                move(deltaX, deltaY);
                if (progress < 1) {
                    handler.post(this);
                } else {
                    hideEdgeAnima();//开始隐藏
                }
            }
    
            private void stop() {
                handler.removeCallbacks(this);
            }
        }
    
        private void move(float deltaX, float deltaY) {
            setX(getX() + deltaX);
            setY(getY() + deltaY);
        }
    
        public void onAttach() {
        }
    
        public void onDetach() {
        }
    
        //关于靠边隐藏==============================================================================================
        boolean isHideEdge = true;//是否隐藏边缘
        protected static final int HEDE_FLOAT_VIEW_TIME = 3000;//靠边隐藏时间
        protected static final int MES_ANIMA_LEFT = 0;
        protected static final int MES_ANIMA_RIGHT = 1;
        protected static final int VIEW_GONE = 2;
        protected static final int VIEW_Transparent = 3;
    
        protected AnimationSet animationleft;
        protected AnimationSet animationright;
    
        /**
         * 设置是否隐藏边缘
         */
        public void setIsHideEdge(boolean isHideEdge) {
            this.isHideEdge = isHideEdge;
        }
    
        /**
         * 隐藏边缘动画
         */
        protected void hideEdgeAnima() {
            if (isHideEdge) {
                if (isNearestLeft()) {
                    handler.postDelayed(myRunnableLeft, HEDE_FLOAT_VIEW_TIME);
                } else {
                    handler.postDelayed(myRunnableRigth, HEDE_FLOAT_VIEW_TIME);
                }
            }
        }
    
        private void removeRunable() {
            handler.removeCallbacksAndMessages(null);
        }
    
        protected Runnable runnableViewGone = new Runnable() {
            public void run() {
                Message message = handler.obtainMessage();
                message.what = VIEW_GONE;
                handler.sendMessage(message);
    
            }
        };
    
        protected Runnable runnableTransparent = new Runnable() {
            @Override
            public void run() {
                Message message = handler.obtainMessage();
                message.what = VIEW_Transparent;
                handler.sendMessage(message);
            }
        };
    
        protected Runnable myRunnableLeft = new Runnable() {
            public void run() {
                Message message = handler.obtainMessage();
                message.what = MES_ANIMA_LEFT;
                handler.sendMessage(message);
            }
        };
    
        protected Runnable myRunnableRigth = new Runnable() {
            public void run() {
                Message message = handler.obtainMessage();
                message.what = MES_ANIMA_RIGHT;
                handler.sendMessage(message);
            }
        };
    
        protected Handler handler = new Handler(Looper.getMainLooper()) {
    
            public void handleMessage(Message msg) {
    
                switch (msg.what) {
                    case MES_ANIMA_LEFT:
                        if (null == animationleft) {
                            animationleft = new AnimationSet(true);
                            animationleft.setDuration(250);
                            animationleft.addAnimation(new TranslateAnimation(Animation.RELATIVE_TO_SELF, 0, Animation.RELATIVE_TO_SELF, -0.5f, 0, 0, 0, 0));
                            animationleft.setFillAfter(true);
                        }
                        startAnimation(animationleft);
                        animationleft.setAnimationListener(ainimaLeft);
                        break;
    
                    case MES_ANIMA_RIGHT:
                        if (null == animationright) {
                            animationright = new AnimationSet(true);
                            animationright.setDuration(250);
                            animationright.addAnimation(new TranslateAnimation(Animation.RELATIVE_TO_SELF, 0, Animation.RELATIVE_TO_SELF, 0.5f, 0, 0, 0, 0));
                            animationright.setFillAfter(true);
                        }
                        startAnimation(animationright);
                        animationright.setAnimationListener(ainimaRigth);
                        break;
    
    //                case VIEW_GONE:
    //
    //                    imgFloatView.setVisibility(View.GONE);
    //                    Log.d(TAG, "handleMessage: bingo");
    //                    break;
    
                    case VIEW_Transparent:
                        break;
    
                    default:
                        break;
                }
    
            }
        };
    
        protected Animation.AnimationListener ainimaLeft = new Animation.AnimationListener() {
    
            @Override
            public void onAnimationStart(Animation animation) {
    
            }
    
            @Override
            public void onAnimationRepeat(Animation animation) {
    
            }
    
            @Override
            public void onAnimationEnd(Animation animation) {
                onEndHide();
            }
        };
    
        protected Animation.AnimationListener ainimaRigth = new Animation.AnimationListener() {
    
            @Override
            public void onAnimationStart(Animation animation) {
    
            }
    
            @Override
            public void onAnimationRepeat(Animation animation) {
    
            }
    
            @Override
            public void onAnimationEnd(Animation animation) {
                onEndHide();
            }
        };
    
        /**
         * 隐藏,点击又出现之后,由子类进行具体实现
         */
        protected void onEndAppear() {
        }
        /**
         * 隐藏半边之后,由子类进行具体实现
         */
        protected void onEndHide() {
        }
    
    }
    

    两个个辅助工具类:
    EnContext.java

    
    import android.app.Application;
    
    /**
     * Created by Yunpeng Li on 2018/11/8.
     */
    public class EnContext {
    
        private static final Application INSTANCE;
    
        static {
            Application app = null;
            try {
                app = (Application) Class.forName("android.app.AppGlobals").getMethod("getInitialApplication").invoke(null);
                if (app == null)
                    throw new IllegalStateException("Static initialization of Applications must be on main thread.");
            } catch (final Exception e) {
                e.printStackTrace();
                try {
                    app = (Application) Class.forName("android.app.ActivityThread").getMethod("currentApplication").invoke(null);
                } catch (final Exception ex) {
                    e.printStackTrace();
                }
            } finally {
                INSTANCE = app;
            }
        }
    
        public static Application get() {
            return INSTANCE;
        }
    }
    
    

    SystemUtils.java

    
    import android.content.Context;
    
    /**
     * Created by Yunpeng Li on 2018/3/15.
     */
    public class SystemUtils {
    
        public static int getStatusBarHeight(Context context) {
            int result = 0;
            int resourceId = context.getResources().getIdentifier("status_bar_height", "dimen", "android");
            if (resourceId > 0) {
                result = context.getResources().getDimensionPixelSize(resourceId);
            }
            return result;
        }
    
        public static int getScreenWidth(Context context) {
            int screenWith = -1;
            try {
                screenWith = context.getResources().getDisplayMetrics().widthPixels;
            } catch (Exception e) {
                e.printStackTrace();
            }
            return screenWith;
        }
    
        public static int getScreenHeight(Context context) {
            int screenHeight = -1;
            try {
                screenHeight = context.getResources().getDisplayMetrics().heightPixels;
            } catch (Exception e) {
                e.printStackTrace();
            }
            return screenHeight;
        }
    
    }
    
    

    应用例子:
    在基础类的地方创建一个全局的CFloatingManager.FloatingImp floatingImp对象,目的是为了保持对象唯一,然后在各个子类中进行引用:

    @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.main);
            if (null == floatingImp) {
                floatingImp = CFloatingManager.build()
                        .setLayout(R.layout.en_floating_view)//设置布局
                        .setInitViews(new CFloatingView.IFloatingViews() { // 可以在此处对布局内的子控件单独进行控制,todo:设置点击事件,则会影响到拖动事件
                            @Override
                            public void onInitViews(CFloatingView cFloatingView) {
                                cFloatingView.view.findViewById(R.id.icon).setOnClickListener(new View.OnClickListener() {
                                    @Override
                                    public void onClick(View v) {
                                        Toast.makeText(TestActivity.this, "点击了icon", Toast.LENGTH_SHORT).show();
                                    }
                                });
                            }
                        })
                        .setListener(new CFloatingView.MagnetViewListener() {//设置监听事件
                            @Override
                            public void onRemove(CFloatingView cFloatingView) {
                                Toast.makeText(TestActivity.this, "我没了", Toast.LENGTH_SHORT).show();
                            }
    
                            @Override
                            public void onClick(CFloatingView cFloatingView) {
                                Toast.makeText(TestActivity.this, "点到我了", Toast.LENGTH_SHORT).show();
                            }
    
                            @Override
                            public void onEndAppear(CFloatingView cFloatingView) {
                                ((ImageView)cFloatingView.view.findViewById(R.id.icon)).setImageResource(R.drawable.jy_sdk_float_window_normal);
                                Toast.makeText(TestActivity.this, "又出现了", Toast.LENGTH_SHORT).show();
                            }
    
                            @Override
                            public void onEndHide(CFloatingView cFloatingView) {
                                ((ImageView)cFloatingView.view.findViewById(R.id.icon)).setImageResource(R.drawable.jy_sdk_float_window_transparent);
                                Toast.makeText(TestActivity.this, "隐藏了一半", Toast.LENGTH_SHORT).show();
                            }
                        })
                        .setIsMovable(true)//控制是否可移动
                        .setIsHideEdge(true)//控制是否需要隐藏
                        .create();//创建实体
            }
        }
    
        @Override
        protected void onStart() {
            super.onStart();
            floatingImp.attach(this);//绑定实体至页面
        }
    
        @Override
        protected void onStop() {
            super.onStop();
            floatingImp.detach(this);//移除实体离开页面
        }
    

    en_floating_view.xml

    <?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content">
    
        <ImageView
            android:id="@+id/icon"
            android:layout_width="50dp"
            android:layout_height="50dp"
            android:src="@drawable/imuxuan" />
    
        <ImageView
            android:id="@+id/icon2"
            android:layout_width="50dp"
            android:layout_height="50dp"
            android:layout_toRightOf="@+id/icon"
            android:src="@drawable/imuxuan" />
    
    </RelativeLayout>
    

    其中的小图标我就不上了,更换一下就行

    相关文章

      网友评论

        本文标题:Android 悬浮窗--无需权限

        本文链接:https://www.haomeiwen.com/subject/icosnctx.html