修改名称。

This commit is contained in:
2025-11-07 09:22:39 +08:00
parent d9cf55b053
commit a8dcfbb6a7
2203 changed files with 3 additions and 4 deletions

View File

@@ -0,0 +1,245 @@
package com.xscm.moduleutil.widget.floatingView;
import android.content.Context;
import android.content.res.Configuration;
import android.os.Handler;
import android.os.Looper;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import com.blankj.utilcode.util.BarUtils;
/**
* @Author $
* @Time $ $
* @Description $
*/
public class Floa extends FrameLayout {
public static final int MARGIN_EDGE = 13;
private float mOriginalRawX;
private float mOriginalRawY;
private float mOriginalX;
private float mOriginalY;
private MagnetViewListener mMagnetViewListener;
private static final int TOUCH_TIME_THRESHOLD = 150;
private long mLastTouchDownTime;
protected MoveAnimator mMoveAnimator;
protected int mScreenWidth;
private int mScreenHeight;
private int mStatusBarHeight;
private boolean isNearestLeft = true;
private float mPortraitY;
public void setMagnetViewListener(MagnetViewListener magnetViewListener) {
this.mMagnetViewListener = magnetViewListener;
}
public interface MagnetViewListener {
void onClick(Floa floa);
}
public Floa(Context context) {
this(context, null);
}
public Floa(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public Floa(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
mMoveAnimator = new MoveAnimator();
mStatusBarHeight = BarUtils.getStatusBarHeight();
setClickable(true);
updateSize();
// 确保视图初始位置在屏幕内
setX(Math.max(0, Math.min(getX(), mScreenWidth - getWidth())));
setY(Math.max(mStatusBarHeight, Math.min(getY(), mScreenHeight - getHeight())));
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (event == null) {
return false;
}
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
changeOriginalTouchParams(event);
updateSize();
mMoveAnimator.stop();
break;
case MotionEvent.ACTION_MOVE:
Log.d("FloatView", "ACTION_MOVE: " + event.getX() + ", " + event.getY()); // 添加日志
updateViewPosition(event);
break;
case MotionEvent.ACTION_UP:
clearPortraitY();
moveToEdge();
if (isOnClickEvent()) {
dealClickEvent();
}
break;
}
return true;
}
protected void dealClickEvent() {
if (mMagnetViewListener != null) {
mMagnetViewListener.onClick(this);
}
}
protected boolean isOnClickEvent() {
return System.currentTimeMillis() - mLastTouchDownTime < TOUCH_TIME_THRESHOLD;
}
private void updateViewPosition(MotionEvent event) {
// 计算新的Y位置
float desY = mOriginalY + event.getRawY() - mOriginalRawY;
// 限制Y位置不超出屏幕高度
if (desY < mStatusBarHeight) {
desY = mStatusBarHeight;
}
if (desY > mScreenHeight - getHeight()) {
desY = mScreenHeight - getHeight();
}
// 计算新的X位置
float desX = mOriginalX + event.getRawX() - mOriginalRawX;
// 限制X位置不超出屏幕宽度
if (desX < 0) {
desX = 0;
}
if (desX > mScreenWidth - getWidth()) {
desX = mScreenWidth - getWidth();
}
// 设置视图的新位置
setX(desX);
setY(desY);
// 确保视图可见
if (getVisibility() != VISIBLE) {
setVisibility(VISIBLE);
}
}
private void changeOriginalTouchParams(MotionEvent event) {
mOriginalX = getX();//getX()相对于控件X坐标的距离
mOriginalY = getY();
mOriginalRawX = event.getRawX();//getRawX()指控件在屏幕上的X坐标
mOriginalRawY = event.getRawY();
mLastTouchDownTime = System.currentTimeMillis();
}
protected void updateSize() {
ViewGroup viewGroup = (ViewGroup) getParent();
if (viewGroup != null) {
mScreenWidth = viewGroup.getWidth() ;
mScreenHeight = viewGroup.getHeight();
}else {
// 如果父视图为空,使用屏幕的实际宽度和高度
mScreenWidth = getResources().getDisplayMetrics().widthPixels;
mScreenHeight = getResources().getDisplayMetrics().heightPixels;
}
// mScreenWidth = (SystemUtils.getScreenWidth(getContext()) - this.getWidth());
// mScreenHeight = SystemUtils.getScreenHeight(getContext());
}
public void moveToEdge() {
moveToEdge(isNearestLeft(), false);
}
public void moveToEdge(boolean isLeft, boolean isLandscape) {
float moveDistance = isLeft ? MARGIN_EDGE : mScreenWidth - MARGIN_EDGE;
float y = getY();
if (!isLandscape && mPortraitY != 0) {
y = mPortraitY;
clearPortraitY();
}
mMoveAnimator.start(moveDistance, Math.min(Math.max(0, y), mScreenHeight - getHeight()));
}
private void clearPortraitY() {
mPortraitY = 0;
}
protected boolean isNearestLeft() {
int middle = mScreenWidth / 2;
isNearestLeft = getX() < middle;
return isNearestLeft;
}
public void onRemove() {
if (mMagnetViewListener != null) {
mMagnetViewListener=null;
}
}
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);
}
}
private void stop() {
handler.removeCallbacks(this);
}
}
private void move(float deltaX, float deltaY) {
setX(getX() + deltaX);
setY(getY() + deltaY);
}
@Override
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
if (getParent() != null) {
final boolean isLandscape = newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE;
markPortraitY(isLandscape);
((ViewGroup) getParent()).post(new Runnable() {
@Override
public void run() {
updateSize();
moveToEdge(isNearestLeft, isLandscape);
}
});
}
}
private void markPortraitY(boolean isLandscape) {
if (isLandscape) {
mPortraitY = getY();
}
}
}

