版权声明:本文为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);
}
}
完。