第一次写稿,写的不好,请大神多提建议


前言

前一段时间由于项目需要,写了一个可拖动可靠边吸附的悬浮窗,特意记录下来,方便大家一起学习


一、FloatingViewMagnet

悬浮窗的吸附管理类,代码示例:

package com.example.myapplication;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.view.ViewGroup;
import android.widget.FrameLayout;import androidx.annotation.NonNull;
import androidx.annotation.Nullable;/*** 吸附悬浮窗管理类* @author zgp* @data 2022/2/17*/
public class FloatingViewMagnet extends FrameLayout {public static final int MARGIN_EDGE = 13;private static final int TOUCH_TIME_THRESHOLD = 150;private float mOriginalRawX;private float mOriginalRawY;private float mOriginalX;private float mOriginalY;private MagnetViewListener mMagnetViewListener;private long mLastTouchDownTime;protected MoveAnimator mMoveAnimator;protected int mScreenWidth;private int mScreenHeight;private int mStatusBarHeight;private boolean isNearestLeft = true;private float mPortraitY;private Context mContext;public FloatingViewMagnet(@NonNull Context context) {this(context, null);}public FloatingViewMagnet(@NonNull Context context, @Nullable AttributeSet attrs) {this(context, attrs, 0);}public FloatingViewMagnet(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);this.mContext = context;init();}private void init() {mMoveAnimator = new MoveAnimator();mStatusBarHeight = AppUtils.INSTANCE.getStatusBarHeight(mContext);setClickable(true);}@Overridepublic 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: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;}public void moveToEdge() {moveToEdge(isNearestLeft(), false);}private 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()));}protected boolean isNearestLeft() {int middle = mScreenWidth / 2;isNearestLeft = getX() < middle;return isNearestLeft;}private void clearPortraitY() {mPortraitY = 0;}private void updateViewPosition(MotionEvent event) {setX(mOriginalX + event.getRawX() - mOriginalRawX);// 限制不可超出屏幕高度float desY = mOriginalY + event.getRawY() - mOriginalRawY;if (desY < mStatusBarHeight) {desY = mStatusBarHeight;}if (desY > mScreenHeight - getHeight()) {desY = mScreenHeight - getHeight();}setY(desY);}private void updateSize() {ViewGroup viewGroup = (ViewGroup) getParent();if (viewGroup != null) {mScreenWidth = viewGroup.getWidth() - getWidth();mScreenHeight = viewGroup.getHeight();}}private void changeOriginalTouchParams(MotionEvent event) {mOriginalX = getX();mOriginalY = getY();mOriginalRawX = event.getRawX();mOriginalRawY = event.getRawY();mLastTouchDownTime = System.currentTimeMillis();}public void onRemove() {if (mMagnetViewListener != null) {mMagnetViewListener.onRemove(this);}}public void setMagnetViewListener(MagnetViewListener magnetViewListener) {this.mMagnetViewListener = magnetViewListener;}private 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);}@Overridepublic 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);}@Overrideprotected 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() {@Overridepublic void run() {updateSize();moveToEdge(isNearestLeft, isLandscape);}});}}private void markPortraitY(boolean isLandscape) {if (isLandscape) {mPortraitY = getY();}}
}

2.FloatingView

悬浮窗,代码如下(示例):