View File

@@ -0,0 +1,201 @@
package com.xscm.moduleutil.widget.floatingView;
import android.content.Context;
import android.content.res.Configuration;
import android.os.Handler;
import android.os.Looper;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.widget.FrameLayout;
import com.blankj.utilcode.util.BarUtils;
import com.blankj.utilcode.util.ScreenUtils;
/**
* 可拖拽悬浮
* 稳定版悬浮窗控件 - 支持点击/拖动分离、适配横竖屏、自动吸附边缘
*/
public class FloatingMagnetView extends FrameLayout {
public static final int MARGIN_EDGE = 0;
private float mOriginalX, mOriginalY; // 当前 View 的坐标
private float mOriginalRawX, mOriginalRawY; // 触摸点的原始屏幕坐标
private long mLastTouchDownTime;
private boolean isDragging = false;
private boolean isNearestLeft = true;
private int mScreenWidth;
private int mScreenHeight;
private int mStatusBarHeight;
private MoveAnimator mMoveAnimator;
private OnFloatingClickListener listener;
private static final int TOUCH_TIME_THRESHOLD = 150;
private static final float CLICK_DRAG_THRESHOLD = 10;
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 = BarUtils.getStatusBarHeight();
setClickable(true);
updateSize();
}
@Override
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
updateSize();
moveToEdge(isNearestLeft);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
return false; // 不拦截,让子控件优先处理点击
case MotionEvent.ACTION_MOVE:
float dx = Math.abs(ev.getX() - (mOriginalRawX - mOriginalX));
float dy = Math.abs(ev.getY() - (mOriginalRawY - mOriginalY));
if (dx > CLICK_DRAG_THRESHOLD || dy > CLICK_DRAG_THRESHOLD) {
isDragging = true;
}
return isDragging;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
isDragging = false;
return false;
default:
return super.onInterceptTouchEvent(ev);
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (event == null) return false;
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mOriginalX = getX();
mOriginalY = getY();
mOriginalRawX = event.getRawX();
mOriginalRawY = event.getRawY();
mLastTouchDownTime = System.currentTimeMillis();
mMoveAnimator.stop();
isDragging = false;
break;
case MotionEvent.ACTION_MOVE:
if (isDragging) {
float newX = mOriginalX + (event.getRawX() - mOriginalRawX);
float newY = mOriginalY + (event.getRawY() - mOriginalRawY);
// 限制 Y 轴边界
if (newY < mStatusBarHeight) newY = mStatusBarHeight;
if (newY > mScreenHeight - getHeight()) newY = mScreenHeight - getHeight();
setX(newX);
setY(newY);
}
break;
case MotionEvent.ACTION_UP:
if (isOnClickEvent()) {
if (listener != null) {
listener.onClick();
}
} else {
moveToEdge();
}
isDragging = false;
break;
}
return true;
}
private boolean isOnClickEvent() {
long time = System.currentTimeMillis() - mLastTouchDownTime;
float dx = Math.abs(getX() - mOriginalX);
float dy = Math.abs(getY() - mOriginalY);
return time < TOUCH_TIME_THRESHOLD && dx < CLICK_DRAG_THRESHOLD && dy < CLICK_DRAG_THRESHOLD;
}
private void updateSize() {
mScreenWidth = ScreenUtils.getScreenWidth() - getWidth();
mScreenHeight = ScreenUtils.getScreenHeight();
}
public void moveToEdge() {
moveToEdge(isNearestLeft());
}
public void moveToEdge(boolean isLeft) {
float moveX = isLeft ? MARGIN_EDGE : mScreenWidth - MARGIN_EDGE;
mMoveAnimator.start(moveX, getY());
isNearestLeft = isLeft;
}
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, destinationY;
private long startTime;
void start(float x, float y) {
this.destinationX = x;
this.destinationY = y;
this.startTime = System.currentTimeMillis();
handler.post(this);
}
@Override
public void run() {
if (getRootView() == null || getRootView().getParent() == null) return;
float progress = Math.min(1f, (System.currentTimeMillis() - startTime) / 400f);
float deltaX = (destinationX - getX()) * progress;
float deltaY = (destinationY - getY()) * progress;
setX(getX() + deltaX);
setY(getY() + deltaY);
if (progress < 1f) {
handler.post(this);
}
}
void stop() {
handler.removeCallbacks(this);
}
}
public void setOnFloatingClickListener(OnFloatingClickListener listener) {
this.listener = listener;
}
public interface OnFloatingClickListener {
void onClick();
}
}

