Android模仿微信浮窗功能的效果实现

转载请注明出处,谢谢:https://blog.csdn.net/HarryWeasley/article/details/82591320

源码地址:https://github.com/HarryWeasley/weChatFloatDemo

最近研究了微信悬浮窗的效果实现,写此文章记录一下,后面有我的GitHub源码地址。
老规矩,先放效果图,效果如下所示:

这里写图片描述

首先,说下项目的主要几个功能点。
1.app申请悬浮窗权限,通过WindowManager添加视图
2.一共添加三个视图,右下角两个视图,分别表示小删除视图和大删除视图,一个是真正的浮窗视图
3.webView消失动画效果实现

我的整个项目,是在这个项目https://github.com/yhaolpz/FloatWindow的基础上添加和修改的,还是要感谢之前的大神的无私奉献啊。

申请权限,该项目实现了一个工具类,对于小米手机不同的系统版本,需要专门去适配,下面来判断通过哪种方式申请权限:
FloatPhone.init()

@Overridepublic void init() {if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {req();} else if (Miui.rom()) {if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {req();} else {mLayoutParams.type = WindowManager.LayoutParams.TYPE_PHONE;Miui.req(mContext, new PermissionListener() {@Overridepublic void onSuccess() {mWindowManager.addView(mView, mLayoutParams);if (mPermissionListener != null) {mPermissionListener.onSuccess();}}@Overridepublic void onFail() {if (mPermissionListener != null) {mPermissionListener.onFail();}}});}} else {try {mLayoutParams.type = WindowManager.LayoutParams.TYPE_TOAST;mWindowManager.addView(mView, mLayoutParams);} catch (Exception e) {mWindowManager.removeView(mView);LogUtil.e("TYPE_TOAST 失败");req();}}}

如果是小米手机,用以下方式申请权限

