Android进阶——自定义View之WindowManager概述及利用WindowManager实现悬浮所有界面之上的悬浮窗Floating View(二)

版权声明:本文为CrazyMo_原创,转载请在显著位置注明原文链接 https://blog.csdn.net/CrazyMo_/article/details/88400048

引言

紧接上一篇Android进阶——自定义View之WindowManager概述及利用WindowManager实现悬浮所有界面之上的悬浮窗Floating View(一),这篇正式开始实现全界面悬浮View。

一、定义悬浮窗管理工具类

所谓的悬浮窗管理类并不是必须的,只是我习惯把这样的操作封装起来,利于管理和调用,本质上就是通过WindowManager实例调用对应的方法

package com.crazymo.common.ui.widgets.floating;

/**
 * Auther: Crazy.Mo
 * DateTime: 2019/3/6 11:31
 * Summary:
 */
import android.content.Context;
import android.view.View;
import android.view.WindowManager;

/**
 * 悬浮窗管理类
 */
public class FloatingManager {

    private WindowManager mWindowManager;
    private volatile static FloatingManager mInstance;
    private Context mContext;

    //单例模式,其实这里可以不用DLC形式的
    public static FloatingManager getInstance(Context context) {
        if (mInstance == null) {
            synchronized(FloatingManager.class) {
                mInstance = new FloatingManager(context);
            }
        }
        return mInstance;
    }

    private FloatingManager(Context context) {
        mContext = context;
        //初始化获取WindowManager对象
        mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
    }

