Android自定义简单TextView

自定义属性

<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.TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="11111"/>* */public TextView(Context context, @Nullable AttributeSet attrs) {this(context, attrs,0);}//在布局layout中使用(调用),但是会有stylepublic 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.TextViewstyle="@style/default_text"android:text="11111"/>* */public TextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {super(context, attrs, defStyleAttr, defStyleRes);}@Overrideprotected 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的宽度为文本的宽度,文本也就是设置的mTextmPaint.getTextBounds(mText,0,mText.length(),bounds);//如果xml设置了padding记得要加没有就是0width=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记得要加没有就是0height=bounds.height()+getPaddingTop()+getPaddingBottom();}setMeasuredDimension(width,height);}@Overrideprotected 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();}/*** 处理跟用户交互的,手指触摸等等*/@Overridepublic 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.TextViewandroid: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

在这里插入图片描述

    @Overrideprotected 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方法

   @CallSuperpublic 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 neededint 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 contentonDraw(canvas);// Step 4, draw the childrendispatchDraw(canvas);drawAutofilledHighlight(canvas);// Overlay is part of the content and draws beneath Foregroundif (mOverlay != null && !mOverlay.isEmpty()) {mOverlay.getOverlayView().dispatchDraw(canvas);}// Step 6, draw decorations (foreground, scrollbars)onDrawForeground(canvas);// Step 7, draw the default focus highlightdrawDefaultFocusHighlight(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 backgroundsif ((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 defaultif (!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.rhkb.cn/news/407565.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

torchvision中的数据集使用

1.数据集&#xff1a; 自定义数据集transforms中的类 如何将数据集和transforms结合在一起&#xff1f; 以CIFAR10为列 2.CIFAR10数据集的下载与导入 import torchvisiontrain_settorchvision.datasets.CIFAR10(root"./dataset",trainTrue,downloadTrue) test_set…

判别分析2|Bayes判别分析|Fisher判别分析|软件求解

Bayes判别分析 引入先验信息 距离判别只要求知道总体的数字特征&#xff0c;不涉及总体的分布函数 当均值和协方差未知时&#xff0c;就用样本的均值和协方差矩阵做估计值。距离判别方法简单实用&#xff0c;但没有考虑到每个总体出现的机会大小&#xff0c;即先验概率&#…

Git的使用教程及常用语法03

七.如何从版本库中删除文件 第一种方式&#xff1a;直接在工作区删除文件&#xff0c;然后提交 rm ffile1.txt (注意&#xff1a;这个不是git命令&#xff0c;而是linux命令) 看到状态发现&#xff0c;文件file1.txt已经被删除&#xff0c;提示需要提交到暂存区。 因为我们只…

从开发到集成:视频美颜SDK与直播美颜API详解

在本文中&#xff0c;我们将详细探讨视频美颜SDK的开发过程及其与直播美颜API的集成方案&#xff0c;帮助开发者更好地理解和应用这些技术。 一、视频美颜SDK的开发概述 视频美颜SDK是一个用于实时视频处理的开发工具包&#xff0c;提供了包括磨皮、美白、瘦脸、眼睛放大等多…

盘古信息IMS MCM制造协同管理系统:为中小企业数字化转型量身打造的数字化方案

近年来&#xff0c;全球经济的不稳定性&#xff0c;给中小企业的经营和发展带来了巨大的挑战。为提升企业竞争力&#xff0c;中小企业纷纷谋求数字化转型路径&#xff0c;优化生产流程、提高运营效率、降低生产成本&#xff0c;以应对变幻莫测的市场环境。IMS MCM是盘古信息为广…

python爬虫521

爬虫521 记录 记录 最近想学爬虫&#xff0c;尝试爬取自己账号下的文章标题做个词云 csdn有反爬机制 原理我就不说啦 大家都写了 看到大家结果是加cookie 但是我加了还是521报错 尝试再加了referer 就成功了(╹▽╹) import matplotlib import requests from wordcloud impor…

TinaSDKV2.0 自定义系统开发

TinaSDKV2.0 自定义系统开发 什么是自定义系统&#xff1f; TinaSDK Kconfig界面配置 Tina Linux采用 Kconfig 机制对 SDK 和内核进行配置。 Kconfig 是一种固定格式的配置文件。Linux 编译环境中的 menuconfig 程序可以识别这种格式的配置文件&#xff0c;并提取出有效信息…

使用redis模拟cookie-session,例子:实现验证码功能

在前后端分离架构中不建议使用cookie-session机制实现端状态识别 原因&#xff1a; 1.前后端分离存在跨域问题&#xff0c;cookie无法共享 2.后台服务器一旦建立集群&#xff0c;可能导致session数据丢失&#xff0c;即后台有多台服务器&#xff0c;每个服务器存的session不一…

Flutter ListView控件