package com.yhao.floatwindow;import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.provider.Settings;
import android.view.View;
import android.view.WindowManager;import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;import static com.yhao.floatwindow.Rom.isIntentAvailable;/*** Created by yhao on 2017/12/30.* https://github.com/yhaolpz* <p>* 需要清楚:一个MIUI版本对应小米各种机型,基于不同的安卓版本,但是权限设置页跟MIUI版本有关* 测试TYPE_TOAST类型:* 7.0:* 小米      5        MIUI8         -------------------- 失败* 小米   Note2       MIUI9         -------------------- 失败* 6.0.1* 小米   5                         -------------------- 失败* 小米   红米note3                  -------------------- 失败* 6.0:* 小米   5                         -------------------- 成功* 小米   红米4A      MIUI8         -------------------- 成功* 小米   红米Pro     MIUI7         -------------------- 成功* 小米   红米Note4   MIUI8         -------------------- 失败* <p>* 经过各种横向纵向测试对比,得出一个结论,就是小米对TYPE_TOAST的处理机制毫无规律可言!* 跟Android版本无关,跟MIUI版本无关,addView方法也不报错* 所以最后对小米6.0以上的适配方法是:不使用 TYPE_TOAST 类型,统一申请权限*/class Miui {private static final String miui = "ro.miui.ui.version.name";private static final String miui5 = "V5";private static final String miui6 = "V6";private static final String miui7 = "V7";private static final String miui8 = "V8";private static final String miui9 = "V9";private static List<PermissionListener> mPermissionListenerList;private static PermissionListener mPermissionListener;static boolean rom() {LogUtil.d(" Miui  : " + Miui.getProp());return Build.MANUFACTURER.equals("Xiaomi");}private static String getProp() {return Rom.getProp(miui);}/*** Android6.0以下申请权限*/static void req(final Context context, PermissionListener permissionListener) {if (PermissionUtil.hasPermission(context)) {permissionListener.onSuccess();return;}if (mPermissionListenerList == null) {mPermissionListenerList = new ArrayList<>();mPermissionListener = new PermissionListener() {@Overridepublic void onSuccess() {for (PermissionListener listener : mPermissionListenerList) {listener.onSuccess();}mPermissionListenerList.clear();}@Overridepublic void onFail() {for (PermissionListener listener : mPermissionListenerList) {listener.onFail();}mPermissionListenerList.clear();}};req_(context);}mPermissionListenerList.add(permissionListener);}private static void req_(final Context context) {switch (getProp()) {case miui5:reqForMiui5(context);break;case miui6:case miui7:reqForMiui67(context);break;case miui8:case miui9:reqForMiui89(context);break;}FloatLifecycle.setResumedListener(new ResumedListener() {@Overridepublic void onResumed() {if (PermissionUtil.hasPermission(context)) {mPermissionListener.onSuccess();} else {mPermissionListener.onFail();}}});}private static void reqForMiui5(Context context) {String packageName = context.getPackageName();Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);Uri uri = Uri.fromParts("package", packageName, null);intent.setData(uri);intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);if (isIntentAvailable(intent, context)) {context.startActivity(intent);} else {LogUtil.e("intent is not available!");}}private static void reqForMiui67(Context context) {Intent intent = new Intent("miui.intent.action.APP_PERM_EDITOR");intent.setClassName("com.miui.securitycenter","com.miui.permcenter.permissions.AppPermissionsEditorActivity");intent.putExtra("extra_pkgname", context.getPackageName());intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);if (isIntentAvailable(intent, context)) {context.startActivity(intent);} else {LogUtil.e("intent is not available!");}}private static void reqForMiui89(Context context) {Intent intent = new Intent("miui.intent.action.APP_PERM_EDITOR");intent.setClassName("com.miui.securitycenter", "com.miui.permcenter.permissions.PermissionsEditorActivity");intent.putExtra("extra_pkgname", context.getPackageName());intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);if (isIntentAvailable(intent, context)) {context.startActivity(intent);} else {intent = new Intent("miui.intent.action.APP_PERM_EDITOR");intent.setPackage("com.miui.securitycenter");intent.putExtra("extra_pkgname", context.getPackageName());intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);if (isIntentAvailable(intent, context)) {context.startActivity(intent);} else {LogUtil.e("intent is not available!");}}}/*** 有些机型在添加TYPE-TOAST类型时会自动改为TYPE_SYSTEM_ALERT,通过此方法可以屏蔽修改* 但是...即使成功显示出悬浮窗,移动的话也会崩溃*/private static void addViewToWindow(WindowManager wm, View view, WindowManager.LayoutParams params) {setMiUI_International(true);wm.addView(view, params);setMiUI_International(false);}private static void setMiUI_International(boolean flag) {try {Class BuildForMi = Class.forName("miui.os.Build");Field isInternational = BuildForMi.getDeclaredField("IS_INTERNATIONAL_BUILD");isInternational.setAccessible(true);isInternational.setBoolean(null, flag);} catch (Exception e) {e.printStackTrace();}}}

获取到权限后,就开始添加视图了。这里主要说下,右下角的小的消除视图,因为他有个动画效果,从右下角底部移动到某个坐标点,动画实现方式如下所示:

 private void showWithAnimator(final boolean isShow) {if (xCancelOffset == 0) {IFloatWindow cancelWindow = FloatWindow.get("cancel");if (cancelWindow != null) {int[] array = cancelWindow.getOffset();xCancelOffset = array[0];yCancelOffset = array[1];}}if (xCoordinate == 0) {xCoordinate = Util.getScreenWidth(mB.mApplicationContext);yCoordinate = Util.getScreenHeight(mB.mApplicationContext);}ValueAnimator mAnimator = new ValueAnimator();mAnimator.setDuration(500);if (isShow) {mAnimator.setObjectValues(new PointF(xCoordinate, yCoordinate), new PointF(xCancelOffset, yCancelOffset));} else {mAnimator.setObjectValues(new PointF(xCancelOffset, yCancelOffset), new PointF(xCoordinate, yCoordinate));}mAnimator.setEvaluator(new TypeEvaluator<PointF>() {@Overridepublic PointF evaluate(float fraction, PointF startValue, PointF endValue) {int valueX = (int) (startValue.getX() + fraction * (endValue.getX() - startValue.getX()));int valueY = (int) (startValue.getY() + fraction * (endValue.getY() - startValue.getY()));return new PointF(valueX, valueY);}});mAnimator.start();mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {@Overridepublic void onAnimationUpdate(ValueAnimator valueAnimator) {PointF point = (PointF) valueAnimator.getAnimatedValue();mFloatView.updateXY(point.getX(), point.getY());}});}

