Android 通用带箭头提示窗

news2024/11/23 8:12:51

简介

自定义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;
    }
}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

作为前端应该了解的后端常识

1、前端面试题库 &#xff08;面试必备&#xff09; 推荐&#xff1a;★★★★★ 地址&#xff1a;前端面试题库 什么是服务端 服务端&#xff0c;又称后端、server 端前端是用户可见、可操作的部分&#xff0c;如树枝树叶服务端为前端提供 “支撑”和 “营养”&…

Ubuntu22.04 locale出错

问题&#xff1a; locale: Cannot set LC_CTYPE to default locale: No such file or directory locale: Cannot set LC_MESSAGES to default locale: No such file or directory locale: Cannot set LC_ALL to default locale: No such file or directory 解决参考&#xff…

下载Google113版本无更新组件,禁止更新

自动化测试下载谷歌驱动需要与浏览器版本一致&#xff0c;需要设置google浏览器禁止自动更新&#xff0c;这样google就可以不再自动更新了&#xff0c;目的是防止浏览器更新后&#xff0c;那么浏览器驱动也需要同时更新&#xff0c;这样在工作中会十分麻烦。 因此这里提供无更…

day45-SpringMVC

0目录 SpringMVC 1.2.3 1.SpringMVC 1.1 引入依赖&#xff1a; <!--SpringMVC的依赖--> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.1.0&…

如何在 docker hub 分享自己的镜像?看完不踩坑

前言&#xff1a;前几天vip课讲了如何创建配置jenkins容器&#xff0c;怕大家踩坑&#xff0c;我提前打好了jenkins镜像&#xff0c;直接让大家通过命令去拉取镜像就可以了。 然而&#xff0c;很多好学的同学来问这个是怎么操作的。今天就来聊一聊&#xff0c;怎么将自己打好的…

centos 8安装A10显卡驱动-AI人工智能

centos 8安装A10显卡驱动命令:./NVIDIA-Linux-x86_64-535.54.03.run --kernel-source-path/usr/src/kernels/4.18.0-147.el8.x86_64 安装完毕; 测试: 检查驱动版本号: nvidia-smi 验证驱动模块已加载: lsmod | grep nvidia

centos7安装mysql数据库详细教程及常见问题解决

mysql数据库详细安装步骤 1.在root身份下输入执行命令&#xff1a; yum -y update 2.检查是否已经安装MySQL&#xff0c;输入以下命令并执行&#xff1a; mysql -v 如出现-bash: mysql: command not found 则说明没有安装mysql 也可以输入rpm -qa | grep -i mysql 查看是否已…

CMIP6数据处理及在气候变化、水文、生态等领域技术教程

详情点击链接&#xff1a;最新CMIP6数据处理及在气候变化、水文、生态等领域技术教程 一&#xff0c;CMIP6中的模式比较计划 1.1 GCM 全球气候模型&#xff08;Global Climate Model, GCM&#xff09;&#xff0c;也被称为全球环流模型或全球大气模型&#xff0c;是一种用于…

vue项目中为高德地图信息窗体添加点击事件踩坑

这里遇到了一个坑&#xff0c;在给信息窗体中的内容添加点击事件时&#xff0c;信息窗体弹出的时候点击事件自动执行了。在此记录一下踩坑和解决过程。 部分代码如下&#xff1a; this.map.on(click, e > {const item {val: 1234,name: zhang}const content <li οnc…

快解析内网穿透帮我实现零成本自建网站

我是一名 90后&#xff0c;大概就是大家嘴里“别人家的孩子”&#xff0c;大学学的是IT专业&#xff0c;毕业后结婚、生子一切按部就班 。随波逐流工作了几年&#xff0c;父母年龄变大&#xff0c;培养孩子投入也增加&#xff0c;逐渐开销变大&#xff0c;可是我的薪资还处于中…

【设计模式】详解观察者模式

