最近工(xian)作(de)需(dan)要(teng),封装了一个提示型的悬浮窗工具类,简化了悬浮窗的创建显示和隐藏等步骤,并预定义了上中下三种简单的进场退场动画,拓展了悬浮窗自定义消失时间的功能。

使用方法很简单:

1.实例化悬浮窗工具类FloatView

FloatView floatView = new FloatView(context);

2.传入需要显示在悬浮窗上的view,可以是xml也可以是自定义view

//inflate xml类型view
LayoutInflater inflate = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
mView = inflate.inflate(R.layout.my_view, null);
floatView.setView(mView);

//或自定义类型view
VolumeBar volumeBar = new TVolumeBar(context);
floatView.setView(volumeBar);

3.可自定义悬浮窗的宽高, 默认是wrap_content

//可自定义宽高,不设置则默认均为wrap_content
//单位是px,建议使用资源文件定义宽高,使用resources.getDimensionPixelSize()获取,方便做不同分辨率适配
floatView.setWidth(250);
floatView.setHeight(40);

4.可自定义显示时间,工具类中预定义了三种方案,分别是短时间(2秒)显示,长时间(4秒)显示,一直显示(直至主动隐藏悬浮窗),也可以自定义显示时间(单位秒)

//短时间显示
floatView.setDuration(FloatView.LENGTH_SHORT);

//长时间显示
floatView.setDuration(FloatView.LENGTH_LONG);

//一直显示
floatView.setDuration(FloatView.LENGTH_ALWAYS);

//自定义显示时间,单位秒,10秒后消失
floatView.setDuration(10);

5.自定义布局位置,工具类预定义了3种布局方案

