自己很少做自定义 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. JS 防止表单重复提交

    <script type="text/javascript"> var checkSubmitFlg = false; function checkSubmit() { ...

  2. 什么php?

    PHP是一种开源的通用计算机脚本语言,尤其适用于网络开发并可嵌入HTML中使用.PHP的语法借鉴吸收了C语言.Java和Perl等流行计算机语言的特点,易于一般程序员学习.PHP的主要目标是允许网络开 ...

  3. HTML的窗口分帧

    下面通过一个后台管理的部分设计来说明窗口分帧 frameset.html代码 <!-- <frameset>标签(常用来做后台管理界面) 属性:rows(行).cols(列).可以使 ...

  4. coreData旧版本增加字段,新版本是否可以继续使用旧版本内容的测试(MagicalRecord的使用)

    coreData使用第三方库MagicalRecord, 参考文章:http://blog.csdn.net/kuizhang1/article/details/21200367 coreData数据 ...

  5. translate居中

      <!doctype html>   <html>   <head>   <meta charset="UTF-8">   < ...

  6. 重学STM32----(一)

    在这学习stm32半年的时间中,虽然明显的感觉到自己在进步,但是还是发现学习方法的错误.由于急功近利的性格,在学习stm32之初,我选择了最简单的办法,用库函数来写程序,而且也由于我这急功近利的性格, ...

  7. Codeforces Round #308 (Div. 2) A B C 水 数学

    A. Vanya and Table time limit per test 2 seconds memory limit per test 256 megabytes input standard ...

  8. C特殊浮点值NaN

    特殊浮点值NaN(Not-a-Number),例如asin()函数返回反正弦值,所以输入参数不能大于1,否则函数返回NaN值,printf()显示为nan,NaN或类似形式.

  9. [USACO11JAN]利润Profits

    题目描述 The cows have opened a new business, and Farmer John wants to see how well they are doing. The ...

  10. Codeforces Round #150 (Div. 2)

    A. Dividing Orange 模拟. B. Undoubtedly Lucky Numbers 暴力枚举\(x.y\). C. The Brand New Function 固定左端点,右端点 ...