Android自定义简单TextView

news2025/1/12 3:46:09

自定义属性

<declare-styleable name="TextView">
        <!--
        name 属性名称
        format 格式:string 文字 color颜色
        dimension 宽高 字体大小 integer数字
        reference 资源引用(drawable)
        -->
        <attr name="YiRanText" format="string"/>
        <attr name="YiRanTextColor" format="color"/>
        <attr name="YiRanTextSize" format="dimension"/>
        <attr name="YiRanMaxLength" format="integer"/>
        <!--background是自定义View管理的,可以不用 -->
        <attr name="YiRanBackground" format="reference|color"/>
        <!--枚举-->
        <attr name="YiRanInputType">
            <enum name="number" value="1"/>
            <enum name="text" value="2"/>
            <enum name="password" value="3"/>
        </attr>
    </declare-styleable>

整体代码

public class TextView extends View {

    private String mText;
    private int mTextSize=15;
    private int mTextColor= Color.BLACK;
    private Paint mPaint;


    //这个构造函数会在代码里面new的时候调用
    //TextView tv=new TextView(this)
    public TextView(Context context) {
        this(context,null);
    }

    //在布局中使用
    /*
    <com.example.customview.customview.text.TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="11111"
     />
    * */
    public TextView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs,0);
    }


    //在布局layout中使用(调用),但是会有style
    public TextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        //获取自定义属性
        TypedArray array=context.obtainStyledAttributes(attrs, R.styleable.TextView);
        mText=array.getString(R.styleable.TextView_YiRanText);
        mTextColor=array.getColor(R.styleable.TextView_YiRanTextColor,mTextColor);
        mTextSize=array.getDimensionPixelSize(R.styleable.TextView_YiRanTextSize,spToPx(mTextSize));

        //回收
        array.recycle();
        mPaint=new Paint();
        //抗锯齿
        mPaint.setAntiAlias(true);
        //设置画笔文本大小
        mPaint.setTextSize(mTextSize);
    }

    /*
    *   <com.example.customview.customview.text.TextView
        style="@style/default_text"
        android:text="11111"
         />
    * */
    public TextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        //布局的宽高都是由这个方法指定
        //指定控件的宽高,需要测量
        //获取宽高的模式
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);

        //1.确定的值,这个时候不需要计算,给的多少就是多少
        int width=MeasureSpec.getSize(widthMeasureSpec);

        //2.给的是wrap_content 需要计算
        if(widthMode==MeasureSpec.AT_MOST){
            //计算的宽度和字体大小和长度有关,用画笔来测量
            Rect bounds=new Rect();
            //获取文本的Rect,让Rect的宽度为文本的宽度,文本也就是设置的mText
            mPaint.getTextBounds(mText,0,mText.length(),bounds);
            //如果xml设置了padding记得要加没有就是0
            width=bounds.width()+getPaddingLeft()+getPaddingRight();
        }

        int height=MeasureSpec.getSize(heightMeasureSpec);
        //2.给的是wrap_content 需要计算
        if(heightMode==MeasureSpec.AT_MOST){
            //计算的宽度和字体大小和长度有关,用画笔来测量
            Rect bounds=new Rect();
            //获取文本的Rect,让Rect的高度为文本的高度
            mPaint.getTextBounds(mText,0,mText.length(),bounds);
            //如果xml设置了padding记得要加没有就是0
            height=bounds.height()+getPaddingTop()+getPaddingBottom();
        }
        setMeasuredDimension(width,height);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //画文本
        //x起始位置,y基线

        //dy代表的是:高度的一半到baseLine的距离
        Paint.FontMetricsInt fontMetrics=mPaint.getFontMetricsInt();
        //top是一个负值 bottom是一个正值
        Log.d("TAG", "--fontMetrics.bottom"+fontMetrics.bottom+"fontMetrics.top"+fontMetrics.top+"dy"+(fontMetrics.bottom-fontMetrics.top)/2);
        //Log.d("TAG", "fontMetrics.ascent "+fontMetrics.ascent+"fontMetrics.descent"+fontMetrics.descent);
        int dy=(fontMetrics.bottom-fontMetrics.top)/2-fontMetrics.bottom;
        //Log.d("TAG", "baseLine"+baseLine);
        Log.d("TAG", "dy"+dy);
        int baseLine=getHeight()/2+dy;
        //Log.d("TAG", "baseLine"+baseLine);

        //如果x设置0会从0开始画,但是如果设置了padding,应该从paddingLeft开始
        int x=getPaddingLeft();
        canvas.drawText(mText,x,baseLine,mPaint);

        //画弧
     //   canvas.drawArc();

        //画圆
      //  canvas.drawCircle();
    }

    /**
     * 处理跟用户交互的,手指触摸等等
     */
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                //手指按下
                Log.e("TAG", "手指按下");
                break;
            case MotionEvent.ACTION_MOVE:
                //手指移动
                Log.e("TAG", "手指移动");
                break;
            case MotionEvent.ACTION_UP:
                //手指抬起
                Log.e("TAG", "手指抬起");
                break;
        }
        return super.onTouchEvent(event);
    }

    public  int spToPx( int sp) {
        return (int) TypedValue.applyDimension(
                TypedValue.COMPLEX_UNIT_SP, sp, getResources().getDisplayMetrics());
    }
}