    /**
     * 添加悬浮窗的View
     * @param view
     * @param params
     * @return
     */
    protected boolean addView(View view, WindowManager.LayoutParams params) {
        try {
            mWindowManager.addView(view, params);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }

    /**
     * 移除悬浮窗View
     * @param view
     * @return
     */
    protected boolean removeView(View view) {
        try {
            mWindowManager.removeView(view);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }

    /**
     * 更新悬浮窗参数
     * @param view
     * @param params
     * @return
     */
    protected boolean updateView(View view, WindowManager.LayoutParams params) {
        try {
            mWindowManager.updateViewLayout(view, params);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }
}

二、实现悬浮窗的要展示的View

package com.crazymo.common.ui.widgets.floating;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.PixelFormat;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.WindowManager;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.LinearLayout;

import com.crazymo.common.R;

/**
 * Auther: Crazy.Mo
 * DateTime: 2019/3/6 11:31
 * Summary: 悬浮窗view
 */
public class FloatingView extends FrameLayout {
    private Context mContext;
    private View mView;
    private ImageView mImv;
    private int mImgId;
    private int mTouchStartX, mTouchStartY;//手指按下时坐标
    private WindowManager.LayoutParams mParams;
    private FloatingManager mManager;
    private int tempX,tempY;
    private onFloatClicklistener mOnFloatClicklistener;
    private LinearLayout mLayout;
    private int startX,startY,endX,endY;

    public FloatingView(Context context) {
        this(context,null);
    }

    public FloatingView(Context context, AttributeSet attrs) {
        this(context, attrs,0);
    }

    public FloatingView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        this.mContext=context;
        init(attrs);
    }

    private void init(AttributeSet attrs) {
        initAttr(attrs);
        initViews();
    }

    private void initViews() {
        try {
            mView = LayoutInflater.from(mContext).inflate(R.layout.item_floating_view, null);
            mImv = mView.findViewById(R.id.imageview);
            mImv.setImageResource(mImgId);
            mManager = FloatingManager.getInstance(mContext);
            mLayout = mView.findViewById(R.id.ll_floating_container);
        }catch (Exception e){
            throw new RuntimeException("cmo:init FloatingView erro");
        }
        mLayout.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                mOnFloatClicklistener.onFloatingClick();
            }
        });
        mView.setOnTouchListener(new OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                switch (event.getAction()) {
                    case MotionEvent.ACTION_DOWN:
                        mTouchStartX = (int) event.getRawX();
                        mTouchStartY = (int) event.getRawY();
                        startX= (int) event.getRawX();
                        startY= (int) event.getRawY();
                        break;
                    case MotionEvent.ACTION_MOVE:
                        endX= (int) event.getRawX();
                        endY=(int)event.getRawY();
                        mParams.x = tempX + (int) event.getRawX() - mTouchStartX;
                        mParams.y = tempY + (int) event.getRawY() - mTouchStartY;
                        if(Math.abs(startX-endX)>25 || Math.abs(startY-endY)>25) {
                            mManager.updateView(mView, mParams);//移动的过程中及时更新View
                        }
                        break;
                    case MotionEvent.ACTION_CANCEL:
                    case MotionEvent.ACTION_UP:

                        tempX = mParams.x;
                        tempY = mParams.y;
                        break;
                }
                endX= (int) event.getRawX();
                endY=(int)event.getRawY();
                return false;
            }
        });
    }

    private void initAttr(AttributeSet attrs) {
        TypedArray typedArray = null;
        try {
            typedArray = mContext.obtainStyledAttributes(attrs, R.styleable.FloatingView);
            mImgId = typedArray.getResourceId(R.styleable.FloatingView_float_btn_src, R.drawable.select_btn_floating);
        } catch (Exception e) {
            throw new RuntimeException("cmo:init attrs failed!");
        } finally {
            if (typedArray != null) {
                typedArray.recycle();
            }
        }
    }

    public ImageView getImageView(){
        return this.mImv;
    }

    public void setImageView(int resId){
        mImv.setImageResource(resId);
    }

    /**
     * 通过WindowManager 添加悬浮View
     */
    public void show() {
        mParams = new WindowManager.LayoutParams();
        mParams.gravity = Gravity.TOP | Gravity.LEFT;
        mParams.x = 0;
        mParams.y = 100;
        //总是出现在应用程序窗口之上
        mParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
        //设置图片格式,效果为背景透明
        mParams.format = PixelFormat.RGBA_8888;
        mParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL |
                WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR |
                WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
        mParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
        mParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
        mManager.addView(mView, mParams);
        //逐帧动画
       /* AnimationDrawable animationDrawable=(AnimationDrawable)mImv.getDrawable();
        animationDrawable.start();
        */
    }

    public void hide() {
        mManager.removeView(mView);
    }

    public void setOnFloatingClickListener(onFloatClicklistener listener){
        this.mOnFloatClicklistener =listener;
    }
    public interface onFloatClicklistener {
         void onFloatingClick();
    }
}

三、定义悬浮View的属性和xml资源

1、定义悬浮View的布局

此处我就只显示一个按钮,具体的可以根据你们的需要进行修改

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:id="@+id/ll_floating_container"
    android:background="@android:color/transparent">

    <ImageView
        android:id="@+id/imageview"
        android:layout_width="@dimen/dp_40"
        android:layout_height="@dimen/dp_40"
        android:scaleType="fitXY"
        />
</LinearLayout>

2、引用悬浮View的布局

此处我把悬浮View 进行了封装成组件,并且针对上面的item布局定义了自定义的属性,这样封装的好处在于,你需要修改悬浮View的布局时候只需要去修改item布局,这个引用布局不用改变,除非你需要添加自定义属性。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:cmo="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/transparent">

    <com.crazymo.common.ui.widgets.floating.FloatingView
        android:layout_width="@dimen/dp_48"
        android:layout_height="@dimen/dp_48"
        cmo:float_btn_src="@drawable/select_btn_floating"
        />
</LinearLayout>

3、自定义View的属性

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="FloatingView">
        <attr name="float_btn_src" format="reference" />
    </declare-styleable>
</resources>

四、实现用于管理悬浮View的Service

由于悬浮View 需要随时随地的更新,所以放到Service中比较好,在这里有个小知识点