第二个难点是,webView的消失动画效果。

我试过很多次,webView想要实现一个圆角的渐变动画,很难实现,所以最后我选择了一个替代方法,就是先将webView的视图获取到,设置到ImageView中,然后将ImageView设置相应的动画即可:

代码如下所示:

package demo.com.lgx.wechatfloatdemo.weghit;import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.TypeEvaluator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.RectF;
import android.graphics.Xfermode;
import android.support.v7.widget.AppCompatImageView;
import android.util.AttributeSet;/*** Created by Harry on 2018/8/9.* desc:*/public class ScaleCircleImageView extends AppCompatImageView {private RectF mRectF;private ScaleCircleAnimation scaleCircleAnimation;private Paint mPaint;private ScaleCircleListener listener;Bitmap src;private Xfermode xfermode;public ScaleCircleImageView(Context context) {super(context);setWillNotDraw(false);}public ScaleCircleImageView(Context context, AttributeSet attrs) {super(context, attrs);setWillNotDraw(false);}public ScaleCircleImageView(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);setWillNotDraw(false);}@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);if (mPaint == null) {mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);mPaint.setDither(true);}if (mRectF == null) {mRectF = new RectF();}if (xfermode == null) {xfermode = new PorterDuffXfermode(PorterDuff.Mode.SRC_IN);}if (scaleCircleAnimation != null) {int left = scaleCircleAnimation.getLeftX();int top = scaleCircleAnimation.getTopY();int right = scaleCircleAnimation.getRightX();int bottom = scaleCircleAnimation.getBottomY();float radius = scaleCircleAnimation.getRadius();mRectF.set(left, top, right, bottom);
//            canvas.clipRect(mRectF);canvas.drawRoundRect(mRectF, radius, radius, mPaint);//设置XfermodemPaint.setXfermode(xfermode);//源图canvas.drawBitmap(src, 0, 0, mPaint);//还原XfermodemPaint.setXfermode(null);}}private int width;public void startAnimation(Bitmap bitmap, int width) {if (animationParam == null) {throw new IllegalArgumentException("animationParam has  been init!");}this.width = width;src = bitmap;ValueAnimator valueAnimator = new ValueAnimator();valueAnimator.setObjectValues(new ScaleCircleAnimation(animationParam.fromLeftX, animationParam.fromRightX, animationParam.fromTopY, animationParam.fromBottomY, animationParam.fromRadius),new ScaleCircleAnimation(animationParam.toLeftX, animationParam.toRightX, animationParam.toTopY, animationParam.toBottomY, animationParam.toRadius));valueAnimator.setEvaluator(new TypeEvaluator<ScaleCircleAnimation>() {@Overridepublic ScaleCircleAnimation evaluate(float fraction, ScaleCircleAnimation startValue, ScaleCircleAnimation endValue) {int leftX = (int) (startValue.getLeftX() + fraction * (endValue.getLeftX() - startValue.getLeftX()));int topY = (int) (startValue.getTopY() + fraction * (endValue.getTopY() - startValue.getTopY()));int rightX = (int) (startValue.getRightX() + fraction * (endValue.getRightX() - startValue.getRightX()));int bottomY = (int) (startValue.getBottomY() + fraction * (endValue.getBottomY() - startValue.getBottomY()));float radius = (startValue.getRadius() + fraction * (endValue.getRadius() - startValue.getRadius()));return new ScaleCircleAnimation(leftX, rightX, topY, bottomY, radius);}});valueAnimator.setDuration(500);valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {@Overridepublic void onAnimationUpdate(ValueAnimator animation) {scaleCircleAnimation = (ScaleCircleAnimation) animation.getAnimatedValue();invalidate();}});valueAnimator.start();valueAnimator.addListener(new AnimatorListenerAdapter() {@Overridepublic void onAnimationEnd(Animator animation) {super.onAnimationEnd(animation);if (listener != null) {listener.onAnimationEnd();}}});}private  AnimationParam animationParam;public  AnimationParam createAnmiationParam() {return animationParam = new AnimationParam();}public  class AnimationParam {int fromLeftX;int fromRightX;int toLeftX;int toRightX;int fromTopY;int fromBottomY;int toTopY;int toBottomY;int fromRadius;int toRadius;public AnimationParam setFromLeftX(int fromLeftX) {this.fromLeftX = fromLeftX;return this;}public AnimationParam setFromRightX(int fromRightX) {this.fromRightX = fromRightX;return this;}public AnimationParam setToLeftX(int toLeftX) {this.toLeftX = toLeftX;return this;}public AnimationParam setToRightX(int toRightX) {this.toRightX = toRightX;return this;}public AnimationParam setFromTopY(int fromTopY) {this.fromTopY = fromTopY;return this;}public AnimationParam setFromBottomY(int fromBottomY) {this.fromBottomY = fromBottomY;return this;}public AnimationParam setToTopY(int toTopY) {this.toTopY = toTopY;return this;}public AnimationParam setToBottomY(int toBottomY) {this.toBottomY = toBottomY;return this;}public AnimationParam setFromRadius(int fromRadius) {this.fromRadius = fromRadius;return this;}public AnimationParam setToRadius(int toRadius) {this.toRadius = toRadius;return this;}}public void setScaleCircleListener(ScaleCircleListener listener) {this.listener = listener;}public interface ScaleCircleListener {void onAnimationEnd();}
}

