自定义View - 简单的TextView封装

简介: 引言在平常的开发中,我们总会有各种各样的按钮,圆角的、直角的、正常状态的、按下状态的、禁用状态的。一直的做法就是在drawable中写一个selector,然后用item加shap来实现。

引言

在平常的开发中,我们总会有各种各样的按钮,圆角的、直角的、正常状态的、按下状态的、禁用状态的。一直的做法就是在drawable中写一个selector,然后用item加shap来实现。这种做法实现起来也是非常简单,但是存在一个问题:当我们shap文件有上千个的时候,我们应该如何维护?

分析

先上一张图吧:

img_015ca7320482b148c04fade4e82d4633.png
不同状态的按钮

仔细分析下来,图中的几个按钮都是差不多的,他们之间有着许多的相通点,像这种情况,我们真的需要为每一个TextView写一个单独的selector吗?按照我个人的理解,其实可以将这些不同的属性抽取出来做成自定义View。

确定属性

既然分析出了异同,那么我们就可以先确定好需要哪些属性,所谓磨刀不误砍柴工,编码之前有个完整的思路,能少走许多弯路。


属性名 说明
TextRadius 圆角
背景颜色相关
NormalBackgroundColor 正常情况下的背景颜色
PressBackgroundColor 按下情况下的背景颜色
DisableBackgroundColor 禁用状态下的背景颜色
SelectedBackgroundColor 选中状态下背景颜色
文字颜色相关
NormalTextColor 正常情况下的文字颜色
PressTextdColor 按下情况下的文字颜色
DisableTextColor 禁用状态下的文字颜色
SelectedTextColor 选中状态下文字颜色

开始编码

创建PressTextView

开始编码了,创建PressTextView继承至TextView,我的做法是将TextView当做Button来使用,好像是TextView有更好的拓展性。

img_ee9c8a598410a3f5fdcf8c3b68b56e63.png
创建好PressTextView

创建attr

class创建完成,在value目录下创建press_text_view_attr文件,将前面确定好的属性转换为attr,内容如下:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="PressTextView">
        <!--圆角-->
        <attr name="TextRadius" format="dimension" />
        <!--正常情况下的背景颜色-->
        <attr name="NormalBackgroundColor" format="color" />
        <!--按下情况下的背景颜色-->
        <attr name="PressBackgroundColor" format="color" />
        <!--禁用状态下的背景颜色-->
        <attr name="DisableBackgroundColor" format="color" />
        <!--选中状态下背景颜色-->
        <attr name="SelectedBackgroundColor" format="color" />
        <!--正常情况下的文字颜色-->
        <attr name="NormalTextColor" format="color" />
        <!--按下情况下的文字颜色-->
        <attr name="PressTextColor" format="color" />
        <!--禁用状态下的文字颜色-->
        <attr name="DisableTextColor" format="color" />
        <!--选中状态下文字颜色-->
        <attr name="SelectedTextColor" format="color" />
    </declare-styleable>
</resources>

初始化相关属性

这里定义的属性稍微有点多,而且将来相关属性可能会越来越多,所以选择创建一个config类来存储相关属性,减少PressTextView的代码量。

PressTextViewConfig内容:


/**不使用public修饰是不想其它模块进行访问*/
/*public*/ class PressTextViewConfig {
    /**
     * 圆角
     */
    public float TextRadius;
    /**
     * 正常情况下的背景颜色
     */
    public int NormalBackgroundColor;
    /**
     * 按下情况下的背景颜色
     */
    public int PressBackgroundColor;
    /**
     * 禁用状态下的背景颜色
     */
    public int DisableBackgroundColor;
    /**
     * 选中状态下背景颜色
     */
    public int SelectedBackgroundColor;
    /**
     * 正常情况下的文字颜色
     */
    public int NormalTextColor;
    /**
     * 按下情况下的文字颜色
     */
    public int PressTextColor;
    /**
     * 禁用状态下的文字颜色
     */
    public int DisableTextColor;
    /**
     * 选中状态下文字颜色
     */
    public int SelectedTextColor;
}

然后再PressTextView中定义一个PressTextViewConfig的属性,这样就可以方便的赋值和使用:

/**
 * 初始化相关属性
 *
 * @param context
 * @param attrs
 */
private void init(Context context, AttributeSet attrs) {
    mPressTextViewConfig = new PressTextViewConfig();

    // 打开样式资源
    TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.PressTextView);

    //圆角
    mPressTextViewConfig.TextRadius = typedArray.getDimension(R.styleable.PressTextView_TextRadius, 5);

    // 正常背景颜色
    mPressTextViewConfig.NormalBackgroundColor = typedArray
            .getColor(R.styleable.PressTextView_NormalBackgroundColor,
                    0xff3F51B5);
    // 按下背景颜色
    mPressTextViewConfig.PressBackgroundColor = typedArray
            .getColor(R.styleable.PressTextView_PressBackgroundColor,
                    0xff303F9F);
    // 禁言背景颜色
    mPressTextViewConfig.DisableBackgroundColor = typedArray
            .getColor(R.styleable.PressTextView_DisableBackgroundColor,
                    0xffeeeeee);

    // 选中背景颜色
    mPressTextViewConfig.SelectedBackgroundColor = typedArray
            .getColor(R.styleable.PressTextView_SelectedBackgroundColor,
                    0xffFF4081);

    // 正常字体颜色
    mPressTextViewConfig.NormalTextColor = typedArray.getColor(R.styleable.PressTextView_NormalTextColor,
            Color.WHITE);
    // 按下字体颜色
    mPressTextViewConfig.PressTextColor = typedArray.getColor(R.styleable.PressTextView_PressTextColor,
            0xffeeeeee);
    // 禁用字体颜色
    mPressTextViewConfig.DisableTextColor = typedArray.getColor(R.styleable.PressTextView_DisableTextColor,
            0xff999999);
    // 选中字体颜色
    mPressTextViewConfig.SelectedTextColor = typedArray.getColor(R.styleable.PressTextView_SelectedTextColor,
            0xffcdcdcd);

    //释放资源
    typedArray.recycle();

    mPaint = new Paint();
    mPaint.setAntiAlias(true);

    setClickable(true);
    setFocusable(true);
}

绘制

我们继承的是TextView,所以其他逻辑可以直接忽略,包括宽高啊、测量之类的,只需要重写onDraw方法,在onDraw中进行绘制。而绘制有以下要点:

  • 因为背景使用圆角,所以使用drawRoundRect来进行绘制,而绘制的时机是在super.onDraw(canvas)之前进行绘制。
  • 需要判断不同的状态来使用不同的背景颜色和字体颜色
  • 设置字体颜色需要在super.onDraw(canvas)之后
  • 需要重写onTouchEvent方法,方法内不做其它事情,就为了调用postInvalidate方法触发绘制。

核心代码也比较简单,这里就直接全部贴上了:

@Override
protected void onDraw(Canvas canvas) {
    if (mBackgroundRectf == null) {
        mBackgroundRectf = new RectF(0, 0, getWidth(), getHeight());
    }
    // 画笔颜色
    mPaint.setColor(switchBackground());
    canvas.drawRoundRect(mBackgroundRectf, mPressTextViewConfig.TextRadius,
            mPressTextViewConfig.TextRadius,
            mPaint);

    super.onDraw(canvas);

    //设置字体颜色
    setTextColor(switchTextColor());
}

/**
 * 字体颜色
 */
private int switchTextColor() {
    if (!isEnabled()) {
        return mPressTextViewConfig.DisableTextColor;
    }
    // 按下状态
    if (isPressed()) {
        return mPressTextViewConfig.PressTextColor;
    }

    // 选中状态
    if (isSelected()) {
        return mPressTextViewConfig.SelectedTextColor;
    }
    return mPressTextViewConfig.NormalTextColor;
}

/**
 * 判断背景颜色
 *
 * @return
 */
private int switchBackground() {
    // 禁用状态
    if (!isEnabled()) {
        return mPressTextViewConfig.DisableBackgroundColor;
    }
    // 按下状态
    if (isPressed()) {
        return mPressTextViewConfig.PressBackgroundColor;
    }

    // 选中状态
    if (isSelected()) {
        return mPressTextViewConfig.SelectedBackgroundColor;
    }

    return mPressTextViewConfig.NormalBackgroundColor;
}

