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

来源:互联网 发布:dnd战士优化 编辑:程序博客网 时间:2024/06/05 20:28

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;}}@Overridepublic ScaleType getScaleType() {return SCALE_TYPE;}@Overridepublic void setScaleType(ScaleType scaleType) {if (scaleType != SCALE_TYPE) {throw new IllegalArgumentException(String.format("ScaleType %s not supported.", scaleType));}}@Overrideprotected void onDraw(Canvas canvas) {if (getDrawable() == null) {return;}canvas.drawCircle(getWidth() / 2, getHeight() / 2, mDrawableRadius, mBitmapPaint);canvas.drawCircle(getWidth() / 2, getHeight() / 2, mBorderRadius, mBorderPaint);}@Overrideprotected 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()。
@Overridepublic void setImageBitmap(Bitmap bm) {super.setImageBitmap(bm);mBitmap = bm;setup();}@Overridepublic void setImageDrawable(Drawable drawable) {super.setImageDrawable(drawable);mBitmap = getBitmapFromDrawable(drawable);setup();}@Overridepublic 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" ><RelativeLayoutandroid:layout_width="match_parent"android:layout_height="0dp"android:layout_weight="1"android:padding="@dimen/base_padding"android:background="@color/light"><de.hdodenhof.circleimageview.CircleImageViewandroid: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><RelativeLayoutandroid:layout_width="match_parent"android:layout_height="0dp"android:layout_weight="1"android:padding="@dimen/base_padding"android:background="@color/dark"><de.hdodenhof.circleimageview.CircleImageViewandroid: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


0 0
原创粉丝点击