package com.example.myapplicationimport android.app.Activity
import android.content.Context
import android.os.Handler
import android.os.Looper
import android.view.Gravity
import android.view.View
import android.view.ViewGroup
import android.widget.FrameLayout
import android.widget.RelativeLayout
import android.R
import androidx.activity.ComponentActivity
import androidx.core.view.ViewCompat
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleObserver
import androidx.lifecycle.OnLifecycleEvent
import java.lang.ref.WeakReference/*** <悬浮窗管理器>* @author zgp* @data 2022/2/17*/
object FloatingView: BaseFloatingBiew, LifecycleObserver {private var sInstance: FloatingView? = null// 悬浮窗吸附管理类对象private var mFloatingViewMagnet: FloatingViewMagnet? = nullprivate var mContainer: WeakReference<FrameLayout>? = nullprivate var mLayoutParams: ViewGroup.LayoutParams = getParams()private var mContext: Context? = nullopen fun getInstance(context: Context): FloatingView {if (sInstance == null) {synchronized(FloatingView::class.java) {if (sInstance == null) {sInstance = FloatingViewmContext = contextif (context is ComponentActivity) {(context as ComponentActivity).lifecycle.addObserver(this)}}}}return sInstance!!}override fun remove() {Handler(Looper.getMainLooper()).post(object : Runnable{override fun run() {if (mFloatingViewMagnet == null) {return}if (ViewCompat.isAttachedToWindow(mFloatingViewMagnet!!) && getContainer() != null) {getContainer()?.removeView(mFloatingViewMagnet)}mFloatingViewMagnet = null}})if (sInstance != null) {sInstance = null}}private fun ensureFloatingView(context: Context) {synchronized(this) {if (mFloatingViewMagnet != null) {return}mFloatingViewMagnet = EnFloatingView(context)mFloatingViewMagnet?.layoutParams = mLayoutParams// 关闭按钮的点击监听(mFloatingViewMagnet as EnFloatingView).mCloseBUtton?.setOnClickListener {mFloatingViewMagnet?.onRemove()remove()}addViewToWindow(mFloatingViewMagnet!!)}}@OnLifecycleEvent(Lifecycle.Event.ON_START)fun onStart() {
//        AppLog.d("FloatingView", "onResume, mContext=$mContext")attach(mContext as Activity)}@OnLifecycleEvent(Lifecycle.Event.ON_STOP)fun onStop() {
//        AppLog.d("FloatingView", "onStop, mContext=$mContext")detach(mContext as Activity)}/*** 添加显示悬浮窗*/override fun add(): FloatingView {AppLog.d("FloatingView", "add, mContext=$mContext")ensureFloatingView(mContext!!)return this}/*** 注册悬浮窗*/override fun attach(activity: Activity): FloatingView {getActivityRoot(activity)?.let { attach(it) }return this}private fun attach(frameLayout: FrameLayout) {if (frameLayout == null || mFloatingViewMagnet == null) {mContainer = WeakReference(frameLayout)return}if (mFloatingViewMagnet!!.parent == frameLayout) {return}if (mFloatingViewMagnet!!.parent != null) {(mFloatingViewMagnet!!.parent as ViewGroup).removeView(mFloatingViewMagnet)}mContainer = WeakReference(frameLayout)frameLayout.addView(mFloatingViewMagnet)}/****/override fun detach(activity: Activity): FloatingView {getActivityRoot(activity)?.let { detach(it) }return this}private fun detach(frameLayout: FrameLayout) {if (mFloatingViewMagnet != null && frameLayout != null && ViewCompat.isAttachedToWindow(mFloatingViewMagnet!!)) {frameLayout.removeView(mFloatingViewMagnet)mFloatingViewMagnet = null}if (getContainer() == frameLayout) {mContainer = null}if (sInstance != null) {sInstance = null}}override fun getView(): FloatingViewMagnet? {return mFloatingViewMagnet}override fun customView(viewGroup: FloatingViewMagnet): FloatingView {mFloatingViewMagnet = viewGroupreturn this}override fun layoutParams(params: ViewGroup.LayoutParams): FloatingView {mLayoutParams = paramsmFloatingViewMagnet?.layoutParams = paramsreturn this}override fun listener(magnetViewListener: MagnetViewListener): FloatingView {mFloatingViewMagnet?.setMagnetViewListener(magnetViewListener)return this}private fun addViewToWindow(view: View) {if (getContainer() == null) {return}getContainer()?.addView(view)}private fun getContainer(): FrameLayout?{if (mContainer == null) {return null}return mContainer!!.get()}private fun getParams(): FrameLayout.LayoutParams {var params = FrameLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT,RelativeLayout.LayoutParams.WRAP_CONTENT)params.gravity = Gravity.CENTER_VERTICALparams.setMargins(15, params.topMargin, params.rightMargin, params.bottomMargin)return params}private fun getActivityRoot(activity: Activity): FrameLayout? {try {return activity.window.decorView.findViewById(R.id.content)} catch (e: Exception) {e.printStackTrace()}return null}
}

该类我加入了Lifecycle,是为了感知Activity生命周期的变化,不用再手动的去关闭悬浮窗。

3.MainActivity使用