@Override
public boolean onTouchEvent(MotionEvent event) {
    // 触摸的时候重绘
    postInvalidate();
    return super.onTouchEvent(event);
}

使用

编写完成,现在在xml中进行引用。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <com.jc.lib.view.PressTextView
        android:layout_width="wrap_content"
        android:layout_height="48dp"
        android:layout_marginTop="10dp"
        android:gravity="center"
        android:paddingLeft="5dp"
        android:paddingRight="5dp"
        android:text="正常可点击(有按下背景和字体颜色)"
        android:textSize="18sp"
        app:NormalBackgroundColor="#03a4f9"
        app:NormalTextColor="#fff"
        app:PressBackgroundColor="#3F51B5"
        app:PressTextColor="#eee"
        app:TextRadius="8dp" />

    <com.jc.lib.view.PressTextView
        android:layout_width="wrap_content"
        android:layout_height="48dp"
        android:layout_marginTop="10dp"
        android:gravity="center"
        android:paddingLeft="5dp"
        android:paddingRight="5dp"
        android:text="禁用状态"
        android:textSize="18sp"
        android:enabled="false"
        app:DisableBackgroundColor="#eeeeee"
        app:DisableTextColor="#999999"
        app:TextRadius="20dp" />

    <com.jc.lib.view.PressTextView
        android:id="@+id/mSelectTv"
        android:layout_width="wrap_content"
        android:layout_height="48dp"
        android:layout_marginTop="10dp"
        android:gravity="center"
        android:paddingLeft="5dp"
        android:paddingRight="5dp"
        android:text="选中状态"
        android:textSize="18sp"
        app:SelectedBackgroundColor="@color/colorPrimaryDark"
        app:SelectedTextColor="#fff"
        app:TextRadius="4dp" />
</LinearLayout>

效果

现在就是验证编码成果的时候了。

img_1aa174efc713f184a00e8fc8c10dcf75.gif
结果

总结

其实这只是一个简单的TextView封装,并不难,有时候只是一个思路问题,由此延伸,我们的其他布局也是不是可以这样做呢?或者再加上边框、实线边框、虚线边框、上边框。这些就不一一去实现了,在这里主要是为了提供这样一个思路,我们更需要做的是跳出当前思想的局限性,用散发性思维去思考事物。

最后

