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) {
            @Override
            public 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;
    }
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

vue项目开发环境和生产环境代理的配置问题

1.跨域 跨域解决方案&#xff1a; 1.JSONP 通过动态 script标签跨域 2.document.domain iframe跨域 3.location.hash iframe 4.window.name iframe跨域 5.postMessage 跨 window 通信 6.跨域资源共享&#xff08;CORS&#xff09; 7.nginx代理跨域 8.nodejs中间件代理跨域 9…

283. 移动零

题目 题解一&#xff1a; 反向收集不是0 的数字放到一个数组里面&#xff0c;用原数组大小减去收集数组的大小就是0 的个数 /*** 反向收集不是0 的数字放到一个数组里面&#xff0c;用原数组大小减去收集数组的大小就是0 的个数* param nums*/public static void moveZeroes(i…

8. Spring Boot 日志文件

目录 1. 日志的作用 2. 如何使用日志 3. 自定义日志打印 3.1 获取日志对象 3.2 设置打印的内容 3.3 常见的日志框架 3.4 日志格式说明 4. 日志级别 4.1 日志级别的作用 4.2 日志级别的分类 4.3 日志级别的使用 4.4 设置日志级别 4.5 分目录设置日志级别 5. 日志…

《Docker与持续集成/持续部署:构建高效交付流程,打造敏捷软件交付链》

&#x1f337;&#x1f341; 博主 libin9iOak带您 Go to New World.✨&#x1f341; &#x1f984; 个人主页——libin9iOak的博客&#x1f390; &#x1f433; 《面试题大全》 文章图文并茂&#x1f995;生动形象&#x1f996;简单易学&#xff01;欢迎大家来踩踩~&#x1f33…

Qt5.14.2+VS2019配置MSVC2017

问题&#xff1a; The compiler " Microsoft Visual C Compiler 16 . 11 . 32106 . 194 ( amd64 x86 )( x86-windows-msvc2019-pe-32bit ) cannot produce code for the Qt version " Qt5.14.2 MSVC2017 64bit " ( x86-windows-msvc2017-pe-64bit 编译器“…

区间预测 | MATLAB实现QRBiGRU双向门控循环单元分位数回归多输入单输出区间预测

区间预测 | MATLAB实现QRBiGRU双向门控循环单元分位数回归多输入单输出区间预测 目录 区间预测 | MATLAB实现QRBiGRU双向门控循环单元分位数回归多输入单输出区间预测效果一览基本介绍模型描述程序设计参考资料 效果一览 基本介绍 MATLAB实现QRBiGRU双向门控循环单元分位数回归…

选读SQL经典实例笔记14_层次查询

1. 结果集 1.1. sql select empno,mgrfrom emp order by 2EMPNO MGR ---------- ----------7788 75667902 75667499 76987521 76987900 76987844 76987654 76987934 77827876 77887566 78397782 7…

Hadoop生态体系-2

目录标题 1、MapReduce介绍2、数据仓库3、HIVE4、HQL4.1 hive读写文件机制4.2 Hive数据存储路径 1、MapReduce介绍 思想&#xff1a;分而治之 map:“分”&#xff0c;即把复杂的任务分解为若干个“简单的任务”来处理。可以进行拆分的前提是这些小任务可以并行计算&#xff0c…

第四章 HL7 架构和可用工具 - 查看数据结构

文章目录 第四章 HL7 架构和可用工具 - 查看数据结构查看数据结构查看代码表使用自定义架构编辑器 第四章 HL7 架构和可用工具 - 查看数据结构 查看数据结构 当单击“数据结构”列中的名称时&#xff0c;InterSystems 会显示该数据结构中的所有字段。这是 HL7 数据结构页面。…

网络安全法律法规

数据参考&#xff1a;CISP官方 目录 国家立法体系网络安全法解析网络安全相关法律 一、国家立法体系 1、我国的立法体系 我国的立法体系在网络空间治理中扮演着基础工作的角色。为了应对快速发展的网络技术和威胁&#xff0c;我国采取了多级立法机制来完善网络空间的法律…