使用

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="16dp"
    android:paddingLeft="16dp"
    android:paddingRight="16dp"
    android:paddingTop="16dp"
    xmlns:yiran="http://schemas.android.com/apk/res-auto">

    <com.example.customview.customview.text.TextView
        android:layout_width="wrap_content"
        yiran:YiRanText="HelloWord"
        yiran:YiRanTextColor="@color/purple_500"
        yiran:YiRanInputType="number"
        yiran:YiRanTextSize="20sp"
        android:padding="10dp"
        android:layout_height="wrap_content"
        />

</LinearLayout>

效果图:

在这里插入图片描述

基线

基线计算=一半的高度+dy。
dy代表的是:高度的一半到baseLine的距离
一半的高度=getHeight()/2
而dy就是通过(fontMetrics.bottom-fontMetrics.top)/2得到一半,然后再减去fontMetrics.bottom

在这里插入图片描述

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //画文本
        //x起始位置,y基线
        //dy代表的是:高度的一半到baseLine的距离
        Paint.FontMetricsInt fontMetrics=mPaint.getFontMetricsInt();
        //top是一个负值 bottom是一个正值 
        int dy=(fontMetrics.bottom-fontMetrics.top)/2-fontMetrics.bottom;
        int baseLine=getHeight()/2+dy;
        //如果x设置0会从0开始画,但是如果设置了padding,应该从paddingLeft开始
        int x=getPaddingLeft();
        canvas.drawText(mText,x,baseLine,mPaint);
    }

onDraw面试题

extends LinearLayout能不能出来效果?
出不来,因为默认的ViewGroup不会调用onDraw方法。

View中的draw方法

   @CallSuper
    public void draw(Canvas canvas) {
        final int privateFlags = mPrivateFlags;
        mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;

        /*
         * Draw traversal performs several drawing steps which must be executed
         * in the appropriate order:
         *
         *      1. Draw the background
         *      2. If necessary, save the canvas' layers to prepare for fading
         *      3. Draw view's content
         *      4. Draw children
         *      5. If necessary, draw the fading edges and restore layers
         *      6. Draw decorations (scrollbars for instance)
         *      7. If necessary, draw the default focus highlight
         */

        // Step 1, draw the background, if needed
        int saveCount;

        drawBackground(canvas);

        // skip step 2 & 5 if possible (common case)
        final int viewFlags = mViewFlags;
        boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
        boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
        if (!verticalEdges && !horizontalEdges) {
            // Step 3, draw the content
            onDraw(canvas);

            // Step 4, draw the children
            dispatchDraw(canvas);

            drawAutofilledHighlight(canvas);

            // Overlay is part of the content and draws beneath Foreground
            if (mOverlay != null && !mOverlay.isEmpty()) {
                mOverlay.getOverlayView().dispatchDraw(canvas);
            }

            // Step 6, draw decorations (foreground, scrollbars)
            onDrawForeground(canvas);

            // Step 7, draw the default focus highlight
            drawDefaultFocusHighlight(canvas);

            if (isShowingLayoutBounds()) {
                debugDrawFocus(canvas);
            }

            // we're done...
            return;
        }
//---------------
}

