Android自定义姓名头像

简介
  • WanAndroid项目中涉及到用户头像的生成,需求有以下几点:
    • 仿照探照灯效果
    • 头像含有用户姓名
复习View绘制流程
Android窗口机制

在这里插入图片描述

  • PhoneWindow:继承自Window类,负责管理界面显示以及事件响应,每个Activity 界面都包含一个PhoneWindow对象,它是Activity和整个View系统交互的接口。

  • DecorView:是PhoneWindow中的起始节点View,继承自View类,作为整个视图容器来使用的,主要负责设置窗口属性。

  • ViewRoot:在系统启动一个Activty组件的同时将其创建,类似于MVC模型中的Controller,负责管理、布局和渲染窗口UI等事务。

View绘制准备

在这里插入图片描述

  • 系统启动一个Activity的同时创建一个ViewRoot实例。Activity在attach阶段生成一个PhoneWindow对象,它包含一个DecorView对象。在Activity执行onCreate中的setContentView之后,将设置进来的view加载进入ContentViews区域。之后触发ViewRoot中的scheduleTraversals异步函数,从而进入ViewRoot的performTraversals函数,View的绘制从这里开始。
View绘制流程

在这里插入图片描述

  • View 的绘制流程是从ViewRoot 的 performTraversals 方法开始,它以DecorView为父容器开始自上而下的进行View绘制工作,它经过 measure 、 layout 和 draw 三个过程才能最终将一个 View 绘制出来,其中 measure 用来测量 View 的宽和高,layout 用来确定 View 在父容器中的放置位置,而 draw 则负责将 View 绘制在屏幕上
  • 注意
    • Measure 过程决定了 View 的宽/高, Measure 完成以后可以通过 getMeasuredWidthgetMeasuredHeight 方法来获取到 View 测量后的宽/高,在几乎所有的情况下它都等同于 View 最终的宽/高,但是特殊情况除外。
    • Layout 过程 决定了 View 的四个顶点的坐标和实际的 View 的宽/高,完成以后可以通过getTop、getBottom、getLeft、getRight 来拿到 View 的四个顶点的位置,并可以通过getWidth 和 getHeight 方法拿到 View 最终的宽/高。
    • Draw 过程则决定了 View 的显示,只有 draw 方法完成以后 View 的内容才能呈现在屏幕上。
探照灯效果
具体实现
发光效果
  • 绘制发光效果难度不大直接上代码
// 绘制发光效果
int color = getColor(R.color.always_white_text);
mPaintBackground.setColor(color);
mPaintBackground.setStyle(Paint.Style.FILL);
mPaintBackground.setMaskFilter(new BlurMaskFilter(10, BlurMaskFilter.Blur.SOLID));
canvas.drawCircle(getWidth() / 2, getWidth() / 2, (getWidth() - 20) / 2, mPaintBackground);
绘制头像文本
难点:垂直居中
  • 首先文字的绘制 参考【绘图入门

  • 在Android中绘制文字采用基线的方式,同时系统还会利用四条辅助线来辅助绘制文字,具体释义如下 :
    在这里插入图片描述

  • 注意:

    • 蓝线:基线
    • 红线:ascent线
    • 绿线:descent线
    • 黄线:top线
    • 黑线:bottom线
ascent:系统推荐的,在绘制单个字符的时,字符应当的最高高度所在线
descent:系统推荐的,在绘制单个字符时,字符应当的最低高度所在线
top:可绘制的最高高度所在线
bottom:可绘制的最低高度所在线

我们就尽量将文字绘制在系统推荐的最高和最低的限度之内,这样在不同的屏幕的手机上,文字都可以完整的展现出来

// 通过如下的方式计算出显示文字的区域
ascent线y坐标 = baseline线y坐标 + fontMetric.ascent
descent线 y坐标 = baseline线的y坐标 + fontMetric.descent
top线y坐标 = baseline线y坐标 + fontMetric.top
bottom线的y坐标 = baseline线y坐标 + fontMetric.bottom
  • 明白了如何绘制就来绘制垂直居中的文字吧
  • 公式:
 int baseLine = getHeight() / 2 - fontMetricsInt.descent  + (fontMetricsInt.bottom - fontMetricsInt.top) / 2;
对公式的理解
  • getHeight() / 2 - fontMetricsInt.descent
    • 将文本的bottom线抬高至控件的1/2处,效果图及数据如下
    • 在这里插入图片描述
getHeight() / 2: 270
baseLine:       252
ascent:        185.20312
descent:      269.57812
top:        175.95703
bottom:     271.51172

getHeight () /2 约等于desect 也约等于bottom, 即控件向上抬至控件1/2处
  • getHeight() / 2 - fontMetricsInt.descent + (fontMetricsInt.bottom - fontMetricsInt.top) / 2
    • 即将文字绘制在控件中央,文本的辅助线(top+bottom)/2就是文本的中位线(我是这样理解的)恰好在控件中位线处,即垂直居中。效果图及数据如下
    • 在这里插入图片描述