文章目录 1、简介2、观察者模式简单实现抽象主题&#xff08;Subject&#xff09;具体主题&#xff08;ConcreteSubject&#xff09;抽象观察者&#xff08;Observer&#xff09;具体观察者&#xff08;ConcrereObserver&#xff09;测试&#xff1a; 观察者设计模式优缺点观察…

使用serverless实现从oss下载文件并压缩

公司之前开发一个网盘系统, 可以上传文件, 打包压缩下载文件, 但是在处理大文件的时候, 服务器遇到了性能问题, 主要是这个项目是单机部署.......(离谱), 然后带宽只有100M, 现在用户比之前多很多, 然后所有人的压缩下载请求都给到这一台服务器了, 比如多个人下载的时候带宽问…

springboot解决跨域

跨域问题指的是不同站点之间&#xff0c;使用 ajax 无法相互调用的问题。跨域问题本质是浏览器的一种保护机制&#xff0c;它的初衷是为了保证用户的安全&#xff0c;防止恶意网站窃取数据。但这个保护机制也带来了新的问题&#xff0c;它的问题是给不同站点之间的正常调用&…

“单片机定时器:灵活计时与创新功能的关键“

学会定时器的使用对单片机来说非常重要&#xff0c;因为它可以帮助实现各种时序电路。时序电路在工业和家用电器的控制中有广泛的应用。 举个例子&#xff0c;我们可以利用单片机实现一个具有按钮控制的楼道灯开关。当按钮按下一次后&#xff0c;灯会亮起并持续3分钟&#xff…

Android 中 app freezer 原理详解(一):R 版本

基于版本&#xff1a;Android R 0. 前言 在之前的两篇博文《Android 中app内存回收优化(一)》和 《Android 中app内存回收优化(二)》中详细剖析了 Android 中 app 内存优化的流程。这个机制的管理通过 CachedAppOptimizer 类管理&#xff0c;为什么叫这个名字&#xff0c;而不…

第五章:linux进程控制

系列文章目录 文章目录 系列文章目录前言进程创建fork函数初识fork写时拷贝fork常规用法fork调用失败的原因 进程终止进程退出场景进程的退出码系统自带的退出码strerrorC语言提供的退出码 进程退出深度理解进程常见退出方法正常退出缓冲区 进程等待进程等待必要性进程等待的方…

SAP中获取成品物料的全部配置(SAP配置BOM攻略四)

基于系统内的全配置BOM设定&#xff0c;全部的子配置是由四大配置产生&#xff08;即车身颜色、内饰颜色、车型、选装&#xff09;。如果按某一车型&#xff0c;要带出该车的全部BOM子物料&#xff0c;首先需要具备通过四大配置&#xff0c;得到全部子配置的能力&#xff0c;然…

【iVX】在百花齐放的低代码平台中独领风骚

&#x1f482;作者简介&#xff1a; THUNDER王&#xff0c;一名热爱财税和SAP ABAP编程以及热爱分享的博主。目前于江西师范大学本科在读&#xff0c;同时任汉硕云&#xff08;广东&#xff09;科技有限公司ABAP开发顾问。在学习工作中&#xff0c;我通常使用偏后端的开发语言A…

IntelliJ IDEA 2023.2 新版本,拥抱 AI

IntelliJ IDEA 近期连续发布多个EAP版本&#xff0c;官方在对用户体验不断优化的同时&#xff0c;也新增了一些不错的功能&#xff0c;尤其是人工智能助手补充&#xff0c;AI Assistant&#xff0c;相信在后续IDEA使用中&#xff0c;会对开发者工作效率带来不错的提升。 以下是…

mybatisPlus入门篇

文章目录 初窥门径1.1 初识MybatisPlus1.2 MybatisPlus的特性1.3 MybatisPlus的架构模型 入门案例2.1 准备相关开发环境2.2 搭建springboot工程2.3 创建数据库2.4 引入相关依赖2.5 创建实体类2.6 集成MybatisPlus2.7 单元测试2.8 springboot日志优化 初窥门径 1.1 初识Mybatis…