1.自定义属性以备使用

新建attrs.xml

<?xml version="1.0" encoding="utf-8"?>
<resources><!-- name 自定义view的名字 --><declare-styleable name="MyTextView"><!-- name 属性名称 format 格式 --><attr name="ChjText" format="string"/><attr name="ChjTextColor" format="color"/><attr name="ChjTextSize" format="dimension"/><attr name="ChjMaxLength" format="integer"/><attr name="ChjBackground" format="reference|color"/><!-- 枚举类型的自定义属性 --><attr name="inputType"><enum name="number" value="1"/><enum name="text" value="2"/><enum name="password" value="3"/></attr></declare-styleable>
</resources>

2.创建自定义textview java文件

3.从xml读取自定义属性

        //获取自定义属性TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.MyTextView);myText = array.getString(R.styleable.MyTextView_ChjText);myColor = array.getColor(R.styleable.MyTextView_ChjTextColor, Color.BLACK);//15pxmTextSize = array.getDimensionPixelSize(R.styleable.MyTextView_ChjTextSize, mTextSize);mTextSize = sp2px(mTextSize);//回收array.recycle();

4.继续完成构造方法 onMeasure以及onDraw

package com.example.chj.baseline;import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.util.Log;
import android.util.TypedValue;
import android.view.MotionEvent;
import android.view.View;
import android.widget.LinearLayout;@SuppressLint("AppCompatCustomView")
public class MyTextView extends LinearLayout {//如果改成LinearLayout会出现view显示不出来的问题 原因是 view group 不会触发on Draw方法private static final String TAG = "chjchj";private String myText;private int myColor = Color.BLACK;//default value:blackprivate int mTextSize = 15;//default value:15dpprivate Paint mPaint;//绘制文字的paint;public MyTextView(Context context) {this(context, null);}public MyTextView(Context context, @Nullable AttributeSet attrs) {this(context, attrs, 0);}public MyTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);//获取自定义属性TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.MyTextView);myText = array.getString(R.styleable.MyTextView_ChjText);myColor = array.getColor(R.styleable.MyTextView_ChjTextColor, Color.BLACK);//15pxmTextSize = array.getDimensionPixelSize(R.styleable.MyTextView_ChjTextSize, mTextSize);mTextSize = sp2px(mTextSize);//回收array.recycle();mPaint = new Paint();mPaint.setAntiAlias(true);//抗锯齿mPaint.setTextSize(mTextSize);mPaint.setColor(myColor);setWillNotDraw(false);}private int sp2px(int sp) {return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, sp, getResources().getDisplayMetrics());}/*** 自定义view的测量方法** @param widthMeasureSpec* @param heightMeasureSpec*/@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {super.onMeasure(widthMeasureSpec, heightMeasureSpec);//自定view宽高都由这个方法指定//获取宽高的测量模式
//        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
//        //MeasureSpec包含了两个信息 他是一个三十二位的值 开头两位代表mode 后面30位代表值(size)
//        //mode和值组合起来就是32位的MeasureSpec
//        heightMeasureSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST);//这种写法可以解决scrollview 嵌套listview高度显示不全的问题
//        int heightMode = MeasureSpec.getMode(heightMeasureSpec);int widthMode = MeasureSpec.getMode(widthMeasureSpec);int width = MeasureSpec.getSize(widthMeasureSpec);if (widthMode == MeasureSpec.AT_MOST) {//处理wrap_content的宽高计算//计算文字的宽度 与文字的size和font有关//测量文字myText 起始点为x==0 终点到text的末尾 计算完成之后 rect会被赋值Rect bounds = new Rect();mPaint.getTextBounds(myText, 0, myText.length(), bounds);width = bounds.width() + getPaddingStart() + getPaddingEnd();}int heightMode = MeasureSpec.getMode(heightMeasureSpec);int height = MeasureSpec.getSize(heightMeasureSpec);if (heightMode == MeasureSpec.AT_MOST) {//处理wrap_content的宽高计算//计算文字的高度 与文字的size和font有关Rect bounds = new Rect();mPaint.getTextBounds(myText, 0, myText.length(), bounds);height = bounds.height() + getPaddingTop() + getPaddingBottom();}setMeasuredDimension(width, height);//实际真正设置宽高的地方}//用于绘制@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);//drawText的参数 文本 x y paint//x代表起始点 y代表基线Paint.FontMetricsInt fontMetricsInt = mPaint.getFontMetricsInt();Log.d(TAG, "onDraw: fontMetricsInt.top " + fontMetricsInt.top);Log.d(TAG, "onDraw: fontMetricsInt.ascent " + fontMetricsInt.ascent);Log.d(TAG, "onDraw: fontMetricsInt.descent " + fontMetricsInt.descent);Log.d(TAG, "onDraw: fontMetricsInt.bottom " + fontMetricsInt.bottom);Log.d(TAG, "onDraw: getHeight " + getHeight());int dy = (fontMetricsInt.bottom - fontMetricsInt.top) / 2 - fontMetricsInt.bottom;//int baseLine = Math.abs((fontMetricsInt.bottom - fontMetricsInt.top - getHeight()) / 2 + fontMetricsInt.top);//错误算法int baseLine = getHeight() / 2 + dy;int baseLine2 = getHeight() / 2 - fontMetricsInt.bottom / 2 - fontMetricsInt.top / 2;Log.d(TAG, "onDraw: baseLine " + baseLine);Log.d(TAG, "onDraw: baseLine2 " + baseLine2);canvas.drawText(myText, getPaddingStart(), baseLine, mPaint);//canvas.drawArc();//画弧//canvas.drawCircle();}//处理事件分发 与用户交互@Overridepublic boolean onTouchEvent(MotionEvent event) {return super.onTouchEvent(event);}
}

