自己很少做自定义 View ,只有最开始的时候跟着郭神写了一个小 Demo ,后来随着见识的越来越多,特别是在开源社区看到很多优秀的漂亮的控件,都是羡慕的要死,但是拉下来的代码还是看不明白,而且当时因为时间因素,没有深入学习和研究控件和动画方面的知识,而是把更多时间花在了 Android 的异步通信和网络框架这一块。

目标效果

需求:实习公司一个产品,因为很多是临时用户,需要为这些没有自觉设置头像的用户,给予随机头像。生成的规则是根据用户用户名的第一个字符随机匹配颜色集。

从需求中我们可以知道:

      • 该控件需要展示图片
      • 该控件需要按照规则生成图像
      • 一般头像都是圆形

大致上可以知道是这样的。
开搞!

继承 ImageView 开始

我们都知道 Android 自带了很多控件,我们自定义控件的出发点只是官方提供的控件无法满足业务需求的时候。
从我们的需求来看,该控件是图片展示类的,所以我们很自然想到了只需要在系统 ImageView 上进行功能拓展即可,这样就可以满足新的需求又不会失去 ImageView 自带的功能。

public class CharAvatarView extends ImageView {
private static final String TAG = CharAvatarView.class.getSimpleName();
// 颜色画板集
private static final int[] colors = {
0xff1abc9c, 0xff16a085, 0xfff1c40f, 0xfff39c12, 0xff2ecc71,
0xff27ae60, 0xffe67e22, 0xffd35400, 0xff3498db, 0xff2980b9,
0xffe74c3c, 0xffc0392b, 0xff9b59b6, 0xff8e44ad, 0xffbdc3c7,
0xff34495e, 0xff2c3e50, 0xff95a5a6, 0xff7f8c8d, 0xffec87bf,
0xffd870ad, 0xfff69785, 0xff9ba37e, 0xffb49255, 0xffb49255, 0xffa94136
};
private Paint mPaintBackground;
private Paint mPaintText;
private Rect mRect;
private String text;
private int charHash;
public CharAvatarView(Context context) {
this(context, null);
}
public CharAvatarView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public CharAvatarView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
mPaintBackground = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaintText = new Paint(Paint.ANTI_ALIAS_FLAG);
mRect = new Rect();
}
}

在这里我做了一些初始化工作,并且在其中的一个构造函数中实例化了 Paint 和 Rect 。

关于 View 的构造函数的区别:

public CharAvatarView(Context context) {
super(context);
}
public CharAvatarView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CharAvatarView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
      • 第一种属于程序内实例化时采用,之传入 Context 即可

        CharAvatarView avatarView = new CharAvatarView(this);

这样我们的 View 就新建出来了,根据需求添加到布局即可。

      • 第二种用于 layout 文件实例化,会把 XML 内的参数通过 AttributeSet 带入到 View 内。

      • 第三个主题的 style 信息,也会从 XML 里带入

为了自定义的 View 兼容 Java 和 Xml 两种代码的使用方式,一般推荐这样写构造方法:

public CharAvatarView(Context context) {
this(context, null);
}
public CharAvatarView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public CharAvatarView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
mPaintBackground = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaintText = new Paint(Paint.ANTI_ALIAS_FLAG);
mRect = new Rect();
}

工作流程

我们的 View 系统是如何将它绘制到屏幕上的呢?

View 的绘制流程是从 ViewRoot 的 performTraversals 方法开始,它经过 measure 、 layout 和 draw 三个过程才能最终将一个 View 绘制出来,其中 measure 用来测量 View 的宽和高,layout 用来确定 View 在父容器中的放置位置,而 draw 则负责将 View 绘制在屏幕上。针对 performTraversals 的大致流程如图:

Measure 过程决定了 View 的宽/高, Measure 完成以后,可以通过 getMeasuredWidth 和getMeasuredHeight 方法来获取到 View 测量后的宽/高,在几乎所有的情况下它都等同于 View 最终的宽/高,但是特殊情况除外。
Layout 过程 决定了 View 的四个顶点的坐标和实际的 View 的宽/高,完成以后,可以通过getTopgetBottomgetLeftgetRight 来拿到 View 的四个顶点的位置,并可以通过getWidth 和 getHeight 方法拿到 View 最终的宽/高。
Draw 过程则决定了 View 的显示,只有 draw 方法完成以后 View 的内容才能呈现在屏幕上。

关于 View 工作流程的深入我们在以后另外开篇进行研究。目前我们已经从宏观了解到了 View 会经历三个过程绘制出来,而且清楚了其中不同方法中的用途。接下来我们看看 CharAvatarView 在这三个流程中分别做了什么。

onMeasure()

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, widthMeasureSpec); // 宽高相同
}

让宽高相同,我在这里是只直接传入宽度进行测量。
这样会得到一个正方形的 View。

onLayout()

@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
}

我在这里什么也没有做,因为需求里对 View 的位置没有什么需要特殊的处理。

onDraw()

大部分自定义控件,最核心的代码就是在 onDraw() 里了。

protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (null != text) {
int color = colors[charHash % colors.length];
// 画圆
mPaintBackground.setColor(color);
canvas.drawCircle(getWidth() / 2, getWidth() / 2, getWidth() / 2, mPaintBackground);
// 写字
mPaintText.setColor(Color.WHITE);
mPaintText.setTextSize(getWidth() / 2);
mPaintText.setStrokeWidth(3);
mPaintText.getTextBounds(text, 0, 1, mRect);
// 垂直居中
Paint.FontMetricsInt fontMetrics = mPaintText.getFontMetricsInt();
int baseline = (getMeasuredHeight() - fontMetrics.bottom - fontMetrics.top) / 2;
// 左右居中
mPaintText.setTextAlign(Paint.Align.CENTER);
canvas.drawText(text, getWidth() / 2, baseline, mPaintText);
}
}
      1. 首先从颜色数组里根据 hash 取余得到背景颜色
      2. 然后画出背景圆
      3. 接下来就是写字
      4. 最后是对字居中的处理
/**
* @param content 传入字符内容
* 只会取内容的第一个字符,如果是字母转换成大写
*/
public void setText(String content) {
if (content == null) {
content=" ";
}
this.text = String.valueOf(content.toCharArray()[0]);
this.text = text.toUpperCase();
charHash = this.text.hashCode();
// 重绘
invalidate();
}

这是暴露给外部的方法,我们也是在这里得到要画的字符。

使用

在 gradle 依赖里添加:

compile 'com.github.xcc3641:charavatarview:0.1'
<com.hugo.charavatarview.CharAvatarView
android:layout_width="50dp"
android:layout_height="50dp"
android:id="@+id/avatar"/>
CharAvatarView mAvatarView;
mAvatarView = (CharAvatarView) findViewById(R.id.avatar);
mAvatarView.setText("谢三弟");

运行:

人生第一个自定义 View 就完成了。

上传到可以参考司机的这篇文章码农必知之上传开源库到 jcenter,配置好各种参数。以后更新版本就执行一行代码就行啦。

./gradlew install // 只需要第一次执行
./gradlew bintrayUpload

开源地址:GitHub 地址