public class MainActivity extends AppCompatActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);initView();}private void initView() {FloatingView.INSTANCE.add().listener(new MagnetViewListener() {@Overridepublic void onRemove(@NotNull FloatingViewMagnet magnetView) {}@Overridepublic void onClick(@NotNull FloatingViewMagnet magnetView) {// 点击监听方法}});}
}

最后感谢简书的这篇文章对我的启发安卓可拖拽悬浮按钮二,文章里面有吸附的效果,如果项目中需要可以参考下~

Android可拖动可吸附悬浮窗相关推荐

  1. Android检测是否有悬浮窗,Android 获取判断是否有悬浮窗权限的方法

    现在很多应用都会用到悬浮窗,很多国产rom把悬浮窗权限加入控制了,你就需要判断是否有悬浮窗权限,然后做对应操作. Android 原生有自带权限管理的,只是被隐藏了.看android源码在androi ...

  2. android悬浮动态权限,Android 获取判断是否有悬浮窗权限的方法

    现在很多应用都会用到悬浮窗,很多国产rom把悬浮窗权限加入控制了,你就需要判断是否有悬浮窗权限,然后做对应操作. Android 原生有自带权限管理的,只是被隐藏了.看android源码在androi ...

  3. react下移动端可吸附悬浮窗按钮,支持拖动拖拽功能

    基于react实现的移动端的可吸附悬浮按钮 预览地址(移动端): kkfor.github.io/suspend-but- 源码地址: github.com/kkfor/suspe- 安装 npm i ...

  4. android 仿微信来电_Android 悬浮窗功能实现(微信语音通话悬浮窗效果实现)

    目录 1.基本介绍 2.代码示例 3.实现效果及便捷工具类 4.仿微信语音通话悬浮窗效果实现 4.1 需求分析及效果展示 4.2 实现 5.最后 1.基本介绍 Android 界面绘制都是通过 Win ...

  5. Android 屏幕录制时去除悬浮窗

    最近在用Android8.0开发一个屏幕录制的功能,要求录制的视频不能有录屏的控制悬浮窗. 录屏方案使用的是MediaRecorder.MediaProjection.VirtualDisplay. ...

  6. android 实现录屏功能(悬浮窗)

    前言:网上关于录屏的介绍已经很多了,本篇文章也不过多介绍详细,主要是阐述其流程原理,输出demo,该demo样式仿照小米自带的系统录屏 1.如何录屏 Android中在5.0以上的版本中系统已经提供了 ...

  7. android课程表控件、悬浮窗、Todo应用、MVP框架、Kotlin完整项目源码

    Android精选源码 Android游戏2048 MVP Kotlin项目(RxJava+Rerotfit+OkHttp+Glide) Android基于自定义Span的富文本编辑器 android ...

  8. android 魅族 toast,小米 TYPE_TOAST 悬浮窗无效的原因

    Android中的悬浮窗显示是一个非常棘手的问题,网上已经有很多解决方案了,大致归为下面两类:设置WindowManager.LayoutParams.type = TYPE_SYSTEM_ALERT ...

  9. Android app打开系统界面设置悬浮窗权限

    1. AndroidManifest.xml添加权限 <uses-permission android:name="android.permission.SYSTEM_ALERT_WI ...

最新文章

  1. Apache Tomcat 7.0.93 发布,开源 Java Web 应用服务器
  2. C语言实现radon变换
  3. 软件和硬件的关系以及软件调动硬件的工作原理
  4. el-table改变宽度刷新不恢复
  5. ABAP OO小例子
  6. 转://RMAN跨平台可传输表空间和数据库
  7. 回放导出数据_王者荣耀本地视频怎么导出MP4格式视频文件?答案在这里
  8. (新聞) 友達光電 A+種子暑期實習計畫 埋下希望的光電種子 (日記)
  9. 昨天购入mac mini一台,4688元港币
  10. MySQL数据库迁移
  11. 2022内推 | 字节跳动校招 + 社招,包括NLP、CV和ASR和研究员等
  12. MyBatis学习(二)使用注解开发、Mybatis 执行流程、一对多多对一的结果集映射
  13. 君威u0073故障码解决_U0073故障码_U0073故障码是什么故障、排除方法、怎么消除_车主指南...
  14. android高清壁纸,40张极Cool的Android系统桌面壁纸
  15. 写贺卡给毕业师姐怎么写计算机系的,给师兄师姐的毕业贺卡寄语
  16. android三星s8底部菜单,【Android】三星Galaxy S8及S8+的屏幕适配
  17. mvvm与virtual dom算法的实践——“hoz”
  18. Unity 镜像sprite
  19. 计算机win10开机音乐,Windows10系统更改开关机声音的两种方法
  20. 【网站备案】2018年以后的阿里云备案以及公安备案流程最佳实践

热门文章

  1. 深度学习:交叉验证(Cross Validation)
  2. keep 虚拟路线修改器_二次元出圈搞综艺,杨颖、虞书欣、小鬼打造二次元虚拟偶像...
  3. 良好的协同管理,是数字时代的成功前提
  4. 逻辑推理题(C语言实现)
  5. 跨境电商平台大全,总有一个适合你
  6. (三十七:2021.01.13)Pre-MICCAI 2019学习(二)《前列腺近距离放射治疗中,检测粒子在CT中的3D位置和方向》
  7. Maven慕课网学习笔记
  8. 论文阅读:Entangled Watermarks as a Defense against Model Extraction
  9. 【社区周会】2021-06-01 内容概要
  10. VScode快捷键(win + mac)