4.1 main_activity的布局文件

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:chj="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:layout_margin="16dp"android:layout_width="match_parent"android:layout_height="match_parent"tools:context=".MainActivity"><com.example.chj.baseline.MyTextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:padding="10dp"chj:ChjText="河南Darren"chj:ChjTextColor="#f00"chj:ChjTextSize="20sp"/></RelativeLayout>

5.关于基线的计算

视频里面的计算有点混乱 其实这是个很容易计算的问题
以上图片取自红橙Darren的简书链接https://www.jianshu.com/p/6e4b3eebbba0
我们已知图中的红线1 2 3 4的4个高度分别为
fontMetricsInt.top
fontMetricsInt.ascent
fontMetricsInt.descent
fontMetricsInt.bottom
并且前两个为负数 后两个为正数
另 r.top+r.botton=getHeight()
最后有一个隐含条件 图中红线5和红线6的距离相等
求:r.top(也就是baseline)
这是一个非常简单的数学问题
(红线1的长度+红线4的长度-getHeight())/2 = 红线5的长度
红线1的长度-红线5的长度=r.top
考虑正负值的因素 最后结果就是:

int baseLine = Math.abs((fontMetricsInt.bottom - fontMetricsInt.top - getHeight()) / 2 + fontMetricsInt.top);

事实上打印几个log 几分钟就能看出baseline的计算


2020/12/7更新
之前的算法有问题 只不过碰巧答案一样
正确理解如下
首先上面代码的log打印如下

12-07 15:02:05.796 24192-24192/com.example.chj.baseline D/chjchj: onDraw: fontMetricsInt.top -22
12-07 15:02:05.797 24192-24192/com.example.chj.baseline D/chjchj: onDraw: fontMetricsInt.ascent -19onDraw: fontMetricsInt.descent 5
12-07 15:02:05.798 24192-24192/com.example.chj.baseline D/chjchj: onDraw: fontMetricsInt.bottom 6
12-07 15:02:05.799 24192-24192/com.example.chj.baseline D/chjchj: onDraw: getHeight 40
12-07 15:02:05.800 24192-24192/com.example.chj.baseline D/chjchj: onDraw: baseLine 28onDraw: baseLine2 28

再来还是之前那张图片 其中有个理解问题 即我之前将getHeight理解错误,从log看 getHeight的值大于 (fontMetricsInt.top的绝对值+fontMetricsInt.bottom的绝对值)
新的理解如下:

已知图中
蓝色线段1=-fontMetricsInt.top(fontMetricsInt.top是负值)
蓝色线段2=fontMetricsInt.bottom
蓝色线段3=getHeight()/2
求蓝色线段4 baseline(线段4的长度刚好是y坐标)

易得dy=(fontMetricsInt.bottom-fontMetricsInt.top)/2-fontMetricsInt.bottom
线段4=getHeight()/2+dy=getHeight()/2+(fontMetricsInt.bottom-fontMetricsInt.top)/2-fontMetricsInt.bottom=(getHeight()-fontMetricsInt.bottom-fontMetricsInt.top)/2

6.关于问题引申 如果自定义textview不是继承view 而是继承LinearLayout呢?

结果:会出现自定义view不显示的情况

原因:view本身会调用onDraw方法 而LinearLayout继承自viewgroup,viewgroup因为一些原因没有调用onDraw方法

观察view的源码
draw(Canvas canvas)方法
if (!dirtyOpaque) onDraw(canvas);
如果dirtyOpaque为true 则不会绘制content
而 mPrivateFlags决定了dirtyOpaque的值
mPrivateFlags的初始化:

    protected void computeOpaqueFlags() {// Opaque if://   - Has a background//   - Background is opaque//   - Doesn't have scrollbars or scrollbars overlayif (mBackground != null && mBackground.getOpacity() == PixelFormat.OPAQUE) {mPrivateFlags |= PFLAG_OPAQUE_BACKGROUND;} else {mPrivateFlags &= ~PFLAG_OPAQUE_BACKGROUND;}final int flags = mViewFlags;if (((flags & SCROLLBARS_VERTICAL) == 0 && (flags & SCROLLBARS_HORIZONTAL) == 0) ||(flags & SCROLLBARS_STYLE_MASK) == SCROLLBARS_INSIDE_OVERLAY ||(flags & SCROLLBARS_STYLE_MASK) == SCROLLBARS_OUTSIDE_OVERLAY) {mPrivateFlags |= PFLAG_OPAQUE_SCROLLBARS;} else {mPrivateFlags &= ~PFLAG_OPAQUE_SCROLLBARS;}}

而在ViewGroup中

    private void initViewGroup() {// ViewGroup doesn't draw by defaultif (!debugDraw()) {setFlags(WILL_NOT_DRAW, DRAW_MASK);//调用view的setFlags方法 重新计算flag}}

使得不能画出content

解决方法:

思路:
看哪些方法调用了正确的flag设置(computeOpaqueFlags重新计算flag) 或者自己重新设置flag
1.protected void onDraw(Canvas canvas) 方法修改为protected void dispatchDraw(Canvas canvas)(直接绘制)
2.构造方法中添加一句调用

setBackgroundColor(Color.TRANSPARENT);

因为在调用setBackgroundColor(Color.TRANSPARENT);的时候 会重新计算flag

    public void setBackgroundColor(@ColorInt int color) {if (mBackground instanceof ColorDrawable) {((ColorDrawable) mBackground.mutate()).setColor(color);computeOpaqueFlags();mBackgroundResource = 0;} else {setBackground(new ColorDrawable(color));}}

3.直接设置flag 在构造方法调用setWillNotDraw(false);
注意:这里的源码是24版本的 29版本的和24的有很大不同 但是解决方案都是可以生效的

红橙Darren视频笔记 自定义TextView 基线的理解 问题引申(viewgroup 不触发onDraw方法)相关推荐

  1. 红橙Darren视频笔记 自定义View总集篇

    本节目的 了解 ActivityManagerService Activity ActivityManager Window WindowManager WindowManagerService Se ...

  2. 红橙Darren视频笔记 自定义sidebar 自定义View ViewGroup套路

    参考链接 https://www.jianshu.com/p/1dc41a770f64 1效果 2 目的 学习onMeasure onDraw onTouchEvent等自定义view方法的使用 3 ...

  3. 红橙Darren视频笔记 自定义RatingBar touch事件学习 dp转px listener监听

    效果图: 一 需求分析 我们需要实现评分的控件 那么主要有几步 1.绘制出评分控件 2.响应用户的触摸改变星星数 3.控件发生星星变化时通知监听者 二 自定义属性 需要属性: 星星总数 选中星星的图片 ...

  4. 红橙Darren视频笔记 UML图简介

    整体架构复制自红橙原视频的课堂笔记 因为他这一课没有博客,所以没有转载链接,CSDN没有转载地址是无法作为转载类型的文章发表的,暂时标记为原创 参考链接 https://blog.csdn.net/r ...

  5. 红橙Darren视频笔记 类加载机制(API28) 自己写个热修复 查看源码网站

    第一部分 类加载机制 一个Activity是如何被Android虚拟机找到的? 在之前的文章 红橙Darren视频笔记 自定义View总集篇(https://blog.csdn.net/u011109 ...

  6. 红橙Darren视频笔记 Behavior的工作原理源码分析

    主要coordinatorlayout的代码来自coordinatorlayout-1.0.0-sources.jar 本文从源码介绍 CoordinatorLayout 的 behavior 怎么工 ...

  7. 红橙Darren视频笔记 代理模式 动态代理和静态代理

    红橙Darren视频笔记 代理模式 动态代理和静态代理(Android API 25) 关于代理模式我之前有过相关的介绍: https://blog.csdn.net/u011109881/artic ...

  8. 红橙Darren视频笔记 利用阿里巴巴AndFix进行热修复

    注意 由于AndFix在2017年左右就停止更新了,在最新版本的apk上遇到很多问题,我最终也没有成功进行热修复.本节主要是学习热修复的原理 在上一篇 红橙Darren视频笔记 自己捕获异常并保存到本 ...

  9. 红橙Darren视频笔记 仿QQ侧滑效果

    这一篇没有什么新的内容 就是改写 红橙Darren视频笔记 仿酷狗侧滑效果 的侧滑的效果 1.去掉淡入淡出效果 2.加上黑色模板效果 效果: 去掉淡入淡出效果很简单 就是注释掉onScrollChan ...

最新文章

  1. 十天学会ASP.net
  2. delphi 调用php接口_爱站权重查询 API 接口请求调用
  3. 我手撸了一个划线翻译工具!
  4. Python编程模块里一些小众但是却比较实用的python内置库
  5. WPF DataGrid:解决排序、ScrollIntoView、刷新和焦点问题
  6. Linux命令(1)—— xargs 命令
  7. win11系统卡死怎么办 Windows11系统卡死的解决方法
  8. 【算法学习】布谷鸟搜索算法【CuckooSearch(CS)】
  9. 计算机网络——网络与互联网
  10. 文件未找到mathpage.wll_解决MathPage.wll文件找不到的问题(找了好久的良心之作)...
  11. 强连通分量(Tarjan算法)和缩点
  12. 1分钟利用Excel快速制作随机点名器
  13. 逆向破解——win7-vm逆向平台搭建
  14. P13 - 软件设计质量评审 之 八个评审要求
  15. git 更新密码之后出现异常(雾)
  16. 外部Portal认证系统 对接 华为 H3C 等设备实现 LDAP AD域 网络准入实名认证
  17. 行政科购入计算机一台,行政单位会计分录练习题.doc
  18. 网络安全:常见的网络协议
  19. 【《Hacker与画家》读厚】第一章 为什么书呆子不受欢迎——Paul眼里的学校 痛苦的根源
  20. Android 截屏(Screenshot)代码流程小结

热门文章

  1. 假设以带头结点的循环链表表示队列_数据结构·链表(C实现)
  2. 子查询返回多个字段_ElasticSearch搜索之QueryFiltering、多/单字符串的多字段查询...
  3. cSpring Boot整合RabbitMQ详细教程
  4. 餐饮店楼梯空间的设计方案
  5. 关系模式候选键求取的算法
  6. 5_1 大理石在哪儿(UVa10474)排序与查找
  7. ip找计算机名 linux,如何从IP地址中查找LAN中的计算机名称?
  8. linux php文件,Linux php文件安装目录在哪
  9. downloader怎么用 hls_如何下载企业微信直播回放视频(HLS格式)
  10. PostgreSQL中常见的14个用户安全配置