Android 通用带箭头提示窗
简介
自定义PopupWindow, 适用于提示类弹窗。
使用自定义Drawable设置带箭头的背景,测试控件和弹窗的尺寸,自动设置弹窗的显示位置,让箭头指向锚点控件的中间位置,且根据锚点控件在屏幕的位置,自动适配弹窗显示位置。
适用于描述、解释弹窗。
一、效果
带箭头弹窗显示在控件的左侧,箭头相对控件居中对齐 ,且与控件左边框挨着
示例代码如下:
二、使用
GuZhiExplainPupopWindow window = new GuZhiExplainPupopWindow(this);//设置弹窗显示文案window.setTips("1234567890-34567890-【qweqwertyuiop[sdfghjkl;zxcvbnm,.我们一起走向富强");//获取窗口的背景drawable对象ArrowsDrawable ad = window.getArrowsDrawable();//设置drawable箭头的大小ad.setArrowsHeight(ConvertUtils.dp2px(8));//设置drawable中箭头与边框距离ad.setArrowsPadding(ConvertUtils.dp2px(10));//设置drawable的padding, 实际是设置显示文案的TextView的padding//ad.setPadding(ConvertUtils.dp2px(10));window.setPadding(ConvertUtils.dp2px(10));//设置drawable背景色ad.setColor(Color.DKGRAY);findViewById(R.id.tv33).setOnClickListener(view -> {if (!window.isShowing()) {//设置窗口显示位置(AUTO_HORIZONTAL 是水平位置,在控件的左侧或右侧,根据控件中心在屏幕中的位置决定)window.setShowPosition(GuZhiExplainPupopWindow.AUTO_HORIZONTAL);//显示弹窗,偏移量是0,默认是箭头在控件居中位置window.show(view, 0, 0);}});
三、自定义布局
重写initView方法,并设置TipsTv, 同时需要注意的是,在设置弹窗布局时,根布局的宽高属性是wrap_content,设置其它是不生效的,如果需要指定textView的宽或高,或弹窗尺寸,根布局使用某ViewGroup控件,再设置其子控件的尺寸。
GuZhiExplainPupopWindow window = new GuZhiExplainPupopWindow(this, R.layout.pupopwindow_view_guzhi_explain) {@Overridepublic void initView(View contentView) {//自定义布局初始化控件super.initView(contentView);TextView customTv = contentView.findViewById(R.id.explain_tv);setTipsTv(customTv);}};
四、源码
package com.ttkx.deviceinfo.bkchart.popupwindow;import android.content.Context;
import android.content.res.Resources;
import android.graphics.Color;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.drawable.ColorDrawable;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.PopupWindow;
import android.widget.TextView;import com.blankj.utilcode.util.ConvertUtils;
import com.blankj.utilcode.util.Utils;
import com.ttkx.deviceinfo.R;
import com.ttkx.deviceinfo.bkchart.ArrowsDrawable;
import com.ttkx.deviceinfo.bkchart.GuZhiActivity;import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;import androidx.annotation.IntDef;
import androidx.core.widget.PopupWindowCompat;/*** 估值说明弹窗* Created by liuyu*/
public class SimpleTipsPupopWindow extends PopupWindow {private ArrowsDrawable mBgDrawable;private TextView mTipsTv;private int mShowPosition = TOP;public static final int AUTO_VERTICAL = Gravity.CENTER_VERTICAL;public static final int AUTO_HORIZONTAL = Gravity.CENTER_HORIZONTAL;public static final int LEFT = Gravity.LEFT;public static final int TOP = Gravity.TOP;public static final int RIGHT = Gravity.RIGHT;public static final int BOTTOM = Gravity.BOTTOM;public SimpleTipsPupopWindow(GuZhiActivity context) {this(context, View.inflate(context, R.layout.pupopwindow_view_guzhi_explain, null));}public SimpleTipsPupopWindow(GuZhiActivity context, int layoutId) {this(context, View.inflate(context, layoutId, null));}public SimpleTipsPupopWindow(GuZhiActivity context, View contentView) {super(context);setContentView(contentView);setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));setOutsideTouchable(true);init(contentView);}private void init(View contentView) {mBgDrawable = new ArrowsDrawable(ArrowsDrawable.TOP, ConvertUtils.dp2px(5));mBgDrawable.setCornerRadius(ConvertUtils.dp2px(4));mBgDrawable.setArrowsPadding(ConvertUtils.dp2px(10));mBgDrawable.setArrowsHeight(ConvertUtils.dp2px(5));boolean redMode = true;mBgDrawable.setColor(Color.parseColor(redMode ? "#e6292F3C" : "#f22b3346"));
// mBgDrawable.setPadding(ConvertUtils.dp2px(10));mTipsTv = contentView.findViewById(R.id.explain_tv);initView(contentView);}/*** 用于自定义布局 初始化* @param contentView*/public void initView(View contentView) {}/*** 设置tips TextView* @param tv*/public void setTipsTv(TextView tv) {mTipsTv = tv;}private int makeDropDownMeasureSpec(int measureSpec) {int mode;if (measureSpec == ViewGroup.LayoutParams.WRAP_CONTENT) {mode = View.MeasureSpec.UNSPECIFIED;} else {mode = View.MeasureSpec.EXACTLY;}return View.MeasureSpec.makeMeasureSpec(View.MeasureSpec.getSize(measureSpec), mode);}public void setTips(String tips) {if (mTipsTv != null) {mTipsTv.setText(tips);}}public void show(View anchor, int os, int oy) {if (mBgDrawable == null) {return;}int showPosition = mShowPosition;int offsetX = 0;int offsetY = 0;int locationX = getLocationOnScreen(anchor)[0];int locationY = getLocationOnScreen(anchor)[1];if (showPosition == LEFT || showPosition == RIGHT || showPosition == AUTO_HORIZONTAL) {mBgDrawable.setArrowsPosition(ArrowsDrawable.LEFT, mTipsTv);getContentView().measure(makeDropDownMeasureSpec(getWidth()), makeDropDownMeasureSpec(getHeight()));int windowWidth = this.getContentView().getMeasuredWidth();os += anchor.getWidth();offsetY = (int) -(anchor.getHeight() / 2 + mBgDrawable.getArrowsCenterDistance());if (showPosition == LEFT) {boolean showLeft = locationX >= windowWidth;offsetX = disHor(showLeft, windowWidth, anchor, os);} else if (showPosition == RIGHT) {boolean showLeft = !(getAppScreenWidth() - (locationX + anchor.getWidth()) > windowWidth);offsetX = disHor(showLeft, windowWidth, anchor, os);} else if (showPosition == AUTO_HORIZONTAL) {int screenWidth = getAppScreenWidth();boolean showLeft = locationX + anchor.getWidth() / 2 >= screenWidth / 2;offsetX = disHor(showLeft, windowWidth, anchor, os);}} else {mBgDrawable.setArrowsPosition(ArrowsDrawable.TOP, mTipsTv);//先设置箭头drawable方向为垂直方向的,因箭头尺寸会影响到计算窗口的高度getContentView().measure(makeDropDownMeasureSpec(getWidth()), makeDropDownMeasureSpec(getHeight()));int windowHeight = this.getContentView().getMeasuredHeight();os += anchor.getWidth() / 2;offsetX = (int) (os - mBgDrawable.getArrowsCenterDistance());if (showPosition == TOP) {int distanceTop = locationY - getStatusBarHeight();//锚点控件距离顶部距离//计算锚点控件在屏幕中的位置offsetY = disVer(distanceTop >= windowHeight, windowHeight, anchor, oy);} else if (showPosition == BOTTOM) {int distanceBottom = getLocationOnScreen(anchor)[1] - anchor.getHeight() - getNavBarHeight();//锚点控件距离底部距离offsetY = disVer(distanceBottom < windowHeight, windowHeight, anchor, oy);} else if (showPosition == AUTO_VERTICAL) {int appScreenHeight = getAppScreenHeight();int anchorCenterY = locationY + anchor.getHeight() / 2;offsetY = disVer(appScreenHeight / 2 < anchorCenterY, windowHeight, anchor, oy);}}//设置textView的padding,防止设置drawable背景不生效Rect padding = mBgDrawable.getPadding();mTipsTv.setPadding(padding.left, padding.top, padding.right, padding.bottom);PopupWindowCompat.showAsDropDown(this, anchor, offsetX, offsetY, Gravity.START);}private int disHor(boolean showLeft, int windowWidth, View anchor, int ox) {int offsetX;if (showLeft) {//锚点控件在屏幕中上方,反之在屏幕中下方mBgDrawable.setArrowsPosition(ArrowsDrawable.RIGHT);offsetX = -windowWidth + ox - anchor.getWidth();} else {mBgDrawable.setArrowsPosition(ArrowsDrawable.LEFT);offsetX = ox;}return offsetX;}private int disVer(boolean showTop, int windowHeight, View anchor, int oy) {int offsetY = 0;if (showTop) {//锚点控件在屏幕中上方,反之在屏幕中下方mBgDrawable.setArrowsPosition(ArrowsDrawable.BOTTOM);offsetY = -(windowHeight + anchor.getHeight() + oy);} else {mBgDrawable.setArrowsPosition(ArrowsDrawable.TOP);offsetY = oy;}return offsetY;}public ArrowsDrawable getArrowsDrawable() {return mBgDrawable;}public void setPadding(int padding) {mBgDrawable.setPadding(padding);}@IntDef({LEFT, RIGHT, TOP, BOTTOM, AUTO_VERTICAL, AUTO_HORIZONTAL})@Retention(RetentionPolicy.SOURCE)public @interface ShowPosition {}/*** 设置显示位置(相对于锚点控件 左边、上方、右边、下面)* 注意:窗口相对控件的方向,与箭头方向是相反的。* LEFT, RIGHT, TOP, BOTTOM** @param showPosition*/public void setShowPosition(@ShowPosition int showPosition) {mShowPosition = showPosition;}private static int[] getLocationOnScreen(View view) {int[] location = new int[2];view.getLocationOnScreen(location);return location;}private static int getStatusBarHeight() {// 获得状态栏高度Resources resources = Resources.getSystem();int resourceId = resources.getIdentifier("status_bar_height", "dimen", "android");return resources.getDimensionPixelSize(resourceId);}private static int getNavBarHeight() {Resources res = Resources.getSystem();int resourceId = res.getIdentifier("navigation_bar_height", "dimen", "android");if (resourceId != 0) {return res.getDimensionPixelSize(resourceId);} else {return 0;}}private static int getAppScreenHeight() {WindowManager wm = (WindowManager) Utils.getApp().getSystemService(Context.WINDOW_SERVICE);if (wm == null) return -1;Point point = new Point();wm.getDefaultDisplay().getSize(point);return point.y;}private static int getAppScreenWidth() {WindowManager wm = (WindowManager) Utils.getApp().getSystemService(Context.WINDOW_SERVICE);if (wm == null) return -1;Point point = new Point();wm.getDefaultDisplay().getSize(point);return point.x;}
}
相关文章:

Android 通用带箭头提示窗
简介 自定义PopupWindow, 适用于提示类弹窗。 使用自定义Drawable设置带箭头的背景,测试控件和弹窗的尺寸,自动设置弹窗的显示位置,让箭头指向锚点控件的中间位置,且根据锚点控件在屏幕的位置,自动适配弹窗显示位置。…...

隧道安全监测解决方案
隧道安全监测 解决方案 一、监测目的 通过监控量测,实现信息化施工,不仅能及时掌握隧道实际的地质情况,掌握隧道围岩、支护衬砌结构的受力特征和变形情况,据此可以尽早发现塌方、大变形等灾害征兆,及时采取措施&…...

3 Linux基础篇-VMware和Linux的安装
3 Linux基础篇-VMware和Linux的安装 文章目录 3 Linux基础篇-VMware和Linux的安装3.1 安装VMware和CentOS3.1.1 VM安装3.1.2 Centos7.6的安装步骤 3.3 虚拟机基本操作3.4 安装VMtools3.5 设置共享文件夹 学习视频来自于B站【小白入门 通俗易懂】2021韩顺平 一周学会Linux。可能…...
什么是预处理器指令,常用的预处理器指令有哪些?什么是运算符,C 语言中的运算符有哪些?
1.什么是预处理器指令,常用的预处理器指令有哪些? 预处理器指令是一种用于在源代码编译之前进行预处理的特殊指令。它们通过在程序编译之前对源代码进行处理,可以在编译阶段之前进行一些文本替换、条件编译等操作,从而对源代码进…...

新功能 – Cloud WAN:托管 WAN 服务
我很高兴地宣布,我们推出了 Amazon Cloud WAN,这是一项新的网络服务,它可以轻松构建和运营连接您的数据中心和分支机构以及多个 Amazon 区域中的多个 VPC 的广域网(WAN)。 亚马逊云科技开发者社区为开发者们提供全球的…...

FPGA_学习_13_方差计算小模块
测距器件APD的性能与器件本身的温度、施加在APD的偏置电压息息相关。 在不同的温度下,APD的偏压对测距性能的影响非常大。 要确定一个合适的APD的偏压Vopt,首先你要知道当前温度下,APD的击穿电压Vbr,一般来讲,Vopt Vb…...

如何安装多个版本的python,python可以装两个版本吗
这篇文章主要介绍了可不可以在同一台计算机上安装多个python版本,具有一定借鉴价值,需要的朋友可以参考下。希望大家阅读完这篇文章后大有收获,下面让小编带着大家一起了解一下。 1、不同版本的python不能安装到同一台计算机上 可以的&#…...
深入理解JVM:Java使用new创建对象的流程
1、创建对象的几种方式 ①new 对象 ②反射 ③对象的复制 ④反序列化 2、创建对象流程 先看看常量池里面有没有,如果有,就用常量池的看这个类有没有被加载过,如果没有,就执行类加载以及类的初始化。(对象的大小&#…...

【MySQL】索引与B+树
【MySQL】索引与B树 索引概念前导硬件软件方面 索引的理解单个page多个page引入B树B树的特征为什么B树做索引优于其他数据结构?聚簇索引与非聚簇索引辅助索引 索引的创建主键索引的创建和查看唯一键索引的创建和查看普通索引的创建和查看复合索引全文索引索引的其他…...
“使用Spring Boot快速构建Java Web应用“
标题:使用Spring Boot快速构建Java Web应用 摘要:本文介绍了如何使用Spring Boot快速构建Java Web应用。通过Spring Boot的自动配置和约定优于配置的特性,开发人员可以轻松地搭建一个简单且高效的Web应用。本文将通过一个示例代码详细演示Sp…...
面试题汇总——设计模式
简单介绍 设计模式共有23种,创建型模式5种,结构型模式7种,行为型模式11种 创建型: 关注对象的创建过程,将对象的创建和使用分开,在使用对象时无须知道对象的创建细节。对象实例化的模式,创建型模式用于解耦对象的实例化过程。单例模式、工厂方法模式、抽象工厂模式、建造…...

Java SpringMvc
0目录 java SpringMvc拓展 1.SpringMvc 创建工程,导入依赖 配置 web.xml文件 配置Spring配置文件,resources目录下新建applicationContext.xml 控制层配置 新建list.jsp并测试 Web.xml详解 如果required是true必须要传参 设置默…...

JVM运行时区域——对象创建内存分配过程
新创建的对象,都存放在伊甸园区域,当垃圾回收时,将伊甸园区域的垃圾数据销毁,然后将存活的对象转移到幸存者0区域,之后创建的新的对象还是存放在伊甸园区域,等到再次垃圾回收后,将伊甸园区域和幸…...
Springboot项目排除Bean的方法
前言: 在最近工作中,相关业务代码需要引用一个工具包。原来的同事在工具包中,封装了Spring AOP切面的Bean,但是这样的工具包非常不友好,工具包不应该有这些特殊的处理。有了这些特殊的处理,引用方也要特殊处…...
阿里云国际版云服务器防火墙设置
阿里云国际版云服务器防火墙设置 入侵防御页面为您实时展示云防火墙拦截流量的源IP、目的IP、阻断应用、阻断来源和阻断事件详情等信息。本文介绍了入侵防御页面展示的信息和相关操作,下面和012一起来了解阿里云国际版云服务器防火墙设置: 前提条件 您需…...

科技资讯|苹果开放Vision Pro头显开发套件申请,此前曝光三款电池
苹果今天宣布面向开发人员,正式接受 Vision Pro 头显开发套件申请,从而帮助其开发和测试应用程序。 苹果官方页面介绍,开发人员在获得 Vision Pro 头显开发套件之外,还可以获得设备设置和入门方面的帮助,与 Apple 专…...
Langchain 的 LLMChain
Langchain 的 LLMChain 1. 开始使用运行 LLM 链的其他方式解析输出从字符串初始化 LLMChain 是一个简单的链,它围绕语言模型添加了一些功能。它在整个LangChain中广泛使用,包括在其他链和代理中。 LLMChain 由 PromptTemplate 和语言模型(LL…...

100天精通Golang(基础入门篇)——第17天:深入解析Go语言中的指针
🌷 博主 libin9iOak带您 Go to Golang Language.✨ 🦄 个人主页——libin9iOak的博客🎐 🐳 《面试题大全》 文章图文并茂🦕生动形象🦖简单易学!欢迎大家来踩踩~🌺 🌊 《I…...

第七章:WILDCAT: 弱监督学习的深度卷积神经网络用于图像分类、点位定位和分割
0.摘要 本文介绍了WILDCAT,一种深度学习方法,它旨在通过对齐图像区域来获得空间不变性和学习强烈局部化特征。我们的模型仅使用全局图像标签进行训练,并致力于三个主要的视觉识别任务:图像分类、弱监督的逐点对象定位和语义分割。…...
Axios-post请求下载文件
场景背景 1.一般来说,都是使用get请求后台接口,如此后台返回文件流于浏览器,则可直接下载。 2.那么除一般情况,就有特殊情况,比如你的请求接口参数特别长,此时便不可使用get请求,get请求的参数…...

接口测试中缓存处理策略
在接口测试中,缓存处理策略是一个关键环节,直接影响测试结果的准确性和可靠性。合理的缓存处理策略能够确保测试环境的一致性,避免因缓存数据导致的测试偏差。以下是接口测试中常见的缓存处理策略及其详细说明: 一、缓存处理的核…...

测试微信模版消息推送
进入“开发接口管理”--“公众平台测试账号”,无需申请公众账号、可在测试账号中体验并测试微信公众平台所有高级接口。 获取access_token: 自定义模版消息: 关注测试号:扫二维码关注测试号。 发送模版消息: import requests da…...

7.4.分块查找
一.分块查找的算法思想: 1.实例: 以上述图片的顺序表为例, 该顺序表的数据元素从整体来看是乱序的,但如果把这些数据元素分成一块一块的小区间, 第一个区间[0,1]索引上的数据元素都是小于等于10的, 第二…...
【根据当天日期输出明天的日期(需对闰年做判定)。】2022-5-15
缘由根据当天日期输出明天的日期(需对闰年做判定)。日期类型结构体如下: struct data{ int year; int month; int day;};-编程语言-CSDN问答 struct mdata{ int year; int month; int day; }mdata; int 天数(int year, int month) {switch (month){case 1: case 3:…...
基于Uniapp开发HarmonyOS 5.0旅游应用技术实践
一、技术选型背景 1.跨平台优势 Uniapp采用Vue.js框架,支持"一次开发,多端部署",可同步生成HarmonyOS、iOS、Android等多平台应用。 2.鸿蒙特性融合 HarmonyOS 5.0的分布式能力与原子化服务,为旅游应用带来…...
在 Nginx Stream 层“改写”MQTT ngx_stream_mqtt_filter_module
1、为什么要修改 CONNECT 报文? 多租户隔离:自动为接入设备追加租户前缀,后端按 ClientID 拆分队列。零代码鉴权:将入站用户名替换为 OAuth Access-Token,后端 Broker 统一校验。灰度发布:根据 IP/地理位写…...
vue3 定时器-定义全局方法 vue+ts
1.创建ts文件 路径:src/utils/timer.ts 完整代码: import { onUnmounted } from vuetype TimerCallback (...args: any[]) > voidexport function useGlobalTimer() {const timers: Map<number, NodeJS.Timeout> new Map()// 创建定时器con…...

html css js网页制作成品——HTML+CSS榴莲商城网页设计(4页)附源码
目录 一、👨🎓网站题目 二、✍️网站描述 三、📚网站介绍 四、🌐网站效果 五、🪓 代码实现 🧱HTML 六、🥇 如何让学习不再盲目 七、🎁更多干货 一、👨…...

【从零学习JVM|第三篇】类的生命周期(高频面试题)
前言: 在Java编程中,类的生命周期是指类从被加载到内存中开始,到被卸载出内存为止的整个过程。了解类的生命周期对于理解Java程序的运行机制以及性能优化非常重要。本文会深入探寻类的生命周期,让读者对此有深刻印象。 目录 …...

从物理机到云原生:全面解析计算虚拟化技术的演进与应用
前言:我的虚拟化技术探索之旅 我最早接触"虚拟机"的概念是从Java开始的——JVM(Java Virtual Machine)让"一次编写,到处运行"成为可能。这个软件层面的虚拟化让我着迷,但直到后来接触VMware和Doc…...