{@link #TOP} 置顶并水平铺满
{@link #CENTER} 居中自适应
{@link #BOTTOM} 置底并水平铺满
预定义方案可以通过 defaultAnim 决定是否使用默认动画
不使用默认动画可以通过 {@link #setWindowAnimations} 自定义
除了三种预定义方案,还可传入 Gravity 自定义参数进行布局

//使用预定义TOP布局,水平铺满并自适应高度
//使用预定义动画,自上而下淡入,自下而上淡出
floatView.setGravity(FloatView.TOP, true);

//使用预定义CENTER布局,宽度高度均自适应
//不实用预定义动画
floatView.setGravity(FloatView.CENTER, false);

//自定义Gravity,使悬浮窗布局在屏幕右侧
floatView.setGravity(Gravity.RIGHT, false);

6.可自定义进出场动画

<style name="my_anim">
    <item name="@android:windowEnterAnimation">@anim/my_anim_in</item>
    <item name="@android:windowExitAnimation">@anim/my_anim_out</item>
</style>

my_anim_in.xmlmy_anim_out.xml

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <translate
        android:fromXDelta="0"
        android:fromYDelta="-120%"
        android:toXDelta="0%"
        android:toYDelta="0%"
        android:fillAfter="true"
        android:duration="1000"
        />
</set>

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <translate
        android:fromXDelta="0"
        android:fromYDelta="0%"
        android:toXDelta="0%"
        android:toYDelta="-120%"
        android:fillAfter="true"
        android:duration="1000"
        />
</set>

自定义进出场动画详细写法请自行查找资料.

//参数传入R.style
floatView.setWindowAnimations(R.style.my_anim);

7.最后调用show()方法显示悬浮窗, 悬浮窗已显示的时候更新view的内容可以立即刷新view。已显示的时候再次调用show()方法会刷新悬浮窗的消失时间重新计时。

//调用show()前必须调用过setView(),否则不显示悬浮窗
floatView.show();
//在floatView显示时对显示的view进行内容修改可立刻刷新悬浮窗显示的内容
myView.setText("内容更新");
//再次调用show()方法可以刷新消失时间,重新倒计时
floatView.show();

//调用hide()播放退场动画并隐藏悬浮窗
floatView.hide();
//调用hideImmediate()立即隐藏悬浮窗,适合在悬浮窗父容器退出时调用,防止内存泄漏
floatView. hideImmediate();

8.在悬浮窗隐藏的时候会触发OnHideListener,可以在悬浮窗隐藏时做适当的处理

floatView.setOnHideListener(new FloatView.OnHideListener() {
    @Override
    public void onHide() {
        Log.e(TAG, "onHide");
    }
});

FloatView工具类源码

public class FloatView {

    public static final int TOP = 0x9731;
    public static final int CENTER = 0x9732;
    public static final int BOTTOM = 0x9733;

    public static final int LENGTH_ALWAYS = 0;
    public static final int LENGTH_SHORT = 2;
    public static final int LENGTH_LONG = 4;

    protected Context mContext;
    protected WindowManager.LayoutParams params;
    protected WindowManager mWM;
    protected View mView;
    protected Handler mHandler;
    protected int mDuration = LENGTH_SHORT;

    public FloatView(Context context){
        this.mContext = context;
        mHandler = new Handler();
        params = new WindowManager.LayoutParams();
        params.height = WindowManager.LayoutParams.WRAP_CONTENT;
        params.width = WindowManager.LayoutParams.WRAP_CONTENT;
        params.format = PixelFormat.TRANSLUCENT;
        params.type = WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY;
        params.setTitle("FloatView");
        params.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
                | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
        mWM = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
    }

    /**
     * 为悬浮窗设置View
     * @param view 自定义view
     */
    public void setView(View view) {
        if(mView != null && mView.getParent() != null) {
            mWM.removeView(mView);
        }
        mView = view;
    }

    /**
     * 设置悬浮窗宽度
     * @param width 宽度
     */
    public void setWidth(int width) {
        params.width = width;
    }

    /**
     * 设置悬浮窗高度
     * @param height 高度
     */
    public void setHeight(int height) {
        params.height = height;
    }

    /**
     * 自定义透明度,0.0f全透明,1.0f全不透明
     * @param alpha 透明度
     */
    public void setAlpha(float alpha) {
        params.alpha = alpha;
    }

    /**
     * 自定义窗口类型,默认 {@link WindowManager.LayoutParams#TYPE_SYSTEM_OVERLAY}
     * @param type 窗口类型
     */
    public void setType(int type) {
        params.type = type;
    }

    /**
     * 自定义窗口进出场动画
     * @param windowAnimations 动画资源res
     */
    public void setWindowAnimations(int windowAnimations) {
        params.windowAnimations = windowAnimations;
    }

    /**
     * 设置Gravity,已预定义三种Gravity分别是
     * {@link #TOP} 置顶并水平铺满
     * {@link #CENTER} 居中自适应
     * {@link #BOTTOM} 置底并水平铺满
     * 预定义方案可以通过 defaultAnim 决定是否使用默认动画
     * 不使用默认动画可以通过 {@link #setWindowAnimations} 自定义
     * 除了三种预定义方案,还可传入 Gravity 自定义参数进行布局
     * @param gravity 预定义或自定义Gravity
     * @param defaultAnim 是否使用默认动画
     */
    public void setGravity(int gravity, boolean defaultAnim) {
        switch (gravity) {
            case TOP:
                params.gravity = Gravity.FILL_HORIZONTAL | Gravity.TOP;
                if (defaultAnim) {
                    params.windowAnimations = R.style.top_anim_view;
                }
                break;
            case CENTER:
                params.gravity = Gravity.CENTER;
                params.width = WindowManager.LayoutParams.WRAP_CONTENT;
                if (defaultAnim) {
                    params.windowAnimations = R.style.center_anim_view;
                }
                break;
            case BOTTOM:
                params.gravity = Gravity.FILL_HORIZONTAL | Gravity.BOTTOM;
                params.width = WindowManager.LayoutParams.MATCH_PARENT;
                if (defaultAnim) {
                    params.windowAnimations = R.style.bottom_anim_view;
                }
                break;
            default:
                params.gravity = gravity;
        }
    }

    private Runnable hideRunnable = new Runnable() {
        @Override
        public void run() {
            hide();
        }
    };

    /**
     * 显示悬浮窗,如果mView还未初始化则不做处理
     * 如果mView已经显示,则只刷新显示时间,未显示添加到mWM
     */
    public void show() {
        if(mView == null) {
            return;
        }
        if (mView.getParent() == null) {
            mWM.addView(mView, params);
        }
        refreshHideTime();
    }

    /**
     * 隐藏悬浮窗,并触发onHide回调
     * 该隐藏方法会触发window退场动画
     * 如果mView的容器退出,建议调用{@link #hideImmediate()}
     * 否则有可能会导致 Activity 出现内存泄漏
     */
    public void hide(){
        if (mView != null && mView.getParent() != null) {
            mWM.removeView(mView);
            if (onHideListener != null) {
                onHideListener.onHide();
            }
        }
    }

    /**
     * 立刻隐藏悬浮窗,触发onHide回调
     * 不会触发window退场动画
     */
    public void hideImmediate(){
        if (mView != null && mView.getParent() != null) {
            params.windowAnimations = 0;
            mWM.updateViewLayout(mView, params);
            mWM.removeView(mView);
            if (onHideListener != null) {
                onHideListener.onHide();
            }
        }
    }

    /**
     * 设置悬浮窗持续显示时间,预定义三种方案
     * {@link #LENGTH_SHORT} 2秒后自动消失
     * {@link #LENGTH_LONG} 4秒后自动消失
     * {@link #LENGTH_ALWAYS} 持续显示悬浮窗,直至调用 hide 或 hideImmediate
     * 除了三种预定义方案,也可自定义显示时间,传入的参数值代表n秒后自动消失
     * @param duration 持续时间
     */
    public void setDuration(int duration) {
        mDuration = duration;
    }

    /**
     * 返回悬浮窗持续显示时间
     * @return 持续时间
     */
    public int getDuration() {
        return mDuration;
    }

    // 刷新悬浮窗的消失时间
    private void refreshHideTime() {
        //判断duration,如果大于#LENGTH_ALWAYS 则设置消失时间
        if (mDuration > LENGTH_ALWAYS) {
            mHandler.removeCallbacks(hideRunnable);
            mHandler.postDelayed(hideRunnable, mDuration * 1000);
        }
    }

    private OnHideListener onHideListener;

    public void setOnHideListener(OnHideListener onHideListener) {
        this.onHideListener = onHideListener;
    }

    public interface OnHideListener {
        void onHide();
    }
}