Android 初阶自定义 View 字符头像的更多相关文章

  1. 高阶自定义View --- 粒子变幻、隧道散列、组合文字

    高阶自定义View --- 粒子变幻.隧道散列.组合文字 作者:林冠宏 / 指尖下的幽灵 掘金:https://juejin.im/user/587f0dfe128fe100570ce2d8 博客:h ...

  2. Android初级教程初谈自定义view自定义属性

    有些时候,自己要在布局文件中重复书写大量的代码来定义一个布局.这是最基本的使用,当然要掌握:但是有些场景都去对应的布局里面写对应的属性,就显得很无力.会发现,系统自带的控件无法满足我们的要求,这个时候 ...

  3. Android中使用自定义View实现下载进度的显示

    一般有下载功能的应用都会有这样一个场景,需要一个图标来标识不同的状态.之前在公司的项目中写过一个,今天抽空来整理一下. 一般下载都会有这么几种状态:未开始.等待.正在下载.下载结束,当然有时候会有下载 ...

  4. Android 自定义View修炼-Android开发之自定义View开发及实例详解

    在开发Android应用的过程中,难免需要自定义View,其实自定义View不难,只要了解原理,实现起来就没有那么难. 其主要原理就是继承View,重写构造方法.onDraw,(onMeasure)等 ...

  5. android尺子的自定义view——RulerView

    项目中用到自定义尺子的样式: 原代码在github上找的,地址:https://github.com/QQabby/HorizontalRuler 原效果为 因为跟自己要使用的view稍有不同  所以 ...

  6. Android开发进阶——自定义View的使用及其原理探索

    在Android开发中,系统提供给我们的UI控件是有限的,当我们需要使用一些特殊的控件的时候,只靠系统提供的控件,可能无法达到我们想要的效果,这时,就需要我们自定义一些控件,来完成我们想要的效果了.下 ...

  7. Android中实现自定义View组件并使其能跟随鼠标移动

    场景 实现效果如下 注: 博客: https://blog.csdn.net/badao_liumang_qizhi 关注公众号 霸道的程序猿 获取编程相关电子书.教程推送与免费下载. 实现 新建An ...

  8. android开发之自定义View 详解 资料整理 小冰原创整理,原创作品。

    2019独角兽企业重金招聘Python工程师标准>>> /** * 作者:David Zheng on 2015/11/7 15:38 * * 网站:http://www.93sec ...

  9. android开发学习 ------- 自定义View 圆 ,其点击事件 及 确定当前view的层级关系

    我需要实现下面的效果:   参考文章:https://blog.csdn.net/halaoda/article/details/78177069 涉及的View事件分发机制 https://www. ...

随机推荐

  1. 收藏的博客--PHP

    32位Win7下安装与配置PHP环境(一至三)  http://blog.csdn.net/yousuosi/article/details/9448903

  2. 过期邮件替换SQL

  3. UITableView全面解析,讲的好详细

    --UIKit之UITableView 概述 在iOS开发中UITableView可以说是使用最广泛的控件,我们平时使用的软件中到处都可以看到它的影子,类似于微信.QQ.新浪微博等软件基本上随处都是U ...

  4. swift系统学习第二章

    第五节:可选类型 optional //: Playground - noun: a place where people can play import UIKit /* Swift学习第五节 可选 ...

  5. 夜黑风高的夜晚用SQL语句做了一些想做的事·······

         IT这条漫漫长路注定是孤独的,陪伴我们的只有那些不知冷暖的代码语句和被手指敲打的磨掉了键上的标识的键盘. 之所以可以继续坚持下去,是因为心中有一份永不熄灭的激情. 成功的路上让我们为自己带盐 ...

  6. html5zero 网站模板 影片素材

    1. http://www.html5zero.com/ HTML5 Zero 收录来自各个网站的网站模版资源,支持响应式网页设计,部分能直接套用于 WordPress.Bootstrap 外,有 M ...

  7. List<T>转换为DataTable

    List<info> infos = Dal.GetInfos(); DataTable dt = new DataTable(); dt.Columns.Add("cName& ...

  8. C#: .net序列化及反序列化 [XmlElement(“节点名称”)]

    .net序列化及反序列化 序列化是指一个对象的实例可以被保存,保存成一个二进制串,当然,一旦被保存成二进制串,那么也可以保存成文本串了.比如,一个计数器,数值为2,我们可以用字符串“2”表示.如果有个 ...

  9. DirectX游戏编程入门

    刚开始学习D3D,安装完DirectX9后,在VS2008中新建Win32项目· ----------------------------------------------------------- ...

  10. alt text 与 tooltip区别

    在做工具的Accessiblity测试时, 对于image对象,一直分不清它的alt属性与tooltip属性的区别与用法, 从网上查了下, 比较认同这样的观点: alt属性: alternative ...