ImageView在我们的项目中经常使用,一般ImageView是正方形的,要使用圆形的ImageView可以通过自定义View来实现,下面我们介绍一下如何实现。
1.CircularImageView.java 继承自ImageView:
package com.yayun.circularimageview; import com.mikhaellopez.circularimageview.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.Paint; import android.graphics.Shader; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.util.AttributeSet; import android.widget.ImageView; public class CircularImageView extends ImageView { private int borderWidth; private int canvasSize; private Bitmap image; private Paint paint; private Paint paintBorder; public CircularImageView(final Context context) { this(context, null); } public CircularImageView(Context context, AttributeSet attrs) { this(context, attrs, R.attr.circularImageViewStyle); } public CircularImageView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); // init paint paint = new Paint(); paint.setAntiAlias(true); paintBorder = new Paint(); paintBorder.setAntiAlias(true);// 抗锯齿 TypedArray attributes = context.obtainStyledAttributes(attrs, R.styleable.CircularImageView, defStyle, 0); /** * 有边框 */ if (attributes.getBoolean(R.styleable.CircularImageView_border, true)) { int defaultBorderSize = (int) (4 * getContext().getResources() .getDisplayMetrics().density + 0.5f);// 默认宽度 setBorderWidth(attributes.getDimensionPixelOffset( R.styleable.CircularImageView_border_width, defaultBorderSize));// 边框大小 setBorderColor(attributes.getColor( R.styleable.CircularImageView_border_color, Color.WHITE));// 边框颜色 } /** * 无边框 */ if (attributes.getBoolean(R.styleable.CircularImageView_shadow, false)) addShadow(); } /** * 设置边框宽度 * * @param borderWidth */ public void setBorderWidth(int borderWidth) { this.borderWidth = borderWidth; this.requestLayout(); this.invalidate(); } /** * 设置边框颜色 * * @param borderColor */ public void setBorderColor(int borderColor) { if (paintBorder != null) paintBorder.setColor(borderColor); this.invalidate(); } public void addShadow() { setLayerType(LAYER_TYPE_SOFTWARE, paintBorder); paintBorder.setShadowLayer(4.0f, 0.0f, 2.0f, Color.BLACK); } @Override public void onDraw(Canvas canvas) { // load the bitmap image = drawableToBitmap(getDrawable()); if (image != null) { canvasSize = canvas.getWidth(); if (canvas.getHeight() < canvasSize)// 取小值 canvasSize = canvas.getHeight(); BitmapShader shader = new BitmapShader(Bitmap.createScaledBitmap( image, canvasSize, canvasSize, false), Shader.TileMode.CLAMP, Shader.TileMode.CLAMP); paint.setShader(shader); /** * 圆心 */ int circleCenter = (canvasSize - (borderWidth * 2)) / 2; // void android.graphics.Canvas.drawCircle(float cx, float cy, float radius, Paint paint) canvas.drawCircle(circleCenter + borderWidth, circleCenter + borderWidth, ((canvasSize - (borderWidth * 2)) / 2) + borderWidth - 4.0f, paintBorder); canvas.drawCircle(circleCenter + borderWidth, circleCenter + borderWidth, ((canvasSize - (borderWidth * 2)) / 2) - 4.0f, paint); } } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int width = measureWidth(widthMeasureSpec); int height = measureHeight(heightMeasureSpec); setMeasuredDimension(width, height); } private int measureWidth(int measureSpec) { int result = 0; int specMode = MeasureSpec.getMode(measureSpec); int specSize = MeasureSpec.getSize(measureSpec); if (specMode == MeasureSpec.EXACTLY) { // The parent has determined an exact size for the child. result = specSize; } else if (specMode == MeasureSpec.AT_MOST) { // The child can be as large as it wants up to the specified size. result = specSize; } else { // The parent has not imposed any constraint on the child. result = canvasSize; } return result; } private int measureHeight(int measureSpecHeight) { int result = 0; int specMode = MeasureSpec.getMode(measureSpecHeight); int specSize = MeasureSpec.getSize(measureSpecHeight); if (specMode == MeasureSpec.EXACTLY) { // We were told how big to be result = specSize; } else if (specMode == MeasureSpec.AT_MOST) { // The child can be as large as it wants up to the specified size. result = specSize; } else { // Measure the text (beware: ascent is a negative number) result = canvasSize; } return (result + 2); } /** * 获取bitmap * * @param drawable * @return */ public Bitmap drawableToBitmap(Drawable drawable) { if (drawable == null) { return null; } else if (drawable instanceof BitmapDrawable) { return ((BitmapDrawable) drawable).getBitmap(); } Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(bitmap); drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight()); drawable.draw(canvas); return bitmap; } }
知识:一般来说,自定义控件都会去重写View的onMeasure方法,因为该方法指定该控件在屏幕上的大小。
protected void?onMeasure?(int widthMeasureSpec, int heightMeasureSpec)
onMeasure传入的两个参数是由上一层控件传入的大小,有多种情况,重写该方法时需要对计算控件的实际大小,然后调用setMeasuredDimension(int, int)设置实际大小。
onMeasure传入的widthMeasureSpec和heightMeasureSpec不是一般的尺寸数值,而是将模式和尺寸组合在一起的数值。我们需要通过int mode = MeasureSpec.getMode(widthMeasureSpec)得到模式,用int size =?MeasureSpec.getSize(widthMeasureSpec)得到尺寸。
mode共有三种情况,取值分别为
MeasureSpec.UNSPECIFIED, MeasureSpec.EXACTLY, MeasureSpec.AT_MOST。
MeasureSpec.EXACTLY是精确尺寸,当我们将控件的layout_width或layout_height指定为具体数值时如andorid:layout_width="50dip",或者为FILL_PARENT是,都是控件大小已经确定的情况,都是精确尺寸。
MeasureSpec.AT_MOST是最大尺寸,当控件的layout_width或layout_height指定为WRAP_CONTENT时,控件大小一般随着控件的子空间或内容进行变化,此时控件尺寸只要不超过父控件允许的最大尺寸即可。因此,此时的mode是AT_MOST,size给出了父控件允许的最大尺寸。
MeasureSpec.UNSPECIFIED是未指定尺寸,这种情况不多,一般都是父控件是AdapterView,通过measure方法传入的模式。
<?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name="CircularImageView"> <attr name="border" format="boolean"></attr> <attr name="border_width" format="dimension"></attr> <attr name="border_color" format="color"></attr> <attr name="shadow" format="boolean"></attr> </declare-styleable> <declare-styleable name="Theme"> <attr name="circularImageViewStyle" format="reference"></attr> </declare-styleable> </resources>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res/com.mikhaellopez.circularimageviewsample" android:layout_width="fill_parent" android:layout_height="fill_parent" android:gravity="center" android:orientation="vertical" > <com.yayun.circularimageview.CircularImageView android:layout_width="250dp" android:layout_height="250dp" android:src="@drawable/image" app:border="true" app:border_color="@color/Blue" app:border_width="14dp" app:shadow="true" /> <com.yayun.circularimageview.CircularImageView android:layout_width="200dp" android:layout_height="200dp" android:src="@drawable/image" /> </LinearLayout>
第一个图片有边框的效果,可以自行设置。
喜欢的朋友关注一下我和我的公众号(侧边栏),谢谢!
源码下载