ListView是flutter中线性排列的可滚动的列表部件。ListView 是最常用的滚动小部件。它在滚动方向上一个接一个地显示其子项。在交叉轴上&#xff0c;子项需要填满 ListView。 如果非空&#xff0c;则 itemExtent 会强制子项在滚动方向上具有给定的范围。 如果非空&#xff0c;…

kafka的一个有趣问题(BUG)

这是我的第104篇原创文章 问题由来 在使用kafka时&#xff0c;创建topic&#xff0c;对某个topic进行扩分区的操作&#xff0c;想必大家肯定都使用过。尤其是集群进行扩容时&#xff0c;对流量较大的topic进行扩分区操作。一般而言&#xff0c;期望的效果是&#xff1a;新扩的分…

【Qt】常用控件QPushButton

常用控件QPushButton QWidget中涉及的各种属性/函数/使用方法&#xff0c;对Qt中的各种控件都是有效的。 QPushButton继承自QAbstractButton。这个类是抽象类&#xff0c;是其他按钮的父类。 QAbstractButton中和QPushButton相关性比较大的属性。 属性说明 text 按钮中的⽂本…

TCP/UDP的对比,粘包分包抓包,http协议

服务器端&#xff1a; 一、loop 127.0.0.1本地回环测试地址 二、tcp特点 面向连接、可靠传输、字节流 粘包问题&#xff1a;tcp流式套接字&#xff0c;数据与数据之间没有套接字&#xff0c;导致可能多次的数据粘到一起 解决方法&#xff1a;&#xff08;1&#xff09;规…

后端Java秋招面试中的自我介绍需要说什么?

本文主要面向校招/实习面试中求职后端开发岗位的同学&#xff0c;其他岗位/社招的同学也可以参考&#xff0c;道理都是相通的 1 背景 1.1为什么要认真准备自我介绍&#xff1f; 1. 必要性&#xff1a;在求职面试中&#xff0c;一般来说同学和面试官打过招呼之后第一项就是自…

html+css+js网页设计 电商 珠宝首饰电商3个页面

htmlcssjs网页设计 电商 珠宝首饰电商3个页面 网页作品代码简单&#xff0c;可使用任意HTML编辑软件&#xff08;如&#xff1a;Dreamweaver、HBuilder、Vscode 、Sublime 、Webstorm、Text 、Notepad 等任意html编辑软件进行运行及修改编辑等操作&#xff09;。 获取源码 1…

网络编程知识点总结

物理链路网络运输会话表示应用 物链网运会表应 实际的数据帧 TCP和UDP的异同&#xff08;笔试面试&#xff09; 主机&#xff1a;host 转换&#xff1a;to 网络&#xff1a;network uint32_t htonl(uint32_t hostlong); //将4字节无符号整数的主机字节序转换为网络字节序&a…

掌握语义内核(Semantic Kernel):如何使用Memories增强人工智能应用

随着人工智能领域的不断发展&#xff0c;语义内核&#xff08;Semantic Kernel&#xff09;的概念应运而生&#xff0c;为我们处理和理解庞大的数据集提供了新的视角。今天&#xff0c;我们将聚焦于语义内核中的一个核心概念——Memories&#xff0c;它是如何使我们的数据查询更…

QT中使用QAxObject类读取xlsx文件内容并显示在ui界面

一、源码 #ifndef MAINWINDOW_H #define MAINWINDOW_H#include <QMainWindow>QT_BEGIN_NAMESPACE namespace Ui { class MainWindow; } QT_END_NAMESPACEclass MainWindow : public QMainWindow {Q_OBJECTpublic:MainWindow(QWidget *parent nullptr);~MainWindow();pr…

鸿蒙内核源码分析(共享内存) | 进程间最快通讯方式

运行机制 共享好端端的一词&#xff0c;近些年被玩坏了&#xff0c;共享单车,共享充电宝,共享办公室&#xff0c;共享雨伞… 甚至还有共享女朋友&#xff0c;真是人有多大胆&#xff0c;共享有多大产。但凡事太尽就容易恶心到人&#xff0c;自己也一度被 共享内存 恶心到了&am…

看图学sql之sql中的子查询

&#xfeff;&#xfeff; &#xfeff;where子句子查询 语法&#xff1a; SELECT column_name [, column_name ] FROM table1 [, table2 ] WHERE column_name OPERATOR(SELECT column_name [, column_name ]FROM table1 [, table2 ][WHERE]) 子查询需要放在括号( )内。O…

解决git checkout -b 拉取远端某分支到本地时报错

问题描述 日常开发场景中&#xff0c;经常会出现切分支的情况&#xff0c;所以git checkout 命令是非常高频的 git checkout -b feature/xxx默认情况下&#xff0c;这条命令是基于当前所在分支来开辟新分支feature/xxx 但是&#xff0c;还有一些情况&#xff0c;我们需要基于…