ViewGroup中的dispatchDraw方法

    protected void dispatchDraw(Canvas canvas) {
    //-----------
        for (int i = 0; i < childrenCount; i++) {
            while (transientIndex >= 0 && mTransientIndices.get(transientIndex) == i) {
                final View transientChild = mTransientViews.get(transientIndex);
                if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE ||
                        transientChild.getAnimation() != null) {
                    more |= drawChild(canvas, transientChild, drawingTime);
                }
                transientIndex++;
                if (transientIndex >= transientCount) {
                    transientIndex = -1;
                }
            }

            final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
            final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex);
            if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
                more |= drawChild(canvas, child, drawingTime);
            }
        }
}
//--------

ViewGroup中dispatchDraw()方法会调用view的draw()进行子view的绘制;调用drawChild方法

    protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
        return child.draw(canvas, this, drawingTime);
    }

child.draw(canvas, this, drawingTime);调用View中的boolean draw方法

  boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
  //-------------------
  if (!drawingWithDrawingCache) {
            if (drawingWithRenderNode) {
                mPrivateFlags &= ~PFLAG_DIRTY_MASK;
                ((RecordingCanvas) canvas).drawRenderNode(renderNode);
            } else {
                // Fast path for layouts with no backgrounds
                if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
                    mPrivateFlags &= ~PFLAG_DIRTY_MASK;
                    dispatchDraw(canvas);
                } else {
                    draw(canvas);
                }
            }
        } 

其判断依据是通过PFLAG_SKIP_DRAW标记来确定的,继承ViewGroup中的onDraw没执行,而继承View中的onDraw执行了,猜测继承ViewGroup的设置了PFLAG_SKIP_DRAW标记。
再来看ViewGroup在初始化的时候

    private void initViewGroup() {
        // ViewGroup doesn't draw by default
        if (!isShowingLayoutBounds()) {
            setFlags(WILL_NOT_DRAW, DRAW_MASK);
        }
        mGroupFlags |= FLAG_CLIP_CHILDREN;
        mGroupFlags |= FLAG_CLIP_TO_PADDING;
        mGroupFlags |= FLAG_ANIMATION_DONE;
        mGroupFlags |= FLAG_ANIMATION_CACHE;
        mGroupFlags |= FLAG_ALWAYS_DRAWN_WITH_CACHE;

        if (mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.HONEYCOMB) {
            mGroupFlags |= FLAG_SPLIT_MOTION_EVENTS;
        }

        setDescendantFocusability(FOCUS_BEFORE_DESCENDANTS);

        mChildren = new View[ARRAY_INITIAL_CAPACITY];
        mChildrenCount = 0;

        mPersistentDrawingCache = PERSISTENT_SCROLLING_CACHE;
    }

ViewGroup初始化的时候,默认设置了WILL_NOT_DRAW,再看一下setFlags方法

void setFlags(int flags,int mask) {
//-----------
 if ((changed & DRAW_MASK) != 0) {
            if ((mViewFlags & WILL_NOT_DRAW) != 0) {
                if (mBackground != null
                        || mDefaultFocusHighlight != null
                        || (mForegroundInfo != null && mForegroundInfo.mDrawable != null)) {
                    mPrivateFlags &= ~PFLAG_SKIP_DRAW;
                } else {
                    mPrivateFlags |= PFLAG_SKIP_DRAW;
                }
            } else {
                mPrivateFlags &= ~PFLAG_SKIP_DRAW;
            }
            requestLayout();
            invalidate(true);
        }
        //-------------------
        }

①如果设置了WILL_NOT_DRAW标记,那么继续检查background、foreground(mDrawable字段)、focusHighLight是否有值,如果三者任意一个设置了,那么将PFLAG_SKIP_DRAW标记清除,否则将该标记加上。

②如果没有设置WILL_NOT_DRAW标记,那么将PFLAG_SKIP_DRAW标记清除。
至此,我们知道了MyFrameLayout onDraw()方法没有执行的原因:viewGroup默认设置了WILL_NOT_DRAW标记,进而设置了PFLAG_SKIP_DRAW标记,而在绘制的时候通过判断PFLAG_SKIP_DRAW标记来决定是否调用MyFrameLayout draw(x)方法,最终调用onDraw()方法。而view默认没有设置WILL_NOT_DRAW标记,也就没有后面的事了。