动画主要是以下的代码:

  mRectF.set(left, top, right, bottom);canvas.drawRoundRect(mRectF, radius, radius, mPaint);//设置XfermodemPaint.setXfermode(xfermode);

通过PorterDuffXfermode通过以下形式,来叠放两个图片和被切的视图

  xfermode = new PorterDuffXfermode(PorterDuff.Mode.SRC_IN);

其中,图片的获取是以下方式:

  View view = parent;Bitmap bitmap = Bitmap.createBitmap(view.getWidth(), view.getHeight(), Bitmap.Config.ARGB_8888);Canvas canvas = new Canvas(bitmap);view.draw(canvas);

项目中,因为项目中代码太多,还有很多内容没有在博客中写出来,如果大家有问题,可以在文末说出来,谢谢。

源码地址:https://github.com/HarryWeasley/weChatFloatDemo
参考文章:
Andorid 任意界面悬浮窗,实现悬浮窗如此简单
https://github.com/yhaolpz/FloatWindow
Android动画篇(二):颜色和形状改变的ChangeShapeAndColorButton
https://blog.csdn.net/u011315960/article/details/74984417
Android图形处理–PorterDuff.Mode那些事儿
https://blog.csdn.net/HardWorkingAnt/article/details/78045232

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/26392.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Qt 停靠悬浮窗口 使用实例

工程中我们常用到悬浮窗口&#xff0c;Qt 实现停靠和悬浮使用类QDockWidget&#xff0c; 效果&#xff1a; 悬浮窗口 这里主要介绍怎么使用&#xff1b; Part1.使用流程&#xff1a; 1. 创建QDockWidget对像的停靠窗体&#xff1b; QDockWidget *dw new QDockWidget(&quo…