View File

@@ -0,0 +1,197 @@
package com.xscm.moduleutil.widget.floatingView;
import android.app.Activity;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.RelativeLayout;
import androidx.annotation.LayoutRes;
import androidx.core.view.ViewCompat;
import com.xscm.moduleutil.base.CommonAppContext;
import java.lang.ref.WeakReference;
/**
* @ClassName FloatingView
* @Description 悬浮窗管理器
*/
public class FloatingView implements IFloatingView {
private View mFloatingView;
private static volatile FloatingView mInstance;
private WeakReference<FrameLayout> mContainer;
@LayoutRes
private int mLayoutId;
private ViewGroup.LayoutParams mLayoutParams = getParams();
private FloatingView() {
}
public static FloatingView get() {
if (mInstance == null) {
synchronized (FloatingView.class) {
if (mInstance == null) {
mInstance = new FloatingView();
}
}
}
return mInstance;
}
@Override
public FloatingView remove() {
orderId = 0;
if (mFloatingView == null) {
return this;
}
if (ViewCompat.isAttachedToWindow(mFloatingView) && getContainer() != null) {
getContainer().removeView(mFloatingView);
}
mFloatingView = null;
return this;
}
private void ensureFloatingView() {
synchronized (this) {
if (mFloatingView != null) {
addViewToWindow(mFloatingView);
return;
}
View view = LayoutInflater.from(CommonAppContext.getInstance()).inflate(mLayoutId, null);
mFloatingView = view;
view.setLayoutParams(mLayoutParams);
view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
}
});
addViewToWindow(view);
}
}
@Override
public FloatingView add() {
ensureFloatingView();
return this;
}
@Override
public FloatingView attach(Activity activity) {
attach(getActivityRoot(activity));
return this;
}
@Override
public FloatingView attach(FrameLayout container) {
if (container == null || mFloatingView == null) {
mContainer = new WeakReference<>(container);
return this;
}
if (mFloatingView.getParent() == container) {
return this;
}
if (getContainer() != null && mFloatingView.getParent() == getContainer()) {
getContainer().removeView(mFloatingView);
}
mContainer = new WeakReference<>(container);
container.addView(mFloatingView);
return this;
}
@Override
public FloatingView detach(Activity activity) {
detach(getActivityRoot(activity));
return this;
}
@Override
public FloatingView detach(FrameLayout container) {
if (mFloatingView != null && container != null && ViewCompat.isAttachedToWindow(mFloatingView)) {
container.removeView(mFloatingView);
}
if (getContainer() == container) {
mContainer = null;
}
return this;
}
@Override
public View getView() {
return mFloatingView;
}
@Override
public FloatingView customView(ViewGroup viewGroup) {
mFloatingView = viewGroup;
return this;
}
@Override
public FloatingView customView(@LayoutRes int resource) {
mLayoutId = resource;
return this;
}
@Override
public FloatingView layoutParams(ViewGroup.LayoutParams params) {
mLayoutParams = params;
if (mFloatingView != null) {
mFloatingView.setLayoutParams(params);
}
return this;
}
private void addViewToWindow(final View view) {
if (getContainer() == null) {
return;
}
getContainer().addView(view);
}
private FrameLayout getContainer() {
if (mContainer == null) {
return null;
}
return mContainer.get();
}
private FrameLayout.LayoutParams getParams() {
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, 500);
return params;
}
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;
}
private boolean isExpand = true;
/**
* 设置展开或者收起
*
* @param expand
*/
public void setExpand(boolean expand) {
isExpand = expand;
}
private int orderId;
}