总结:
若要ViewGroup onDraw()执行,只需要setWillNotDraw(false)、设置背景、设置前景、设置焦点高亮,4个选项其中一项满足即可。
当然如果不想在MyFrameLayout onDraw里绘制,也可以重写MyFrameLayout dispatchDraw()方法,在该方法里绘制MyFrameLayout内容。(需要注意的是,super.dispatchDraw(canvas)要放到后边执行,不然子view内容会被MyFrameLayout覆盖。)

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

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

相关文章

Sora:视频生成模型

​2024年2月16日&#xff0c;OpenAI 在其官网上面正式宣布推出文本生成视频的大模型 Sora: openai.com/sora Sora的能力 Sora是生成视频为主要能力的模型&#xff0c;能够&#xff1a; 1. 文/图生成视频 openai sora文生视频案例&#xff1a; Prompt: Historical footage of…

自动化立体仓库设施及设备:汇总总结

导语 大家好&#xff0c;我是社长&#xff0c;老K。专注分享智能制造和智能仓储物流等内容。 完整版文件和更多学习资料&#xff0c;请球友到知识星球【智能仓储物流技术研习社】自行下载。 这份文件是关于自动化立体仓库的详细介绍&#xff0c;包括其分类、系统组成、基本设施…

SylixOS 网卡混杂模式和组播模式

基本概念 混杂模式&#xff1a; Promiscuous mode&#xff0c;是电脑网络中的术语。是指一台机器的网卡能够接收所有经过它的数据流&#xff0c;而不论其目的地址是否是它。 一般计算机网卡都工作在非混杂模式下&#xff0c;此时网卡只接受来自网络端口的目的地址指向自己的…

《征服数据结构》LRU缓存

摘要&#xff1a; 1&#xff0c;LRU的介绍 2&#xff0c;LRU元素的添加 3&#xff0c;LRU元素的读取 4&#xff0c;LRU完整代码实现 1&#xff0c;LRU的介绍 LRU(Least Recently Used)最近最少使用&#xff0c;它是一种缓存淘汰策略。也就是说在缓存容量满的时候&#xff0c;我…

vue2 part2

数据代理 通过defineProperty里面传入obj2和x&#xff0c;然后get和set下读取接收下然后再接收set中给对象x用value接收下&#xff0c;这样就能两个数据读取和接收了 <!DOCTYPE html> <html><head><meta charset"UTF-8" /><title>何…

printk 操作等级:修改系统日志等级

这是系统日志的等级&#xff0c;在kern_levels.h文件里面 这是printk 函数前面部分先消息等级&#xff0c;中间部分就是&#xff0c;应用层调的printf的格式化字&#xff0c;后面部分就是可变参数 所以信息的等级大于终端的等级才会被打印&#xff0c;命令&#xff0c;cat /pro…

远程连接vscode无法转到定义

遇到用vscode打开远程服务器运行代码的过程中&#xff0c;无法利用插件跳转到定义&#xff08;ctrl鼠标左键///或者F12&#xff09; 问题可能出在两个地方&#xff0c;一个是插件&#xff0c;找到python插件 点击扩展设置&#xff0c;找到language server设置 这里选中pylance…

springboot调用sap接口传输数据,RFC协议接口调用,包含linux,windows部署

背景&#xff1a;我这边需要将一串数组写入到sap系统中&#xff0c;原本希望sap能提供rest形式接口&#xff0c;可惜sap开发那边说sap对外都是rfc接口&#xff0c;现在记录一下sap接口对接&#xff0c;给其他小伙伴提供点经验。 1、首先必须有对应的原料&#xff0c;驱动jar包…

Web应用服务器Tomcat

一、tomcat Tomcat是Java语言开发的&#xff0c;Tomcat服务器是一个免费的开放源代码的Web应用服务器&#xff0c;是Apache软件基金会的Jakarta项目中的一个核心项目,由Apache、Sun和其他一些公司及个人共同开发而成。Tomcat属于轻量级应用服务器&#xff0c;在中小型系统和并发…

【微信小程序】自定义组件 - 数据监听器

1. 什么是数据监听器 2. 数据监听器的基本用法 组件的 UI 结构如下&#xff1a; 组件的 .js 文件代码如下&#xff1a; 3. 监听对象属性的变化 数据监听器 - 案例 案例效果 2. 渲染 UI 结构 3. 定义 button 的事件处理函数 4. 监听对象中指定属性的变化 5. 监听对象中所…