android悬浮窗口的实现

当我们在手机上使用360安全卫士时&#xff0c;手机屏幕上时刻都会出现一个小浮动窗口&#xff0c;点击该浮动窗口可跳转到安全卫士的操作界面&#xff0c;而且该浮动窗口不受其他activity的覆盖影响仍然可见(多米音乐也有相关的和主界面交互的悬浮小窗口)。那么这种不受Activit…

ChatGPT提示词工程(六):Expanding扩展

目录 一、说明二、安装环境三、扩展&#xff08;Expanding&#xff09;1. 自定义自动回复客户电子邮件2. 提醒模型使用客户电子邮件中的详细信息3. 参数 temperature 一、说明 这是吴恩达 《ChatGPT Prompt Engineering for Developers》 的课程笔记系列。 本文是第七讲的内容…

通达信自动包络线指标公式以及ATR通道指标

根据亚历山大埃尔德在其著作《以交易为生》中的描述&#xff0c;自动包络线的设计思路是将通道看作试穿衬衫一样&#xff0c;寻找那些穿起来既不过松也不过紧的衬衫&#xff0c;只让手腕和脖子露在外面。自动包络线能够适应最近的行情波动&#xff0c;只有在极端情况下&#xf…

微信支付费率0.38还是0.6,0.2费率怎么开,3分钟申请教程

目前微信支付官方给到商家的费率统一为0.6%&#xff0c;部分线下实体店商家由服务商推广开户一般是用的0.38%的费率。 其实很多商户都不知道&#xff0c;其实还可以开通更低的费率&#xff0c;0.2&#xff5e;0.35%的费率。 现在就分享一个如何在几分钟申请提交开通0.2费率的…

微信支付申请费率0.2%的方法,百分百通过不求人

微信支付通用的费率都是0.6%,那么如何申请0.2%呢。方法很简单。

openai.error.RateLimitError: You exceeded your current quota, please check your plan and billing det

报错&#xff1a; 此错误消息表明您已达到API的最大月支出&#xff08;硬限制&#xff09;。这意味着您已经消耗了分配给计划的所有积分或单位&#xff0c;并且已经达到计费周期的限制。这种情况可能有几个原因&#xff0c;例如&#xff1a; 您使用的是高容量或复杂的服务&…

关于0.2%费率的微信支付,你需要了解一下。

经营流水大&#xff0c;支付手续费高&#xff0c;想要减少手续费&#xff1f;不了解微信支付申请流程&#xff0c;步骤太多太繁琐&#xff0c;想要快捷开户&#xff1f;不用担心&#xff0c;这些都可以用微信支付特约商户解决&#xff01; 为助力商家享受到更快捷的微信商户申…

定时任务原理方案综述

ChatGPT狂飙160天&#xff0c;世界已经不是之前的样子。 新建了人工智能中文站https://ai.weoknow.com 每天给大家更新可用的国内可用chatGPT资源 导读 本文主要介绍目前存在的定时任务处理解决方案。业务系统中存在众多的任务需要定时或定期执行&#xff0c;并且针对不同的系…

巴比特 | 元宇宙每日必读:AI搜索大战打响!微软官宣ChatGPT引入Bing,“文心一言”传首站将接入百度搜索,接下来是谁?...

摘要&#xff1a;据财联社报道&#xff0c;北京时间8日凌晨&#xff0c;微软宣布推出由ChatGPT支持的最新版Bing搜索引擎和Edge浏览器&#xff0c;新版Bing于今日上线&#xff0c;不过预览人数有限。微软计划近期推出移动版本&#xff0c;预览人数有望扩展至数百万人。那么&…

Docker 十周年 | 历史上的今天

