Android 中CircleImageView自定义View(圆形头像)

1.自定义圆形控件github地址: https://github.com/hdodenhof/CircleImageView

主要的类:

package de.hdodenhof.circleimageview;
import edu.njupt.zhb.main.R;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.RectF;
import android.graphics.Shader;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.widget.ImageView;

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 = 1;

	private static final int DEFAULT_BORDER_WIDTH = 0;
	private static final int DEFAULT_BORDER_COLOR = Color.BLACK;

	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 mBitmap;
	private BitmapShader mBitmapShader;
	private int mBitmapWidth;
	private int mBitmapHeight;

	private float mDrawableRadius;
	private float mBorderRadius;

	private boolean mReady;
	private boolean mSetupPending;

  
  
  1.   /**
  2.      * 构造函数
  3.      */
public CircleImageView(Context context) { super(context); } public CircleImageView(Context context, AttributeSet attrs) { this(context, attrs, 0); } /通过obtainStyledAttributes 获得一组值赋给 TypedArray(数组) , 这一组值来自于res/values/attrs.xml中的name="CircleImageView"的declare-styleable中。 public CircleImageView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); super.setScaleType(SCALE_TYPE);
  1.    //通过TypedArray提供的一系列方法getXXXX取得我们在xml里定义的参数值;
  2.         // 获取边界的宽度
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);    //调用 recycle() 回收TypedArray,以便后面重用 a.recycle();  System.out.println("CircleImageView -- 构造函数");   mReady = true; if (mSetupPending) { setup(); mSetupPending = false; } } @Override public ScaleType getScaleType() { return SCALE_TYPE; } @Override public void setScaleType(ScaleType scaleType) { if (scaleType != SCALE_TYPE) { throw new IllegalArgumentException(String.format("ScaleType %s not supported.", scaleType)); } } @Override protected void onDraw(Canvas canvas) { if (getDrawable() == null) { return; } canvas.drawCircle(getWidth() / 2, getHeight() / 2, mDrawableRadius, mBitmapPaint); canvas.drawCircle(getWidth() / 2, getHeight() / 2, mBorderRadius, mBorderPaint); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); setup(); } public int getBorderColor() { return mBorderColor; } public void setBorderColor(int borderColor) { if (borderColor == mBorderColor) { return; } mBorderColor = borderColor; mBorderPaint.setColor(mBorderColor); invalidate(); } public int getBorderWidth() { return mBorderWidth; } public void setBorderWidth(int borderWidth) { if (borderWidth == mBorderWidth) { return; } mBorderWidth = borderWidth; setup(); }
    1. /**
    2.      * 以下四个函数都是
    3.      * 复写ImageView的setImageXxx()方法 
    4.      * 注意这个函数先于构造函数调用之前调用
    5.      * @param bm
    6.      */
    7. //四个函数都是获取图片Bitmap,调用setup()。
@Override public void setImageBitmap(Bitmap bm) { super.setImageBitmap(bm); mBitmap = bm; setup(); } @Override public void setImageDrawable(Drawable drawable) { super.setImageDrawable(drawable); mBitmap = getBitmapFromDrawable(drawable); setup(); } @Override public void setImageResource(int resId) { super.setImageResource(resId); mBitmap = getBitmapFromDrawable(getDrawable()); setup(); }

  
  
  1. @Override
  2.     public void setImageURI(Uri uri) {
  3.         super.setImageURI(uri);
  4.         mBitmap = getBitmapFromDrawable(getDrawable());
  5.         setup();
  6.     }
private Bitmap getBitmapFromDrawable(Drawable drawable) { if (drawable == null) { return null; } if (drawable instanceof BitmapDrawable) { return ((BitmapDrawable) drawable).getBitmap(); } try { Bitmap bitmap; if (drawable instanceof ColorDrawable) { bitmap = Bitmap.createBitmap(COLORDRAWABLE_DIMENSION, COLORDRAWABLE_DIMENSION, BITMAP_CONFIG); } else { 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); return bitmap; } catch (OutOfMemoryError e) { return null; } }
  1. //因为mReady默认值为false,所以第一次进这个函数的时候if语句为真进入括号体内
  2. //设置mSetupPending为true然后直接返回,后面的代码并没有执行。
private void setup() { if (!mReady) { mSetupPending = true; return; } if (mBitmap == null) { return; } 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(mBorderWidth, mBorderWidth, mBorderRect.width() - mBorderWidth, mBorderRect.height() - mBorderWidth); mDrawableRadius = Math.min(mDrawableRect.height() / 2, mDrawableRect.width() / 2); updateShaderMatrix(); invalidate(); } private void updateShaderMatrix() { float scale; float dx = 0; float dy = 0; mShaderMatrix.set(null); if (mBitmapWidth * mDrawableRect.height() > mDrawableRect.width() * mBitmapHeight) { scale = mDrawableRect.height() / (float) mBitmapHeight; dx = (mDrawableRect.width() - mBitmapWidth * scale) * 0.5f; } else { scale = mDrawableRect.width() / (float) mBitmapWidth; dy = (mDrawableRect.height() - mBitmapHeight * scale) * 0.5f; } mShaderMatrix.setScale(scale, scale); mShaderMatrix.postTranslate((int) (dx + 0.5f) + mBorderWidth, (int) (dy + 0.5f) + mBorderWidth); mBitmapShader.setLocalMatrix(mShaderMatrix); } }
自定义的属性:res/values/attrs.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="CircleImageView">
        <attr name="border_width" format="dimension" />
        <attr name="border_color" format="color" />
    </declare-styleable>