在linux中怎样同时运行三个微服务保证退出时不会终止

前言 1.maven中打jar包 使用插件打包,必须在pom.xml中添加插件,否则不能在linux中编译运行 <build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><version&g…

207. 课程表 Python

文章目录 一、题目描述示例 1示例 2 二、代码三、解题思路 一、题目描述 你这个学期必须选修 numCourses 门课程&#xff0c;记为 0 到 numCourses - 1 。 在选修某些课程之前需要一些先修课程。 先修课程按数组 prerequisites 给出&#xff0c;其中 prerequisites[i] [ai, …

网络安全大厂面试题

自我介绍 有没有挖过src&#xff1f; 平时web渗透怎么学的&#xff0c;有实战吗&#xff1f;有过成功发现漏洞的经历吗&#xff1f; 做web渗透时接触过哪些工具 xxe漏洞是什么&#xff1f;ssrf是什么&#xff1f; 打ctf的时候负责什么方向的题 为什么要搞信息安全&#xff0c;对…

华为数通HCIP-BGP EVPN基础

MP-BGP MP-BGP&#xff08;Multiprotocol Extensions for BGP-4&#xff09;在RFC4760中被定义&#xff0c;用于实现BGP-4的扩展以允许BGP携带多种网络层协议&#xff08;例如IPv6、L3VPN、EVPN等&#xff09;。这种扩展有很好的后向兼容性&#xff0c;即一个支持MP-BGP的路由…

饱和(非饱和)激活函数

1.什么是饱和&#xff08;非饱和&#xff09;激活函数 若h(x)满足&#xff1a;&#xff0c;则h(x)称为饱和激活函数&#xff0c;例如sigmoid和tanh&#xff0c;否则为非饱和激活函数&#xff0c;例如Relu及其变体。 2.非饱和激活函数的优势有两点 能解决所谓的“梯度消失”问…

【小尘送书-第三期】Python机器学习:基于PyTorch和Scikit-Learn 》

大家好&#xff0c;我是小尘&#xff0c;欢迎关注&#xff0c;一起交流学习&#xff01;欢迎大家在CSDN后台私信我&#xff01;一起讨论学习&#xff0c;讨论如何找到满意的实习&#xff01; 本文目录 一、前言二、作者简介三、内容简介四、抽奖方式 一、前言 近年来&#xff0…

VLAN---虚拟局域网

VLAN— 虚拟局域网 LAN—局域网 MAN—城域网 WAN—广域网 1.一个VLAN相当于是一个广播域 VLAN—通过路由器和交换机协同工作后&#xff0c;将原本的一个广播域逻辑上&#xff0c;拆 分为多个虚拟的广播域。 VLAN配置&#xff1a; 1.创建VLAN VID—VLAN ID------用来区分和…

git相关

gerrit用户指南&#xff1a; 资料&#xff1a;Gerrit 用户指南 gerrit-user-guide 上述有介绍如何review&#xff0c;review并非修改代码之后如何重新提交等操作 jenkins介绍 Jenkins详细教程 - 知乎 一、jenkins是什么&#xff1f; Jenkins是一个开源的、提供友好操作界…

【手机】三星手机刷机解决SecSetupWizard已停止

三星手机恢复出厂设置之后&#xff0c;出现SecSetupWizard已停止的解决方案 零、问题 我手上有一部同学给的三星 GT-S6812I&#xff0c;这几天搞了张新卡&#xff0c;多余出的卡就放到这个手机上玩去了。因为是获取了root权限的&#xff08;直接使用KingRoot就可以&#xff0…

基于JAVA SpringBoot和Vue高考志愿填报辅助系统

随着信息技术在管理中的应用日益深入和广泛&#xff0c;管理信息系统的实施技术也越来越成熟&#xff0c;管理信息系统是一门不断发展的新学科&#xff0c;任何一个机构要想生存和发展&#xff0c;要想有机、高效地组织内部活动&#xff0c;就必须根据自身的特点进行管理信息时…