整理 | 王启隆 透过「历史上的今天」&#xff0c;从过去看未来&#xff0c;从现在亦可以改变未来。 今天是 2023 年 3 月 20 日&#xff0c;在 1999 年的今天&#xff0c;人类首次成功乘热气球环球飞行。在 24 年的今天&#xff0c;瑞士人皮尔卡、英国人琼斯经过近 20 天的飞行…

macOS 下载汇总(系统、应用和教程) 2023 持续更新中

macOS Ventura 13, macOS Monterey 12, macOS Big Sur 11, macOS Catalina 10.15, macOS Mojave 10.14, macOS High Sierra 10.13, macOS Sierra 10.12 请访问原文链接&#xff1a;https://sysin.org/blog/macOS/&#xff0c;查看最新版。原创作品&#xff0c;转载请保留出处。…

网景浏览器正式发布 | 历史上的今天

整理 | 王启隆 透过「历史上的今天」&#xff0c;从过去看未来&#xff0c;从现在亦可以改变未来。 今天是 2022 年 12 月 15 日&#xff0c;在 56 年前的今天&#xff0c;美国动画制作家迪士尼逝世。他创造的米老鼠形象曾经给世界上的每一个孩子带来了欢乐&#xff1b;他是一位…

马化腾微信转发文章称要“收紧队形”,腾讯公关回应;微软宣布终止支持 Cortana;TypeScript 5.1发布|极客头条...

「极客头条」—— 技术人员的新闻圈&#xff01; CSDN 的读者朋友们早上好哇&#xff0c;「极客头条」来啦&#xff0c;快来看今天都有哪些值得我们技术人关注的重要新闻吧。 整理 | 梦依丹 出品 | CSDN&#xff08;ID&#xff1a;CSDNnews&#xff09; 一分钟速览新闻点&#…

苹果电脑惊现比特币白皮书,乔布斯会是中本聪吗?

原创&#xff1a;刘教链 * * * 隔夜比特币继续在28k附近横盘&#xff0c;静静等待30日线向上靠近。 市场总在百无聊赖之际找些乐子。昨天&#xff0c;圈子里热议最多的话题就是在苹果电脑系统中发现了比特币的白皮书[1]。 话题的源头是4月5号一个叫Andy Baio的网友在waxy.org发…

本地推理,单机运行,MacM1芯片系统基于大语言模型C++版本LLaMA部署“本地版”的ChatGPT

OpenAI公司基于GPT模型的ChatGPT风光无两&#xff0c;眼看它起朱楼&#xff0c;眼看它宴宾客&#xff0c;FaceBook终于坐不住了&#xff0c;发布了同样基于LLM的人工智能大语言模型LLaMA&#xff0c;号称包含70亿、130亿、330亿和650亿这4种参数规模的模型&#xff0c;参数是指…

安装Windows和MacOS双系统 (UEFI + GPT) 和常用软件及配置,一篇文章解决所有

调节显示器自带扬声器音量用Loopback 镜像下载地址:https://mirrors.dtops.cc/iso/MacOS/daliansky_macos/ 黑苹果AppleALC声卡驱动: 通过windows系统查看,右键我的电脑——管理——设备管理器——右键设备属性——详细信息——属性中选择硬件id,查看第一行“值”,注意dev后…

Win11系统更新后网络速度变的很慢怎么办?

Win11系统更新后网络速度变的很慢怎么办&#xff1f;有用户将自己的电脑系统升级到了Win11之后&#xff0c;出现了一些问题。电脑在使用中出现了网络速度变慢的情况。而且其它的设备在连接网络后速度是正常的&#xff0c;那么这个问题要怎么解决&#xff1f;来看看以下的方法分…

为什么微软的网站访问速度都这么慢?

微软这家企业大家还是挺熟悉的&#xff0c;微软自己研发的Windows系统很多人都在使用&#xff0c;微软除了操作系统还自己研发了浏览器&#xff0c;但是国内的小伙伴在使用微软网站的时候访问速度都比较慢&#xff0c;这是什么原因呢&#xff1f; 微软的的有些服务器在国外有些…