未完待续、敬请期待!
免为其难的关注一下公众号吧!!
img_b246dc41a34e6fdd3598d113e0f65194.jpe
FullScreenDeveloper
目录
相关文章
|
13天前
|
人工智能 自然语言处理 安全
阿里云千问办公 QwenWork详细介绍:产品核心能力、典型场景、价格及常见问题解答
千问办公是阿里云推出的一站式AI办公平台,主打"不止于对话,更注重交付",依托通义千问旗舰大模型,用户一句话即可完成数据分析、PPT生成、视频剪辑等复杂任务,直接输出可用成果。产品深度打通钉钉生态与企业OA,覆盖桌面端、网页端,提供企业标准版198元/人/月等多档订阅方案,新用户注册即赠2000积分,适配工程师、HR、财务等多职业办公场景,成为能动手干活的"全能AI同事"。
|
13天前
|
人工智能
千问办公官网入口:阿里AI办公QwenWork产品页和免费网页端链接
千问办公官网含两大入口:一是网页端(qwenwork.cn),即开即用,支持浏览器直接访问;二是阿里云产品页 https://t.aliyun.com/U/JNKJuO 提供免费/付费版详情、功能介绍及使用指南。
|
12天前
|
IDE 开发工具
Qoder 上线 Sonus 模型,Computer Use 能力全面增强
Qoder国际版上线全新内置大模型Sonus(/ˈsoʊnəs/),全球领先,专精超长任务执行与电脑操作(Computer Use)。配合Qoder桌面端0.2.3版本,可自主完成编程、金融建模、科研及表格制作等复杂工作。现全面支持Qoder全系产品,效率提升3.2倍。
1542 8
Qoder 上线 Sonus 模型,Computer Use 能力全面增强
|
14天前
|
缓存 人工智能 自然语言处理
阿里云qwen3.8-flash大模型介绍:模型能力、模型价格、免费额度与最新活动
本文是阿里云百炼平台Qwen3.8-Flash大模型的选型接入指南,作为兼顾性能与响应速度的高性价比多模态模型,它支持百万级上下文窗口、全场景多模态输入与完整智能体能力矩阵,适配编程辅助、智能体协作等核心场景。文中同步梳理了最新下调的阶梯定价、夜间4折等优惠活动,搭配OpenAI兼容流式调用示例,帮助开发者低成本快速落地高并发AI应用。
阿里云qwen3.8-flash大模型介绍:模型能力、模型价格、免费额度与最新活动
|
14天前
|
人工智能 API 内存技术
刚刚 DeepSeek V4.1 Flash 开启内测,1 分钟教你用上!
刚刚 DeepSeek 内测群发布了 DeepSeek V4.1 Flash 中间版本内测的消息,这次的模型采用了新的结构,原生支持多模态、能力更强、速度更快、且成本更低。
1993 15
|
7天前
|
缓存 IDE Java
【保姆级】Android Studio下载、安装和汉化教程(2026最新)
Android Studio 是 Google 官方推出的免费 Android 应用开发集成环境,基于 IntelliJ IDEA,内置模拟器、调试器、性能分析及 Compose 界面工具,功能全面,文档丰富,是安卓开发首选工具。(239字)
|
18天前
|
人工智能 运维 BI
阿里云千问办公QwenWork深度解析:基于Qwen3.8,六大核心能力重构企业全自动化工作流与计费选型指南
传统AI办公工具大多停留在对话问答、文档摘要、简单文案生成层面,只能完成单点碎片化任务,无法自主拆解复杂业务流程,很难串联多工具、多文档、外部业务系统完成端到端完整工作交付。很多企业在落地AI办公的时候,需要组合多款不同工具,来回切换界面,手动复制粘贴中间结果,智能化改造落地门槛居高不下。千问办公QwenWork是整合多款智能体产品能力打造的一体化企业办公智能体平台,底层基座依托Qwen3.8大模型,打通桌面端Agent、云端Agent、企业协同Agent三种运行形态,不再局限简单问答,接收业务目标之后自主拆解任务步骤,调用各类工具,处理文档、表格、浏览器自动化、数据查询,直接输出可交付的办公
1693 4
|
12天前
|
人工智能 安全 JavaScript
DeepSeek Harness开源Agent运行框架实战:4种安装方式、WebUI启动、插件管理与排坑全流程
随着AI Agent技术快速发展,单纯依靠大模型对话能力,很难完成复杂的自动化任务。模型需要具备读取本地文件、执行脚本、访问网页、操作文件系统、拆分复杂任务并分步执行的能力。DeepSeek Harness,简称DSH,是开源的AI Agent执行运行框架,遵循“Agent = 大模型 + Harness执行底座”的设计理念,为大模型提供一套安全可控的工具调用、任务编排、沙箱执行与插件扩展能力。它提供Web可视化界面与完整命令行工具,支持插件化扩展,能够让大模型自主拆解复杂需求,调用各类工具分步完成目标,无论是本地电脑调试,还是部署在云服务器上长期运行智能体任务都十分合适。本文为从0到1完整保
918 0
|
14天前
|
缓存 JSON API
阿里云千问Qwen3.8‑Max深度解析:核心能力、订阅计费规则、API接入配置与生产落地完整教程
Qwen3.8‑Max作为千问系列新一代MoE架构旗舰基座,总参数量达到2.4万亿,激活参数950亿,是面向复杂专业任务、长周期智能体、工程级代码开发、多模态深度解析的高阶大模型,原生支持文本、图像、视频多模态输入,最大上下文窗口达到百万Token,最大输出Token支持131072,内置深度思考推理链路,在编程、科研、法律金融专业分析、长视频文档解析、自主Agent任务等场景能力表现突出。很多开发者在项目前期直接接入该旗舰模型,却对模型能力边界、多种计费模式、订阅套餐权益、API参数配置、上下文缓存优化缺乏完整认知,出现成本失控、接口报错、长文本信息丢失、深度思考模式额外消耗大量Token等
989 3