getHeight() / 2:  270
baseLine:          300
ascent:           233.20312
descent:           317.57812
top:              223.95703
bottom:          319.51172

getHeight() / 2 - fontMetricsInt.descent  + 
(fontMetricsInt.bottom - fontMetricsInt.top) / 2;

270 约等于现在的(top+bottom)/2, 即将文字绘制在控件中央
完整代码
@SuppressLint("AppCompatCustomView")
public class CustomUserAvatar extends ImageView {


    private Paint mPaintText;


    private Paint mPaintBackground;
    
    private Rect mRect;


    private String mUserName;


    public CustomUserAvatar(Context context) {
        super(context);
        init();
    }


    public CustomUserAvatar(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init();
    }


    public CustomUserAvatar(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }


    private void init() {
        setLayerType(LAYER_TYPE_SOFTWARE, null);
        mPaintText = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaintBackground = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaintBound = new Paint(Paint.ANTI_ALIAS_FLAG);
        mRect = new Rect();
    }


    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, widthMeasureSpec);
    }


    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        // 绘制发光效果
        int color = getColor(R.color.always_white_text);
        mPaintBackground.setColor(color);
        mPaintBackground.setStyle(Paint.Style.FILL);
        mPaintBackground.setMaskFilter(new BlurMaskFilter(10, BlurMaskFilter.Blur.SOLID));
        canvas.drawCircle(getWidth() / 2, getWidth() / 2, (getWidth() - 20) / 2, mPaintBackground);


        // 设置文本大小
        mPaintText.setTextSize(getWidth() / 3);
        // 设置文本颜色跟随应用主题颜色
        mPaintText.setColor(Constant.getColor(getContext()));
        // 设置画笔粗细
        mPaintText.setStrokeWidth(5);
        // 设置阴影半径
        mPaintText.setShadowLayer(5, 5, 5, getColor(R.color.black));
        // 绘制文字的最小矩形
        mPaintText.getTextBounds(mUserName, 0, 1, mRect);
        Paint.FontMetricsInt fontMetricsInt = mPaintText.getFontMetricsInt();
        // baseLine上面是负值,下面是正值
        // 所以getHeight()/2-fontMetricsInt.descent 将文本的bottom线抬高至控件的1/2处
        // + (fontMetricsInt.bottom - fontMetricsInt.top) / 2:(fontMetricsInt.bottom - fontMetricsInt.top) 文本的辅助线(top+bottom)/2就是文本的中位线(我是这样理解的)恰好在控件中位线处
        int baseLine = getHeight() / 2 - fontMetricsInt.descent + (fontMetricsInt.bottom - fontMetricsInt.top) / 2;
        // 水平居中
        mPaintText.setTextAlign(Paint.Align.CENTER);
        canvas.drawText(mUserName, getWidth() / 2, baseLine, mPaintText);
    }




    /**
     * 判断一个字符是否是中文
     */
    public boolean isChineseChar(char c) {
        // 根据字节码判断
        return c >= 0x4E00 && c <= 0x9FA5;
    }


    /**
     * 判断一个字符串是否含有中文
     *
     * @param str
     * @return
     */
    public boolean isChineseString(String str) {
        if (str == null) {
            return false;
        }
        for (char c : str.toCharArray()) {
            if (isChineseChar(c)) {
                return true;
            }
        }
        return false;
    }


    /**
     * 设置显示的名字
     *
     * @param userName
     */
    public void setUserName(String userName) {
        // 中文名字取后两个
        if (isChineseString(userName)) {
            if (userName.length() > 2) {
                mUserName = userName.substring(userName.length() - 2, userName.length());
            } else {
                mUserName = userName;
            }
        } else {
            // 非中文名字取第一个
            if (userName.length() > 1) {
                mUserName = userName.substring(0, 1);
                mUserName = mUserName.toUpperCase();
            } else {
                mUserName = userName;
                mUserName = mUserName.toUpperCase();
            }
        }
        invalidate();
    }
}



添加使用依赖

allprojects {
    repositories {
        ...
        maven { url 'https://jitpack.io' }
    }
}

引入项目

dependencies {
    implementation 'com.github.wangjianxiandev:CircularAvatar:v1.0.0'
}

使用方法

<com.wjx.android.lib_circularavatar.CustomUserAvatar
        android:id="@+id/avatar_one"
        android:layout_width="110dp"
        android:layout_height="110dp"
        <!--设置头像背景色-->
        app:background_color="@color/background_color"
        <!--设置填充样式 0-不填充 1-填充-->
        app:background_style="0"
        <!--设置字体颜色-->
        app:text_color="@color/black"
        <!--设置中文字符显示数目-->
        app:chinese_name="1"
        <!--设置英文字符显示数目-->
        app:english_name="2"
        <!--设置是否具有发光效果-->
        app:show_blur_Mask="false" />

展示

在这里插入图片描述

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

wjxbless

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值