4d5ac83ea618

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

android:layout_width="wrap_content"

android:layout_height="wrap_content">

android:id="@+id/icon"

android:layout_width="50dp"

android:layout_height="50dp"

android:src="@drawable/imuxuan" />

android:id="@+id/icon2"

android:layout_width="50dp"

android:layout_height="50dp"

android:layout_toRightOf="@+id/icon"

android:src="@drawable/imuxuan" />

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

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