View File

@@ -0,0 +1,33 @@
package com.xscm.moduleutil.widget.floatingView;
import android.app.Activity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import androidx.annotation.LayoutRes;
public interface IFloatingView {
FloatingView remove();
FloatingView add();
FloatingView attach(Activity activity);
FloatingView attach(FrameLayout container);
FloatingView detach(Activity activity);
FloatingView detach(FrameLayout container);
View getView();
FloatingView customView(ViewGroup viewGroup);
FloatingView customView(@LayoutRes int resource);
FloatingView layoutParams(ViewGroup.LayoutParams params);
}

View File

@@ -0,0 +1,195 @@
package com.xscm.moduleutil.widget.floatingView;
import android.app.Activity;
import android.os.Handler;
import android.os.Looper;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.RelativeLayout;
import androidx.annotation.LayoutRes;
import androidx.core.view.ViewCompat;
import com.xscm.moduleutil.base.CommonAppContext;
import java.lang.ref.WeakReference;
/**
* @ClassName FloatingView
* @Description 悬浮窗管理器
*/
public class NotifyFloatingView {
private View mFloatingView;
private static volatile NotifyFloatingView mInstance;
private WeakReference<FrameLayout> mContainer;
@LayoutRes
private int mLayoutId;
private ViewGroup.LayoutParams mLayoutParams = getParams();
private NotifyFloatingView() {
}
public static NotifyFloatingView get() {
if (mInstance == null) {
synchronized (NotifyFloatingView.class) {
if (mInstance == null) {
mInstance = new NotifyFloatingView();
}
}
}
return mInstance;
}
public NotifyFloatingView remove() {
if (mFloatingView == null) {
return this;
}
if (ViewCompat.isAttachedToWindow(mFloatingView) && getContainer() != null) {
getContainer().removeView(mFloatingView);
}
mFloatingView = null;
return this;
}
private void ensureFloatingView() {
synchronized (this) {
if (mFloatingView != null) {
addViewToWindow(mFloatingView);
return;
}
View view = LayoutInflater.from(CommonAppContext.getInstance()).inflate(mLayoutId, null);
mFloatingView = view;
view.setLayoutParams(mLayoutParams);
view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
}
});
addViewToWindow(view);
}
}
public NotifyFloatingView add() {
ensureFloatingView();
handler.removeCallbacks(action);
handler.postDelayed(action, 3000);
return this;
}
public NotifyFloatingView attach(Activity activity) {
attach(getActivityRoot(activity));
return this;
}
public NotifyFloatingView attach(FrameLayout container) {
if (container == null || mFloatingView == null) {
mContainer = new WeakReference<>(container);
return this;
}
if (mFloatingView.getParent() == container) {
return this;
}
if (getContainer() != null && mFloatingView.getParent() == getContainer()) {
getContainer().removeView(mFloatingView);
}
mContainer = new WeakReference<>(container);
container.addView(mFloatingView);
return this;
}
public NotifyFloatingView detach(Activity activity) {
detach(getActivityRoot(activity));
return this;
}
public NotifyFloatingView detach(FrameLayout container) {
if (mFloatingView != null && container != null && ViewCompat.isAttachedToWindow(mFloatingView)) {
container.removeView(mFloatingView);
}
if (getContainer() == container) {
mContainer = null;
}
return this;
}
public View getView() {
return mFloatingView;
}
public NotifyFloatingView customView(ViewGroup viewGroup) {
mFloatingView = viewGroup;
return this;
}
public NotifyFloatingView customView(@LayoutRes int resource) {
mLayoutId = resource;
return this;
}
public NotifyFloatingView layoutParams(ViewGroup.LayoutParams params) {
mLayoutParams = params;
if (mFloatingView != null) {
mFloatingView.setLayoutParams(params);
}
return this;
}
private void addViewToWindow(final View view) {
if (getContainer() == null) {
return;
}
getContainer().addView(view);
}
private FrameLayout getContainer() {
if (mContainer == null) {
return null;
}
return mContainer.get();
}
private FrameLayout.LayoutParams getParams() {
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, 500);
return params;
}
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;
}
private Handler handler = new Handler(Looper.getMainLooper());
private Runnable action = new Runnable() {
@Override
public void run() {
NotifyFloatingView.get().remove();
}
};
}