高级UI系列(四) 扩展TextView 边角Drawable实战篇

简介: 高级UI系列(四) 扩展TextView 边角Drawable实战篇

一. 业务背景

TextView的setCompoundDrawables()可以在其四周设置图片,但是有个众所周知的问题,即无法设置drawable大小。这就导致在实际的使用中有很大的局限性,必须用代码去控制,就略显麻烦了。

1681560706575.png

这个时候我们就需要自定义 TextView 了,这个自定义控件虽然简单,也非常不起眼,但是用处还真不少:

  1. 解决了主要矛盾,无法在布局里设置 TextView 图片大小问题,使用更加简单。
  2. 除了设置图片大小,其它的 TextView 可以的事情这个一样也都可以
  3. 图片加文字的简单组合非常见,原本为了适配图片大小不得不用一个 xxxLayout+ImageView+TextView 才能搞定的事,现在用一个控件即可搞定。
  4. 在方便、高效使用的同时,也有效的减少了布局层。千万不要瞧不上这点苍蝇肉,这可能是你的app卡顿罪魁祸首

二. 扩展TextView原理

  1. 通过Drawable的setBound()设置显示区域,也就是图片大小
  2. 通过TextView的setCompoundDrawables()设置要显示的图片

三. 扩展TextView实现

3.1 定义一个MkDrawableTextView,继承AppCompatTextView,重写三个构造方法

    public MkDrawableTextView(Context context) {
        super(context);
    }
    public MkDrawableTextView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context, attrs);
    }
    public MkDrawableTextView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context, attrs);
    }

3.2 通过自定义属性定义Drawable宽高,顶点坐标;drawable左上右下的图标引用

    <declare-styleable name="DrawableTextView">
        <attr name="left_drawable" format="reference" />
        <attr name="right_drawable" format="reference" />
        <attr name="top_drawable" format="reference" />
        <attr name="bottom_drawable" format="reference" />
        <attr name="drawable_width" format="dimension" />
        <attr name="drawable_height" format="dimension" />
        <attr name="left_drawable_width" format="dimension" />
        <attr name="left_drawable_height" format="dimension" />
        <attr name="right_drawable_width" format="dimension" />
        <attr name="right_drawable_height" format="dimension" />
        <attr name="top_drawable_width" format="dimension" />
        <attr name="top_drawable_height" format="dimension" />
        <attr name="bottom_drawable_width" format="dimension" />
        <attr name="bottom_drawable_height" format="dimension" />
    </declare-styleable>

3.3 获取Drawable真实宽高

局部的大小设置均正常的情况,我们获取局部设置的宽高,局部大小没设置时,看全局的大小是否正确设置,如果正确获取全局大小宽高

    public static class SizeWrap {
        int width;
        int height;
        /**
         * 检查Drawable的宽高是否符合要求
         * @param globalWidth xml定义的Drawable获取真实的宽度
         * @param globalHeight xml定义的Drawable获取真实的高度
         * @param localWidth Drawable实际图标局部设置的宽度
         * @param localHeight Drawable实际图标局部设置的高度
         * @return 是否符合要求
         */
        public boolean checkWidthAndHeight(int globalWidth, int globalHeight, int localWidth, int localHeight) {
            width = 0;
            height = 0;
            // 局部的大小设置均正常的情况
            if (localWidth > 0 && localHeight > 0) {
                width = localWidth;
                height = localHeight;
                return true;
            }
            // 局部大小没设置时,看全局的大小是否正确设置
            if (localWidth == -1 && localHeight == -1) {
                if (globalWidth > 0 && globalHeight > 0) {
                    width = globalWidth;
                    height = globalHeight;
                    return true;
                }
            }
            return false;
        }
    }
}