</resources>
使用时的布局文件:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
	xmlns:app="http://schemas.android.com/apk/res-auto"
	android:layout_width="match_parent"
	android:layout_height="match_parent"
	android:orientation="vertical" >
	<RelativeLayout
		android:layout_width="match_parent"
		android:layout_height="0dp"
		android:layout_weight="1"
		android:padding="@dimen/base_padding"
		android:background="@color/light">

		<de.hdodenhof.circleimageview.CircleImageView
			android:layout_width="160dp"
			android:layout_height="160dp"
			android:layout_centerInParent="true"
			android:src="@drawable/demo"
			app:border_width="2dp"
			app:border_color="@color/dark" />

	</RelativeLayout>

	<RelativeLayout
		android:layout_width="match_parent"
		android:layout_height="0dp"
		android:layout_weight="1"
		android:padding="@dimen/base_padding"
		android:background="@color/dark">

		<de.hdodenhof.circleimageview.CircleImageView
			android:layout_width="160dp"
			android:layout_height="160dp"
			android:layout_centerInParent="true"
			android:src="@drawable/lena"
			app:border_width="2dp"
			app:border_color="@color/light" />

	</RelativeLayout>

</LinearLayout>
效果:

注意:一定要引入命名空间,xmlns:app=”http://schemas.android.com/apk/res-auto”,不然无法使用自定义属性。

总结下CircleImageView的使用:
  1. 将CircleImageView.java拷贝到项目工程中

  2. attrs.xml拷贝到res/values/目录下

  3. 在布局文件中声明自定义View,一定要引入命名空间xmlns:app=”http://schemas.android.com/apk/res-auto”。

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()开始。 
blob.png


  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Android Studio实现圆形头像的方法有以下几种: 1. 使用ImageView圆形裁剪功能 在布局文件添加一个ImageView,并设置其src属性为头像图片,然后设置其scaleType为centerCrop,最后使用android:background属性设置圆形背景即可实现圆形头像。 ``` <ImageView android:id="@+id/avatar" android:layout_width="100dp" android:layout_height="100dp" android:scaleType="centerCrop" android:src="@drawable/avatar" android:background="@drawable/circle"/> ``` 其circle.xml是一个圆形背景的drawable文件,代码如下: ``` <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval"> <solid android:color="#FFFFFF" /> </shape> ``` 2. 使用Glide库实现圆形头像 Glide是一款强大的图片加载库,可以方便地加载网络或本地图片,并提供了很多图片处理的功能,包括圆形裁剪。 在布局文件添加一个ImageView,并设置其src属性为空,然后在Java代码使用Glide加载头像图片,并使用Glide提供的Transformations.circleCrop()方法进行圆形裁剪,最后设置到ImageView即可。 ``` <ImageView android:id="@+id/avatar" android:layout_width="100dp" android:layout_height="100dp"/> Glide.with(this) .load("http://xxx.com/avatar.png") .apply(RequestOptions.circleCropTransform()) .into(imageView); ``` 3. 使用自定义View实现圆形头像 自定义View是一种更灵活、更可定制化的实现方式。可以继承View或其子类,然后在onDraw方法绘制头像图片,并使用canvas.drawCircle()方法绘制圆形背景。 ``` public class CircleImageView extends ImageView { private Paint mPaint; public CircleImageView(Context context) { super(context); init(); } public CircleImageView(Context context, AttributeSet attrs) { super(context, attrs); init(); } public CircleImageView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } private void init() { mPaint = new Paint(); mPaint.setAntiAlias(true); mPaint.setColor(Color.WHITE); } @Override protected void onDraw(Canvas canvas) { Drawable drawable = getDrawable(); if (drawable == null) { super.onDraw(canvas); return; } Bitmap bitmap = ((BitmapDrawable) drawable).getBitmap(); Bitmap circleBitmap = getCircleBitmap(bitmap); canvas.drawBitmap(circleBitmap, 0, 0, null); } private Bitmap getCircleBitmap(Bitmap bitmap) { Bitmap output = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(output); int width = bitmap.getWidth(); int height = bitmap.getHeight(); int radius = Math.min(width, height) / 2; canvas.drawCircle(width / 2, height / 2, radius, mPaint); mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN)); canvas.drawBitmap(bitmap, 0, 0, mPaint); return output; } } ``` 在布局文件使用自定义View即可。 ``` <com.example.CircleImageView android:id="@+id/avatar" android:layout_width="100dp" android:layout_height="100dp" android:src="@drawable/avatar"/> ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值