热门标签 | HotTags
当前位置:  开发笔记 > Android > 正文

Android仿微信文章悬浮窗效果的实现代码

这篇文章主要介绍了Android仿微信文章悬浮窗效果的实现代码,需要的朋友可以参考下

序言

前些日子跟朋友聊天,朋友Z果粉,前些天更新了微信,说微信出了个好方便的功能啊,我问是啥功能啊,看看我大Android有没有,他说现在阅读公众号文章如果有人给你发微信你可以把这篇文章当作悬浮窗悬浮起来,方便你聊完天不用找继续阅读,听完是不是觉得这叫啥啊,我大Android微信版不是早就有这个功能了吗,我看文章的时候看到过有这个悬浮按钮,但是我一直没有使用过,试了一下还是挺方便的,就想着自己实现一下这个功能,下面看图,大家都习惯了无图言X

image

原理

看完动图我们来分析一下,如何在每个页面上都存在一个View呢,有些人可能会说,写在base里面,这样每次启动一个新的Activity都要往页面上addView一次,性能不好,再说了,我们作为一个优秀的程序员能干这种重复的事吗,这种方案果断打回去;既然这样的话那我们肯定要在全局加了,那么全局是哪呢?相信了解过Activity源码的朋友肯定知道,全局可以在Window层加啊,这样既能一次性搞定,又不影响性能,说干就干。

实现

1、权限

首先我们要考虑的一个问题就是权限问题,因为要适配Android 7.0 8.0,添加悬浮窗是需要申请权限的,适配的比较全,可以直接拿来用。这里需要注意的是,为了适配Android 8.0,Window的类型需要配置一下:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
 //Android 8.0
 mLayoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
} else {
 //其他版本
 mLayoutParams.type = WindowManager.LayoutParams.TYPE_PHONE;
}

2、添加ViewGroup到Window

判断好权限之后,直接添加就可以了

@SuppressLint("CheckResult")
private void showWindow(Context context) {
 mWindowManager = (WindowManager) context.getSystemService(WINDOW_SERVICE);
 mView = LayoutInflater.from(context).inflate(R.layout.article_window, null);

 ImageView ivImage = mView.findViewById(R.id.aw_iv_image);
 String imageUrl = SPUtil.getStringDefault(ARTICLE_IMAGE_URL, "");
 RequestOptions requestOptiOns= RequestOptions.circleCropTransform();
 requestOptions.placeholder(R.mipmap.ic_launcher_round).error(R.mipmap.ic_launcher_round);
 Glide.with(context).load(imageUrl).apply(requestOptions).into(ivImage);

 initListener(context);

 mLayoutParams = new WindowManager.LayoutParams();
 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
  mLayoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
 } else {
  mLayoutParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
 }
 mLayoutParams.format = PixelFormat.RGBA_8888; //窗口透明
 mLayoutParams.gravity = Gravity.LEFT | Gravity.TOP; //窗口位置
 mLayoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
 mLayoutParams.width = 200;
 mLayoutParams.height = 200;
 mLayoutParams.x = mWindowManager.getDefaultDisplay().getWidth() - 200;
 mLayoutParams.y = 0;
 mWindowManager.addView(mView, mLayoutParams);
}

3、View的拖拽实现

借助WindowManager.LayoutParams来实现,mLayoutParams.x和mLayoutParams.y分别表示mView左上角的横纵坐标,所以我们只需要改动这两个值就行了,当ACTION_UP时,计算当前mView的中心点相对窗口的位置,然后将mView动态滑动到窗口左边或者右边:

//设置触摸滑动事件
mView.setOnTouchListener(new View.OnTouchListener() {
 int startX, startY; //起始点
 boolean isMove; //是否在移动
 long startTime;
 int finalMoveX; //最后通过动画将mView的X轴坐标移动到finalMoveX
 @Override
 public boolean onTouch(View v, MotionEvent event) {
  switch (event.getAction()) {
   case MotionEvent.ACTION_DOWN:
    startX = (int) event.getX();
    startY = (int) event.getY();
    startTime = System.currentTimeMillis();
    isMove = false;
    return false;
   case MotionEvent.ACTION_MOVE:
    mLayoutParams.x = (int) (event.getRawX() - startX);
    mLayoutParams.y = (int) (event.getRawY() - startY);
    updateViewLayout(); //更新mView 的位置
    return true;
   case MotionEvent.ACTION_UP:
    long curTime = System.currentTimeMillis();
    isMove = curTime - startTime > 100;
    //判断mView是在Window中的位置,以中间为界
    if (mLayoutParams.x + mView.getMeasuredWidth() / 2 >= mWindowManager.getDefaultDisplay().getWidth() / 2) {
     finalMoveX = mWindowManager.getDefaultDisplay().getWidth() - mView.getMeasuredWidth();
    } else {
     finalMoveX = 0;
    }
    //使用动画移动mView
    ValueAnimator animator = ValueAnimator.ofInt(mLayoutParams.x, finalMoveX).setDuration(Math.abs(mLayoutParams.x - finalMoveX));
    animator.addUpdateListener((ValueAnimator animation) -> {
     mLayoutParams.x = (int) animation.getAnimatedValue();
     updateViewLayout();
    });
    animator.start();
    return isMove;
  }
  return false;
 }
});

4、注意

为了让Window与Activity脱离,这里我们采用Service来做,通过Service来添加和移除View;在权限申请成功之后我们需要通知Service(其实是Activity,可能会有保存数据等操作)作相应改变(提供一个接口给Service),然后在Service中使用广播来通知Activity;最后一个需要注意的地方就是我们需要判断应用程序是否在前台还是后台来添加或移除Window,这里通过使用ActivityLifecycleCallbacks来监听Activity在前台的数量来判断应用程序是在前台还是后台