3.4 将顶角图标Drawable设置到我们的TextView上

    private void init(Context context, AttributeSet attrs) {
        // 1. 通过style.xml文件拿到所有的样式文件
        TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.DrawableTextView);
        int width = ta.getDimensionPixelOffset(R.styleable.DrawableTextView_drawable_width, -1);
        int height = ta.getDimensionPixelOffset(R.styleable.DrawableTextView_drawable_height, -1);
        SizeWrap sizeWrap = new SizeWrap();
        Drawable leftDrawable = ta.getDrawable(R.styleable.DrawableTextView_left_drawable);
        if (leftDrawable != null) {
            int lwidth = ta.getDimensionPixelOffset(R.styleable.DrawableTextView_left_drawable_width, -1);
            int lheight = ta.getDimensionPixelOffset(R.styleable.DrawableTextView_left_drawable_height, -1);
            if (sizeWrap.checkWidthAndHeight(width, height, lwidth, lheight)) {
                leftDrawable.setBounds(0, 0, sizeWrap.width, sizeWrap.height);
            } else {
                throw new IllegalArgumentException("error left drawable size setting");
            }
        }
        Drawable rightDrawable = ta.getDrawable(R.styleable.DrawableTextView_right_drawable);
        if (rightDrawable != null) {
            int rwidth = ta.getDimensionPixelOffset(R.styleable.DrawableTextView_right_drawable_width, -1);
            int rheight = ta.getDimensionPixelOffset(R.styleable.DrawableTextView_right_drawable_height, -1);
            if (sizeWrap.checkWidthAndHeight(width, height, rwidth, rheight)) {
                rightDrawable.setBounds(0, 0, sizeWrap.width, sizeWrap.height);
            } else {
                throw new IllegalArgumentException("error right drawable size setting");
            }
        }
        Drawable topDrawable = ta.getDrawable(R.styleable.DrawableTextView_top_drawable);
        if (topDrawable != null) {
            int twidth = ta.getDimensionPixelOffset(R.styleable.DrawableTextView_top_drawable_width, -1);
            int theight = ta.getDimensionPixelOffset(R.styleable.DrawableTextView_top_drawable_height, -1);
            if (sizeWrap.checkWidthAndHeight(width, height, twidth, theight)) {
                topDrawable.setBounds(0, 0, sizeWrap.width, sizeWrap.height);
            } else {
                throw new IllegalArgumentException("error top drawable size setting");
            }
        }
        Drawable bottomDrawable = ta.getDrawable(R.styleable.DrawableTextView_bottom_drawable);
        if (bottomDrawable != null) {
            int bwidth = ta.getDimensionPixelOffset(R.styleable.DrawableTextView_bottom_drawable_width, -1);
            int bheight = ta.getDimensionPixelOffset(R.styleable.DrawableTextView_bottom_drawable_height, -1);
            if (sizeWrap.checkWidthAndHeight(width, height, bwidth, bheight)) {
                bottomDrawable.setBounds(0, 0, sizeWrap.width, sizeWrap.height);
            } else {
                throw new IllegalArgumentException("error bottom drawable size setting");
            }
        }
        this.setCompoundDrawables(leftDrawable, topDrawable, rightDrawable, bottomDrawable);
        ta.recycle();
        ta = null;
    }

四. 使用指南

        <com.github.microkibaco.view.MkDrawableTextView
           android:id="@+id/mk_text"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:layout_marginStart="8dp"
           android:layout_toEndOf="@+id/top_text"
           android:background="@drawable/mk_circle_rect_10_white_bg"
           android:drawablePadding="4dp"
           android:gravity="center"
           android:paddingStart="10dp"
           android:paddingTop="3dp"
           android:paddingEnd="10dp"
           android:paddingBottom="3dp"
           android:textColor="@color/white"
           android:textSize="@dimen/text_size_11"
           android:visibility="gone"
           app:right_drawable="@drawable/mk_arrow"
           app:right_drawable_height="10dp"
           app:right_drawable_width="10dp"
           tools:text="@string/mk_rank_no" />

五. 扩展TextView注意事项

在网上看到有一个版本,在控件的onMeasure()设置Drawable.setBound(), 在onDraw()里设置: setCompoundDrawables()。看setCompoundDrawables()源码可以知道,这个方法最终会调用invalide()和requestLayout(),会导致严重的后果就是,onMeasure()和onDraw()会无限循环的互调下去,有点浪费

1681560880490.png

改不完的 Bug,写不完的矫情。公众号 杨正友 现在专注移动基础开发 ,涵盖音视频和 APM,信息安全等各个知识领域;只做全网最 Geek 的公众号,欢迎您的关注!