Vite + Vue 3 项目中实现路由自动化完整步骤。

下面是从创建项目到实现路由自动化的完整步骤 在 Vite Vue 3 项目中实现路由自动化可以通过使用文件系统路由生成器来简化路由管理过程。以下是实现路由自动化的完整步骤&#xff1a; 1.创建 Vite Vue 3 项目 如果你还没有一个 Vite Vue 3 项目&#xff0c;可以使用以下命令…

Oracle开始严查Java许可!

0x01、 前段时间在论坛里就看到一个新闻&#xff0c;说“Oracle又再次对Java下手&#xff0c;开始严查Java许可&#xff0c;有企业连夜删除JDK”&#xff0c;当时就曾在网上引起了一阵关注和讨论。 这不最近在科技圈又看到有媒体报道&#xff0c;Oracle再次严查&#xff0c;对…

【鸿蒙学习】HarmonyOS应用开发者高级认证 - 应用开发安全(含闯关习题)

学完时间&#xff1a;2024年8月23日 学完排名&#xff1a;第1748名 一、安全设计理念 以硬件TCB作为安全信任基础,软硬结合的安全设计 HarmonyOS系统安全设计基础: 基于最小的可信计算基础TCB;硬件主密钥,加解密引擎关键安全组件基于TEE可信运行环境;TEE&#xff08;Truste…

自编码器(Autoencoder, AE):深入理解与应用

自编码器&#xff08;Autoencoder, AE&#xff09;&#xff1a;深入理解与应用 引言 自编码器&#xff08;Autoencoder, AE&#xff09;是一种通过无监督学习方式来学习数据有效表示的神经网络模型。其核心思想是通过编码器将输入数据压缩成低维潜在表示&#xff0c;然后通过…

Element-UI自学实践(二)

因上篇 Element-UI自学实践&#xff08;一&#xff09; 文字过多&#xff0c;不便于观看&#xff0c;故另起一篇。 5. 反馈组件 反馈组件用于与用户进行交互&#xff0c;提供即时反馈&#xff0c;包括警告&#xff08;Alert&#xff09;、消息提示&#xff08;Message&#x…

Datawhale X 李宏毅苹果书 AI夏令营task1-3笔记

1.1 通过案例了解机器学习 机器学习基础 首先简单介绍一下机器学习&#xff08;Machine Learning&#xff0c;ML&#xff09;和深度学习&#xff08;Deep Learning&#xff0c;DL&#xff09;的基本概念。机器学习&#xff0c;顾名思义&#xff0c;机器具备有学习的能力。具体…

[笔记] 某振动分析软件的可能侦测范围

1.可检测量【部分】 后面有图例&#xff0c;很好找&#xff0c;其实。可以在bing.com搜索image. {"type": "sdc-application-types","version": 1,"data": [{"name": "Disabled ","type": "disa…

<数据集>遥感船舶识别数据集<目标检测>

数据集格式&#xff1a;VOCYOLO格式 图片数量&#xff1a;15047张 标注数量(xml文件个数)&#xff1a;15047 标注数量(txt文件个数)&#xff1a;15047 标注类别数&#xff1a;25 标注类别名称&#xff1a;[Aircraft Carrier, Auxiliary Ships, Other Ship, Other Warship,…

实车测试的目的和作用 (Purpose and function of real vehicle test)

实车测试的目的和作用主要在于验证整车控制器软件的功能&#xff0c;确保其在实车环境下的安全性和稳定性。实车测试是整车控制器软件发布前不可或缺的一个测试环节&#xff0c;主要目的是在实车环境上验证VCU最常规的功能&#xff0c;对HIL测试台架无法模拟的工况进行补充测试…

孤独症托养无需家长陪护中心:守护每一份独特的星光

在星贝育园&#xff0c;作为一所专业的孤独症寄宿学校&#xff0c;我们致力于为孤独症儿童提供最全面、最专业的康复与成长环境。以下是我们的主要优势&#xff1a; 一、专业的师资团队 星贝育园拥有一支经验丰富、专业素养极高的教师团队。我们的教师不仅具备深厚的…