class ApplicationLifecycle : Application.ActivityLifecycleCallbacks {
 private var started: Int = 0
 override fun onActivityPaused(activity: Activity?) {
 }
 override fun onActivityResumed(activity: Activity?) {
 }
 override fun onActivityStarted(activity: Activity?) {
  started++
  if (started == 1) {
   Log.e("TAG", "应用在前台了!!!")
  }
 }
 override fun onActivityDestroyed(activity: Activity?) {
 }
 override fun onActivitySaveInstanceState(activity: Activity?, outState: Bundle?) {
 }
 override fun onActivityStopped(activity: Activity?) {
  started--
  if (started == 0) {
   Log.e("TAG", "应用在后台了!!!")
  }
 }
 override fun onActivityCreated(activity: Activity?, savedInstanceState: Bundle?) {
 }
}

本文代码已传至Github,有需要的朋友可以下载下来看看。

总结

以上所述是小编给大家介绍的Android仿微信文章悬浮窗效果,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对网站的支持!


推荐阅读
  • 嵌入式开发环境搭建与文件传输指南
    本文详细介绍了如何为嵌入式应用开发搭建必要的软硬件环境,并提供了通过串口和网线两种方式将文件传输到开发板的具体步骤。适合Linux开发初学者参考。 ... [详细]
  • SQLite 动态创建多个表的需求在网络上有不少讨论,但很少有详细的解决方案。本文将介绍如何在 Qt 环境中使用 QString 类轻松实现 SQLite 表的动态创建,并提供详细的步骤和示例代码。 ... [详细]
  • 解决微信电脑版无法刷朋友圈问题:使用安卓远程投屏方案
    在工作期间想要浏览微信和朋友圈却不太方便?虽然微信电脑版目前不支持直接刷朋友圈,但通过远程投屏技术,可以轻松实现在电脑上操作安卓设备的功能。 ... [详细]
  • 国内BI工具迎战国际巨头Tableau,稳步崛起
    尽管商业智能(BI)工具在中国的普及程度尚不及国际市场,但近年来,随着本土企业的持续创新和市场推广,国内主流BI工具正逐渐崭露头角。面对国际品牌如Tableau的强大竞争,国内BI工具通过不断优化产品和技术,赢得了越来越多用户的认可。 ... [详细]
  • Android 渐变圆环加载控件实现
    本文介绍了如何在 Android 中创建一个自定义的渐变圆环加载控件,该控件已在多个知名应用中使用。我们将详细探讨其工作原理和实现方法。 ... [详细]
  • 理解存储器的层次结构有助于程序员优化程序性能,通过合理安排数据在不同层级的存储位置,提升CPU的数据访问速度。本文详细探讨了静态随机访问存储器(SRAM)和动态随机访问存储器(DRAM)的工作原理及其应用场景,并介绍了存储器模块中的数据存取过程及局部性原理。 ... [详细]
  • Android LED 数字字体的应用与实现
    本文介绍了一种适用于 Android 应用的 LED 数字字体(digital font),并详细描述了其在 UI 设计中的应用场景及其实现方法。这种字体常用于视频、广告倒计时等场景,能够增强视觉效果。 ... [详细]
  • 本章将深入探讨移动 UI 设计的核心原则,帮助开发者构建简洁、高效且用户友好的界面。通过学习设计规则和用户体验优化技巧,您将能够创建出既美观又实用的移动应用。 ... [详细]
  • MySQL 数据库迁移指南:从本地到远程及磁盘间迁移
    本文详细介绍了如何在不同场景下进行 MySQL 数据库的迁移,包括从一个硬盘迁移到另一个硬盘、从一台计算机迁移到另一台计算机,以及解决迁移过程中可能遇到的问题。 ... [详细]
  • 并发编程:深入理解设计原理与优化
    本文探讨了并发编程中的关键设计原则,特别是Java内存模型(JMM)的happens-before规则及其对多线程编程的影响。文章详细介绍了DCL双重检查锁定模式的问题及解决方案,并总结了不同处理器和内存模型之间的关系,旨在为程序员提供更深入的理解和最佳实践。 ... [详细]
  • TechStride 网站
    TechStride 成立于2014年初,致力于互联网前沿技术、产品创意及创业内容的聚合、搜索、学习与展示。我们旨在为互联网从业者提供更高效的新技术搜索、学习、分享和产品推广平台。 ... [详细]
  • 本文作者分享了在阿里巴巴获得实习offer的经历,包括五轮面试的详细内容和经验总结。其中四轮为技术面试,一轮为HR面试,涵盖了大量的Java技术和项目实践经验。 ... [详细]
  • 深入解析Java枚举及其高级特性
    本文详细介绍了Java枚举的概念、语法、使用规则和应用场景,并探讨了其在实际编程中的高级应用。所有相关内容已收录于GitHub仓库[JavaLearningmanual](https://github.com/Ziphtracks/JavaLearningmanual),欢迎Star并持续关注。 ... [详细]
  • Java项目分层架构设计与实践
    本文探讨了Java项目中应用分层的最佳实践,不仅介绍了常见的三层架构(Controller、Service、DAO),还深入分析了各层的职责划分及优化建议。通过合理的分层设计,可以提高代码的可维护性、扩展性和团队协作效率。 ... [详细]
  • 简化报表生成:EasyReport工具的全面解析
    本文详细介绍了EasyReport,一个易于使用的开源Web报表工具。该工具支持Hadoop、HBase及多种关系型数据库,能够将SQL查询结果转换为HTML表格,并提供Excel导出、图表显示和表头冻结等功能。 ... [详细]
author-avatar
mobiledu2502905277
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有