华为手机css 圆形头像像真讨厌

3556人阅读
Android开源代码学习(2)
转载请注明出处:
本文分为三大部分:
CircleImageView的使用
CircleImageView源码分析
Android自定义View总结
CircleImageView项目源码下载:
打开源码会发现主要就是一个继承了ImageView 的类——CircleImageView .java,代码优雅精致,效果很nice。下面会进行源码分析,让我加深了不少Canvas、BitmapShader、Matrix相关知识。
CircleImageView的使用
我这里一步步展示使用步骤,记录下当时遇到的问题。
按照上面的地址下载,项目文件名CircleImageView-master。目录下的circleimageview为库文件,sample为官方demo,使用fragment比较繁琐。这里我自己新建demo学会使用。
新建一个Android项目CircleImageViewDemo_1,复制上一步circleimageview文件夹里的CircleImageView.java到CircleImageViewDemo_1中。
CircleImageView.java地址:
CircleImageView-master\circleimageview\src\main\java\de\hdodenhof\circleimageview
项目src中:
此时会发现CircleImageView.java里报错,出错为没有R.styleable.CircleImageView。这是因为我们缺少自定义View的属性attrs.xml。在CircleImageView-master中找到attrs.xml并复制到项目里的res/values下。错误消失。
attrs.xml地址:
CircleImageView-master\circleimageview\src\main\res\values
&?xml version="1.0" encoding="utf-8"?&
name="CircleImageView"&
name="border_width" format="dimension" /&
name="border_color" format="color" /&
name="border_overlay" format="boolean" /&
4 接下来就要在activity_main.xml里使用CircleImageView了。我仿照官方demo进行layout布局,同时引用官方图片。
<pre class="prettyprint prettyprinted" data-original-code="
" data-snippet-id="ext.d6f0adf72b27f8ad0f199" data-snippet-saved="false" data-codota-status="done"> xmlns:android="/apk/res/android"
xmlns:tools="/tools"
xmlns:app="/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.example.circleimageviewdemo_1.MainActivity" &
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:padding="@dimen/base_padding"
android:background="@color/light"&
android:layout_width="160dp"
android:layout_height="160dp"
android:layout_centerInParent="true"
android:src="@drawable/hugh"
app:border_width="2dp"
app:border_color="@color/dark" /&
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:padding="@dimen/base_padding"
android:background="@color/dark"&
android:layout_width="160dp"
android:layout_height="160dp"
android:layout_centerInParent="true"
android:src="@drawable/hugh"
app:border_width="2dp"
app:border_color="@color/light" /&
注意两点:
在xml布局文件中,声明我们的自定义View,路径要正确。
com.example.library.CircleImageView
一定要引入命名空间,xmlns:app=”/apk/res-auto”,不然无法使用自定义属性。
MainActivity里不用修改,因为我直接在XML里面用src设置了图片,你也可以在MainActivity中用setImageXXX设置。直接运行看效果。
总结下CircleImageView的使用:
将CircleImageView.java拷贝到项目工程中
attrs.xml拷贝到res/values/目录下
在布局文件中声明自定义View,一定要引入命名空间xmlns:app=”/apk/res-auto”。
Demo下载:
CircleImageView源码分析
一直很好奇这个开源库是怎么自定义圆形ImageView,结果最近花了很多功夫进行分析,现在也非常迫切想整理出来,希望能帮到一些人。我也看了网络上的一些源码分析,有些分析是错误的会误导大家,有些说的不详细还是无法理解。
首先我先总结下CircleImageView的主要流程,让大家有个整体把握。然后再一步步进入源码分析。
CircleImageView的主要流程:
1. 首先通过setImageXxx()方法设置图片Bitmap;
2. 进入构造函数CircleImageView()获取自定义参数,以及调用setup()函数;
3. 进入setup()函数(非常关键),进行图片画笔边界画笔(Paint)一些重绘参数初始化:构建渲染器BitmapShader用Bitmap来填充绘制区域,设置样式和内外圆半径计算等,以及调用updateShaderMatrix()函数和 invalidate()函数;
4. 进入updateShaderMatrix()函数,计算缩放比例和平移,设置BitmapShader的Matrix参数等;
5. 触发ondraw()函数完成最终的绘制。使用配置好的Paint先画出绘制内圆形来以后再画边界圆形。
下面来详细分析一下源码:
1. 首先从项目里调用CircleImageView开始分析,我们要么在XML里面用src,要么调用CircleImageView的setImageXXX()方法设置图片。那我们的源码的运行入口在哪里呢,是从构造函数CircleImageView()开始呢还是从setImageXXX()开始?一开始就卡壳了。一开始我以为是从构造函数CircleImageView()开始跑,结果分析下来发现并不会进入setup();所以这是行不通的,那接下来就要论证是不是从setImageXXX()开始呢?我的方法是分别在两者进行System.out.println测试,看看谁先执行。测试结果会发现是从setImageXXX()开始。
那我们看下源码:
* 以下四个函数都是
* 复写ImageView的setImageXxx()方法
* 注意这个函数先于构造函数调用之前调用
public void setImageBitmap(Bitmap bm) {
super.setImageBitmap(bm);
public void setImageDrawable(Drawable drawable) {
super.setImageDrawable(drawable);
mBitmap = getBitmapFromDrawable(drawable);
System.out.println("setImageDrawable -- setup");
public void setImageResource(@DrawableRes int resId) {
super.setImageResource(resId);
mBitmap = getBitmapFromDrawable(getDrawable());
public void setImageURI(Uri uri) {
super.setImageURI(uri);
mBitmap = getBitmapFromDrawable(getDrawable());
看上面代码会发现,四个函数都是获取图片Bitmap,调用setup()。
接下来那我们查看setup()源码:
//因为mReady默认值为false,所以第一次进这个函数的时候if语句为真进入括号体内
//设置mSetupPending为true然后直接返回,后面的代码并没有执行。
if (!mReady) {
mSetupPending = true;
会发现第一次进入时,第一个if语句为真,进入括号体内设置mSetupPending为true然后直接返回,后面的代码并没有执行。为什么要这样设置mReady和mSetupPending呢不执行下面的代码呢,不要急,后面再解释。
2. 接下来我们会进入构造函数CircleImageView()。查看源码:
* 构造函数
public CircleImageView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CircleImageView, defStyle, 0);
mBorderWidth = a.getDimensionPixelSize(R.styleable.CircleImageView_border_width, DEFAULT_BORDER_WIDTH);
mBorderColor = a.getColor(R.styleable.CircleImageView_border_color, DEFAULT_BORDER_COLOR);
mBorderOverlay = a.getBoolean(R.styleable.CircleImageView_border_overlay, DEFAULT_BORDER_OVERLAY);
a.recycle();
System.out.println("CircleImageView -- 构造函数");
分析上面代码会发现很简单,就是首先通过TypedArray获取自定义参数,再调用init()函数:
* 作用就是保证第一次执行setup函数里下面代码要在构造函数执行完毕时调用
private void init() {
super.setScaleType(SCALE_TYPE);
mReady = true;
if (mSetupPending) {
mSetupPending = false;
因为在前面mSetupPending为真,则执行setup(),而且这里mReady 也为true,所以会执行setup()下面的代码。为什么要这样设置mSetupPending和mReady呢?
这是因为第一次进入setImageXXX()时候,我们无法获取自定义参数值,所以setup()下面的代码无法绘制我们想要的样式而是默认样式。而获取自定义参数只能在构造函数里,这样我们就能明白大神作者的意图了,通过设置mSetupPending和mReady控制第一次执行setup函数里下面代码要在构造函数执行完毕时。再者如果用户再进行setImageXXX()设置图片的话,就直接会执行setup()下面的代码,因为这之后mReady一直为true。
3 说了这么多,我们来看看setup()下面的代码到底是啥?没错,它就是这么酷炫,很关键:
上面代码注释我写的很详细不再一步步解释了,进行图片画笔边界画笔(Paint)一些重绘参数初始化:构建渲染器BitmapShader用Bitmap来填充绘制区域,设置样式以及内外圆半径计算等,以及调用updateShaderMatrix()函数和 invalidate()函数。
这里关于半径的计算,我画图举个例子:CircleImageView的布局宽高度均为160,边界的宽度为10如图所示:
那么去除边界宽度的内圆半径为70,带边界部分的外圆半径为75。
4 根据上面,接下来进入updateShaderMatrix()函数,查看源码:
通过updateShaderMatrix函数设置BitmapShader的Matrix参数,对图片mBitmap位置用何种缩放平移形式填充。
mDrawableRect.width() * mBitmapHeight写成(mBitmapWidth / mDrawableRect.width()) > (mBitmapHeight / mDrawableRect.height())更好理解。
" data-snippet-id="ext.18e88adf4b29d0e2e070a99" data-snippet-saved="false" data-csrftoken="Egkh30pJ-ZGJoIRsXqujirsBrGxNVXMcvJxo" data-codota-status="done">if判断语句里mBitmapWidth * mDrawableRect.height() & mDrawableRect.width() * mBitmapHeight写成(mBitmapWidth / mDrawableRect.width()) & (mBitmapHeight / mDrawableRect.height())更好理解。
目的是用最小的缩放比例,使得图片的某个方向的边的尺寸缩放到图片显示区域(mDrawableRect)一样。做到了图片损失度最小。同时scale保证Bitmap的宽或高和目标区域一致,那么高或宽就需要进行位移,使得Bitmap居中。这里写得很nice。
5 现在万事俱备,只欠ondraw()了。接着上面再setup()最后会调用invalidate()函数触发ondraw()函数完成最终的绘制。查看源码:
protected void onDraw(Canvas canvas) {
if (getDrawable() == null) {
canvas.drawCircle(getWidth() / 2, getHeight() / 2, mDrawableRadius, mBitmapPaint);
if (mBorderWidth != 0) {
canvas.drawCircle(getWidth() / 2, getHeight() / 2, mBorderRadius, mBorderPaint);
使用配置好的mBitmapPaint和mBorderPaint先画出绘制内圆形来以后再画边界圆形。源码还有一些自定义设置样式函数,很简单。
大功告成,是不是觉得思路比较简单,精致干练。
我总结下源码的精致之处:
流程控制的比较严谨,比如setup函数的使用
updateShaderMatrix保证图片损失度最小和始终绘制图片正中央的那部分
作者思路是画圆用渲染器位图填充,而不是把Bitmap重绘切割成一个圆形图片。
附上完整源码:
package com.example.
import com.example.circleimageviewdemo_1.R;
import android.content.C
import android.content.res.TypedA
import android.graphics.B
import android.graphics.BitmapS
import android.graphics.C
import android.graphics.C
import android.graphics.ColorF
import android.graphics.M
import android.graphics.P
import android.graphics.RectF;
import android.graphics.S
import android.graphics.drawable.BitmapD
import android.graphics.drawable.ColorD
import android.graphics.drawable.D
import android.net.U
import android.support.annotation.ColorR
import android.support.annotation.DrawableR
import android.util.AttributeS
import android.widget.ImageV
* 流程控制的比较严谨,比如setup函数的使用
* updateShaderMatrix保证图片损失度最小和始终绘制图片正中央的那部分
* 作者思路是画圆用渲染器位图填充,而不是把Bitmap重绘切割成一个圆形图片。
public class CircleImageView extends ImageView {
private static final ScaleType SCALE_TYPE = ScaleType.CENTER_CROP;
private static final Bitmap.Config BITMAP_CONFIG = Bitmap.Config.ARGB_8888;
private static final int COLORDRAWABLE_DIMENSION = 2;
private static final int DEFAULT_BORDER_WIDTH = 0;
private static final int DEFAULT_BORDER_COLOR = Color.BLACK;
private static final boolean DEFAULT_BORDER_OVERLAY = false;
private final RectF mDrawableRect = new RectF();
private final RectF mBorderRect = new RectF();
private final Matrix mShaderMatrix = new Matrix();
private final Paint mBitmapPaint = new Paint();
private final Paint mBorderPaint = new Paint();
private int mBorderColor = DEFAULT_BORDER_COLOR;
private int mBorderWidth = DEFAULT_BORDER_WIDTH;
private Bitmap mB
private BitmapShader mBitmapS
private int mBitmapW
private int mBitmapH
private float mDrawableR
private float mBorderR
private ColorFilter mColorF
private boolean mR
private boolean mSetupP
private boolean mBorderO
public CircleImageView(Context context) {
super(context);
public CircleImageView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
* 构造函数
public CircleImageView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CircleImageView, defStyle, 0);
mBorderWidth = a.getDimensionPixelSize(R.styleable.CircleImageView_border_width, DEFAULT_BORDER_WIDTH);
mBorderColor = a.getColor(R.styleable.CircleImageView_border_color, DEFAULT_BORDER_COLOR);
mBorderOverlay = a.getBoolean(R.styleable.CircleImageView_border_overlay, DEFAULT_BORDER_OVERLAY);
a.recycle();
System.out.println("CircleImageView -- 构造函数");
* 作用就是保证第一次执行setup函数里下面代码要在构造函数执行完毕时调用
private void init() {
super.setScaleType(SCALE_TYPE);
mReady = true;
if (mSetupPending) {
mSetupPending = false;
public ScaleType getScaleType() {
return SCALE_TYPE;
* 这里明确指出 此种imageview 只支持CENTER_CROP 这一种属性
* scaleType
public void setScaleType(ScaleType scaleType) {
if (scaleType != SCALE_TYPE) {
throw new IllegalArgumentException(String.format("ScaleType %s not supported.", scaleType));
public void setAdjustViewBounds(boolean adjustViewBounds) {
if (adjustViewBounds) {
throw new IllegalArgumentException("adjustViewBounds not supported.");
protected void onDraw(Canvas canvas) {
if (getDrawable() == null) {
canvas.drawCircle(getWidth() / 2, getHeight() / 2, mDrawableRadius, mBitmapPaint);
if (mBorderWidth != 0) {
canvas.drawCircle(getWidth() / 2, getHeight() / 2, mBorderRadius, mBorderPaint);
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
public int getBorderColor() {
return mBorderC
public void setBorderColor(int borderColor) {
if (borderColor == mBorderColor) {
mBorderColor = borderC
mBorderPaint.setColor(mBorderColor);
invalidate();
public void setBorderColorResource(@ColorRes int borderColorRes) {
setBorderColor(getContext().getResources().getColor(borderColorRes));
public int getBorderWidth() {
return mBorderW
public void setBorderWidth(int borderWidth) {
if (borderWidth == mBorderWidth) {
mBorderWidth = borderW
public boolean isBorderOverlay() {
return mBorderO
public void setBorderOverlay(boolean borderOverlay) {
if (borderOverlay == mBorderOverlay) {
mBorderOverlay = borderO
* 以下四个函数都是
* 复写ImageView的setImageXxx()方法
* 注意这个函数先于构造函数调用之前调用
public void setImageBitmap(Bitmap bm) {
super.setImageBitmap(bm);
public void setImageDrawable(Drawable drawable) {
super.setImageDrawable(drawable);
mBitmap = getBitmapFromDrawable(drawable);
System.out.println("setImageDrawable -- setup");
public void setImageResource(@DrawableRes int resId) {
super.setImageResource(resId);
mBitmap = getBitmapFromDrawable(getDrawable());
public void setImageURI(Uri uri) {
super.setImageURI(uri);
mBitmap = getBitmapFromDrawable(getDrawable());
public void setColorFilter(ColorFilter cf) {
if (cf == mColorFilter) {
mColorFilter =
mBitmapPaint.setColorFilter(mColorFilter);
invalidate();
* Drawable转Bitmap
* drawable
private Bitmap getBitmapFromDrawable(Drawable drawable) {
if (drawable == null) {
return null;
if (drawable instanceof BitmapDrawable) {
return ((BitmapDrawable) drawable).getBitmap();
if (drawable instanceof ColorDrawable) {
bitmap = Bitmap.createBitmap(COLORDRAWABLE_DIMENSION, COLORDRAWABLE_DIMENSION, BITMAP_CONFIG);
bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), BITMAP_CONFIG);
Canvas canvas = new Canvas(bitmap);
drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
drawable.draw(canvas);
} catch (OutOfMemoryError e) {
return null;
* 这个函数很关键,进行图片画笔边界画笔(Paint)一些重绘参数初始化:
* 构建渲染器BitmapShader用Bitmap来填充绘制区域,设置样式以及内外圆半径计算等,
* 以及调用updateShaderMatrix()函数和 invalidate()函数;
private void setup() {
if (!mReady) {
mSetupPending = true;
if (mBitmap == null) {
mBitmapShader = new BitmapShader(mBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
mBitmapPaint.setAntiAlias(true);
mBitmapPaint.setShader(mBitmapShader);
mBorderPaint.setStyle(Paint.Style.STROKE);
mBorderPaint.setAntiAlias(true);
mBorderPaint.setColor(mBorderColor);
mBorderPaint.setStrokeWidth(mBorderWidth);
mBitmapHeight = mBitmap.getHeight();
mBitmapWidth = mBitmap.getWidth();
mBorderRect.set(0, 0, getWidth(), getHeight());
mBorderRadius = Math.min((mBorderRect.height() - mBorderWidth) / 2, (mBorderRect.width() - mBorderWidth) / 2);
mDrawableRect.set(mBorderRect);
if (!mBorderOverlay) {
mDrawableRect.inset(mBorderWidth, mBorderWidth);
mDrawableRadius = Math.min(mDrawableRect.height() / 2, mDrawableRect.width() / 2);
updateShaderMatrix();
invalidate();
* 这个函数为设置BitmapShader的Matrix参数,设置最小缩放比例,平移参数。
* 作用:保证图片损失度最小和始终绘制图片正中央的那部分
private void updateShaderMatrix() {
float dx = 0;
float dy = 0;
mShaderMatrix.set(null);
if (mBitmapWidth * mDrawableRect.height() & mDrawableRect.width() * mBitmapHeight) {
scale = mDrawableRect.height() / (float) mBitmapH
dx = (mDrawableRect.width() - mBitmapWidth * scale) * 0.5f;
scale = mDrawableRect.width() / (float) mBitmapW
dy = (mDrawableRect.height() - mBitmapHeight * scale) * 0.5f;
mShaderMatrix.setScale(scale, scale);
mShaderMatrix.postTranslate((int) (dx + 0.5f) + mDrawableRect.left, (int) (dy + 0.5f) + mDrawableRect.top);
mBitmapShader.setLocalMatrix(mShaderMatrix);
Android自定义View总结
1. 自定义View的属性
2. 继承View,重写构造函数,获取我们自定义的属性值
3. 重写onMesure()方法
4. 重写onDraw()方法
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:92637次
积分:2487
积分:2487
排名:第10742名
原创:113篇
评论:69条
文章:33篇
阅读:12336
文章:10篇
阅读:8906
(5)(6)(19)(29)(10)(19)(16)(2)(2)(3)(2)(2)(1)(1)(1)(2)(2)(2)为什么华为手机能上微信,但进不了朋友圈,且微友头像全为黑影子?_百度知道平板/笔记本
华为帐号怎么修改头像啊
&新学乍练&
来自:浏览器
如题,一直不知道在哪儿修改,有没有大神说下
width:100%">
&花粉版主&
来自:浏览器
按照以下顺序来,头像可以是gif或者jepg格式均可
1.进入设置
1.png (23.31 KB, 下载次数: 9)
12:49 上传
2.点击修改头像
2.png (22.01 KB, 下载次数: 13)
12:49 上传
3.准备选择你准备更换的头像图片
3.png (14.98 KB, 下载次数: 14)
12:49 上传
4.找到该图片
4.png (16.79 KB, 下载次数: 11)
12:49 上传
5.点击保存后,刷新即可
5.png (10.71 KB, 下载次数: 9)
12:49 上传
width:100%">
&神功盖世&
来自:浏览器
楼上热心的花粉已经很详细解答了!
width:100%">
&新学乍练&
来自:浏览器
谢谢大神们,我知道了
width:100%">
&新学乍练&
来自:浏览器
求手机的啊
width:100%">
&新学乍练&
来自:浏览器
手机的 头像上传了,还是变成空白
width:100%">
&新学乍练&
来自:浏览器
手机上怎么上传头像后,还是空白咯
width:100%">
1000万花粉
纪念花粉俱乐部注册花粉数超过1000万
应用分享达人
应用资源合集不得少于3个(合集内资源量不得少于10个);应用资源帖不得少于30个;应用资源下载总量不低于2000
好基友勋章
花粉好机友,注册时间大于99天
在职斑竹的身份勋章,感谢斑竹的辛勤劳动
花粉女生专属勋章
至少3个原创技术帖,每个帖≥5K浏览,有效回复数≥150,被加分数≥15
花粉大贡献
精华帖数 ≥ 15 并且 主题数 & 200 并且 发帖数 & 500 并且 威望 & 500
关注华为花粉俱乐部微信公众平台——“华为花粉俱乐部”
随手拍达人
在“花粉随手拍”中发布优质随手拍即有机会获取勋章。
参与今日头条相关活动获得奖励
界面语言专区花粉勋章
参加过多次竞品测评,且输出的测评报告两次以上为优秀
参加花粉万圣狂欢会获得
【最美的你】【静美沼泽】【晨拍蛤蟆坝】【呼伦贝尔】【海边随拍】【光影习作】
花粉客户端
Make it Possible
Make your device special
华为云服务
Huawei cloud services
音乐播放器
Huawei Music
Huawei Vmall
没有最新动态
关注花粉俱乐部
联系我们:
|关注花粉俱乐部:
Copyright (C)
华为软件技术有限公司 版权所有 保留一切权利

我要回帖

更多关于 华为手机微信头像 的文章

 

随机推荐