相关文章
|
8月前
|
Web App开发 前端开发 JavaScript
Playwright极速UI自动化实战指南
Playwright告别Selenium痛点,以智能等待、强大选择器、网络拦截与多设备模拟四大利器,提升自动化效率与稳定性。本文通过实战代码详解其加速秘籍,助你构建高效、可靠的UI测试方案。
|
8月前
|
存储 消息中间件 人工智能
【04】AI辅助编程完整的安卓二次商业实战-寻找修改替换新UI首页图标-菜单图标-消息列表图标-优雅草伊凡
【04】AI辅助编程完整的安卓二次商业实战-寻找修改替换新UI首页图标-菜单图标-消息列表图标-优雅草伊凡
518 4
|
7月前
|
人工智能 自然语言处理 JavaScript
Playwright MCP在UI回归测试中的实战:构建AI自主测试智能体
Playwright MCP结合AI智能体,革新UI回归测试:通过自然语言驱动浏览器操作,降低脚本编写门槛,提升测试效率与覆盖范围。借助快照解析、智能定位与Jira等工具集成,实现从需求描述到自动化执行的闭环,推动测试迈向智能化、民主化新阶段。
|
8月前
|
JavaScript 前端开发 开发者
鸿蒙应用开发从入门到实战(六):ArkTS声明式UI和组件化
鸿蒙开发语言ArkTS在继承了Typescrip语法的基础上,主要扩展了声明式UI开发相关的能力。
376 1
|
存储 JavaScript 开发者
探索鸿蒙新世界:ArkUI框架实战指南,解锁HarmonyOS应用UI设计的无限可能!
【10月更文挑战第19天】ArkUI框架是华为鸿蒙系统中用于开发用户界面的核心工具,支持ArkTS和eTS两种开发语言。本文介绍了ArkUI的基本概念、组件使用、布局管理和状态管理,通过示例代码帮助开发者轻松构建美观、高效的跨设备UI。
1310 3
|
9月前
|
传感器 人工智能 JavaScript
Playwright实战:写UI自动化脚本,速度直接起飞
简介: 测试工程师老王因UI自动化问题深夜奋战,反映出传统测试工具的局限性。微软开源的Playwright凭借智能等待、跨域操作、移动端模拟与网络拦截等强大功能,正迅速取代Selenium,成为新一代自动化测试标准。其稳定高效的设计显著降低维护成本,助力企业构建高质量测试流程。
|
前端开发
Element UI 【实战】纯前端对表格数据进行增删改查(内含弹窗表单、数据校验、时间日期格式)
Element UI 【实战】纯前端对表格数据进行增删改查(内含弹窗表单、数据校验、时间日期格式)
797 6
鸿蒙使用 @Builder扩展出来的布局数据更新没法更新UI
鸿蒙使用 @Builder扩展出来的布局数据更新没法更新UI
566 1
|
存储 人工智能 编译器
【03】鸿蒙实战应用开发-华为鸿蒙纯血操作系统Harmony OS NEXT-测试hello word效果-虚拟华为手机真机环境调试-为DevEco Studio编译器安装中文插件-测试写一个滑动块效果-介绍诸如ohos.ui等依赖库-全过程实战项目分享-从零开发到上线-优雅草卓伊凡
【03】鸿蒙实战应用开发-华为鸿蒙纯血操作系统Harmony OS NEXT-测试hello word效果-虚拟华为手机真机环境调试-为DevEco Studio编译器安装中文插件-测试写一个滑动块效果-介绍诸如ohos.ui等依赖库-全过程实战项目分享-从零开发到上线-优雅草卓伊凡
1027 11
【03】鸿蒙实战应用开发-华为鸿蒙纯血操作系统Harmony OS NEXT-测试hello word效果-虚拟华为手机真机环境调试-为DevEco Studio编译器安装中文插件-测试写一个滑动块效果-介绍诸如ohos.ui等依赖库-全过程实战项目分享-从零开发到上线-优雅草卓伊凡
|
开发框架 缓存 自然语言处理
HarmonyOS ArkTS声明式UI开发实战教程
本文深入探讨了ArkTS作为HarmonyOS生态中新一代声明式UI开发框架的优势与应用。首先对比了声明式与命令式开发的区别,展示了ArkTS如何通过直观高效的代码提升可维护性。接着分析了其核心三要素:数据驱动、组件化和状态管理,并通过具体案例解析布局体系、交互组件开发技巧及复杂状态管理方案。最后,通过构建完整TODO应用实战,结合调试优化指南,帮助开发者掌握声明式UI设计精髓,感受ArkTS的独特魅力。文章鼓励读者通过“破坏性实验”建立声明式编程思维,共同推动HarmonyOS生态发展。
680 3

热门文章

最新文章