1、通过代码从xml中获取对应的属性并赋值

 @SuppressLint("ResourceType") 
 XmlResourceParser parser=mContext.getResources().getXml(R.layout.floating_view);
 AttributeSet attributes = Xml.asAttributeSet(parser);
 mFloatingView= new FloatingView(this,attributes);

2、实现Service

package com.crazymo.common.ui.widgets.floating;

import android.annotation.SuppressLint;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.res.XmlResourceParser;
import android.os.IBinder;
import android.util.AttributeSet;
import android.util.Xml;

import com.crazymo.common.R;

/**
 * Auther: Crazy.Mo
 * DateTime: 2019/3/6 11:31
 * Summary:启动服务自动生成FloatingView intent.putExtra(FloatingService.ACTION, FloatingService.SHOW);
 * startService(intent);而隐藏View则通过intent.putExtra(FloatingService.ACTION, FloatingService.HIDE);
 startService(intent);
 */
public class FloatingService extends Service {
    private Context mContext;
    public static final String ACTION="action";
    public static final String SHOW="show";
    public static final String HIDE="hide";
    private FloatingView mFloatingView;

    @Override
    public void onCreate(){
        super.onCreate();
        this.mContext=this;
        //mFloatingView=new FloatingView(this,R.drawable.select_btn_floating);
        @SuppressLint("ResourceType") XmlResourceParser parser=mContext.getResources().getXml(R.layout.floating_view);
        AttributeSet attributes = Xml.asAttributeSet(parser);
        mFloatingView= new FloatingView(this,attributes);
        mFloatingView.setOnFloatingClickListener(new FloatingView.onFloatClicklistener() {
            @Override
            public void onFloatingClick() {
            //todo 点击浮动按钮的时候 比如返回MainActivity
                Intent intent=new Intent();
                intent.setAction(MainActivity.ACTION_BACK_MAIN);
                LocalBroadcastManager.getInstance(mContext).sendBroadcast(intent);
                //手电
/*                if(!FlashLightUtils.isOpen()){
                    try {

                        FlashLightUtils.openFlash(getApplication());
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }else{

                    try {
                        FlashLightUtils.closeFlash();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }*/
            }
        });
    }

    @Override
    public IBinder onBind(Intent intent){
        return null;
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        if (intent != null) {
            String action=intent.getStringExtra(ACTION);
            if(SHOW.equals(action)){
                mFloatingView.show();
            }else if(HIDE.equals(action)){
                mFloatingView.hide();
            }
        }
        return super.onStartCommand(intent, flags, startId);
    }
}

五、注册并启动服务

package com.crazymo.crazybot;

import android.annotation.SuppressLint;
import android.content.Intent;
import android.os.Bundle;
import android.view.WindowManager;

import com.crazymo.common.ui.base.BaseActivity;
import com.crazymo.common.ui.widgets.floating.FloatingService;
import com.crazymo.common.ui.widgets.webview.SlideWebView;

public class MainActivity extends BaseActivity{

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        try {
            //强制打开GPU渲染
            getWindow().setFlags(WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED, WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
        } catch (Exception e) {
            throw new RuntimeException("cmo:HARDWARE_ACCELERATED not support!");
        }
    }

    @Override
    protected void onPause() {
        super.onPause();
        setFloatVisibility(false);
    }

    @Override
    protected void onResume() {
        super.onResume();
        setFloatVisibility(true);
    }

    @Override
    protected int getLayoutId() {
        return R.layout.activity_main;
    }
    
    public void setFloatVisibility(boolean isVisibility){
        Intent intent = new Intent(this, FloatingService.class);
        if(isVisibility) {
            intent.putExtra(FloatingService.ACTION, FloatingService.SHOW);
        }else {
            intent.putExtra(FloatingService.ACTION, FloatingService.HIDE);
        }
        startService(intent);
    }
}

在这里插入图片描述
完。

猜你喜欢

转载自blog.csdn.net/CrazyMo_/article/details/88400048