300字范文,内容丰富有趣,生活中的好帮手!
300字范文 > 自定义ImageView实现圆形图片

自定义ImageView实现圆形图片

时间:2019-07-02 14:43:57

相关推荐

自定义ImageView实现圆形图片

前言

一直想封装一个圆形图片的ImageView,正好这两天看见郭霖推送的文章,且讲的正好是我想学习的,于是马上把他的文章看了一遍(文章地址/p/5f2wsQa.html),自己也重新实现了一遍。效果如下:

挺简单的两个效果,也挺实用,在项目中经常用,一个是方形图片的边框圆角,一个是圆形图片,实现逻辑不是很难,不过中间有些地方有点绕,待会慢慢解释

结构图

这是结构图:

大致要实现的效果就是右边那种,红色的Bitmap是经过处理缩放后的效果

自定义属性

要想自由控制是方形圆角(就叫Round)还是圆形(Circle)效果,所以需要一个自定义属性type,来通知ImageView分别绘制Round还是Circle,这是第一个。同时,圆角半径想要灵活输入,也需要一个自定义属性,故,两个自定义属性需要定义,看下代码:

<?xml version="1.0" encoding="utf-8"?><resources><attr name="borderRadio" format="dimension"/><attr name="type"><enum name="circle" value="0"/><enum name="round" value="1"/></attr><declare-styleable name="CircleImageView"><attr name="borderRadio" /><attr name="type" /></declare-styleable></resources>

代码中我们定义了borderRadio和type两个属性,其中type是枚举类型(贫道也是第一次用,额,尴尬。。。),有circle和round两个元素。borderRadio圆角半径, type控制是绘制方形还是圆形的标志,好了自定义属性就看到这里。

自定义ImageView

1 . 首先我们需要定义一个类继承ImageView,实现构造方法

public CircleImageView(Context context) {this(context, null, 0);}public CircleImageView(Context context, AttributeSet attrs) {this(context, attrs, 0);}public CircleImageView(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);mPaint = new Paint();//去锯齿效果mPaint.setAntiAlias(true);matrix = new Matrix();//获取自定义属性TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CircleImageView);mBorderRadio = typedArray.getDimensionPixelSize(R.styleable.CircleImageView_borderRadio,dp2px(DFAULT_ROUND_SIZE));type = typedArray.getInt(R.styleable.CircleImageView_type, CIRCLE); //默认画圆typedArray.recycle();}

我实现了三个构造方法,让一个的和两个的调用三个的构造方法,然后再第三个中实现ImageView的初始化方法,一切都是套路~~~。 初始化方法中,主要获取了自定义属性的值。matrix是一个3*3矩阵,用来实现图片的平移和缩放操作。dp2px()是一个工具方法,把dp转化为px,实现不同平台像素适配。代码为:

private int dp2px(int dp){return (int) TypedValue.applyDimension(PLEX_UNIT_DIP,dp , getResources().getDisplayMetrics());}

这样的效果网上有很多,其实还有别的写法可以实现,就不多说了。

2 . onMeasure测量

方形图片就用系统的测量模式,圆形的需要自己重新实现测量,代码如下:

@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {super.onMeasure(widthMeasureSpec, heightMeasureSpec);if(type == CIRCLE){circleWidth = Math.min(getMeasuredWidth(), getMeasuredHeight());mRadius = circleWidth / 2;setMeasuredDimension(circleWidth, circleWidth);}}

3 . onDraw绘制

先上代码:

if(getDrawable() == null){return;}setBitmapShader();if(type == CIRCLE){canvas.drawCircle(mRadius, mRadius, mRadius, mPaint);}else {canvas.drawRoundRect(recf, mBorderRadio, mBorderRadio, mPaint);}

首先判空,接下来这个setBitmapShader();什么意思? 好了,客官别急,稍好介绍,然后根据type进行圆形和方形图片的绘制。接下来看setBitmapShader()之前先来了解下BitmapShader。

BitmapShader

BitmapShader是Shader的子类,可以通过Paint.setShader(Shader shader)进行设置、

这里我们只关注BitmapShader,构造方法:

mBitmapShader = new BitmapShader(bitmap, TileMode.CLAMP, TileMode.CLAMP);

参数1:bitmap

参数2,参数3:TileMode;

TileMode的取值有三种:

CLAMP 拉伸

REPEAT 重复

MIRROR 镜像

如果大家给电脑屏幕设置屏保的时候,如果图片太小,可以选择重复、拉伸、镜像;

重复:就是横向、纵向不断重复这个bitmap

镜像:横向不断翻转重复,纵向不断翻转重复;

拉伸:这个和电脑屏保的模式应该有些不同,这个拉伸的是图片最后的那一个像素;横向的最后一个横行像素,不断的重复,纵项的那一列像素,不断的重复;

现在大概明白了,BitmapShader通过设置给mPaint,然后用这个mPaint绘图时,就会根据你设置的TileMode,对绘制区域进行着色。

这里需要注意一点:就是BitmapShader是从你的画布的左上角开始绘制的,不在view的右下角绘制个正方形,它不会在你正方形的左上角开始。

好了,到此,我相信大家对BitmapShader有了一定的了解了;当然了,如果你希望对Shader充分的了解,请参考爱歌的神作: /aigestudio/article/details/41799811

然后了解最后稍微有点复杂的setBitmapShader()

/*** 设置BitmapShader,渲染图像,使用图像为绘制图形着色*/private void setBitmapShader() {double scale = 1;float dx = 0, dy = 0;Bitmap bitmap = ((BitmapDrawable)getDrawable()).getBitmap();BitmapShader bitmapShader = new BitmapShader(bitmap,Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);//图片宽高int bitmapWidth = bitmap.getWidth();int bitmapHeight = bitmap.getHeight();//视图宽高int viewWidth = getWidth();int viewHeight = getHeight();if(type == CIRCLE){int bSize = Math.min(bitmapWidth, bitmapHeight);scale = circleWidth * 1.0 / bSize;}else {scale = Math.max(viewHeight * 1.0f / bitmapHeight, viewWidth * 1.0f / bitmapWidth);}if(bitmapWidth * viewHeight > bitmapHeight * viewWidth){dx = (float) ((viewWidth - bitmapWidth*scale)*0.5f);}else {dy = (float) ((viewHeight - bitmapHeight*scale)*0.5f);}matrix.setScale((float) scale, (float) scale);matrix.postTranslate(dx, dy);mPaint.setShader(bitmapShader);}

代码中首先将drawable转化为Bitmap,大家参考鸿洋大神这段代码也可以:

/** * drawable转bitmap * * @param drawable * @return */ private Bitmap drawableToBitamp(Drawable drawable) { if (drawable instanceof BitmapDrawable) { BitmapDrawable bd = (BitmapDrawable) drawable; return bd.getBitmap(); } int w = drawable.getIntrinsicWidth(); int h = drawable.getIntrinsicHeight(); Bitmap bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(bitmap); drawable.setBounds(0, 0, w, h); drawable.draw(canvas); return bitmap; }

我只是为了图简便(人懒啊~~~)。接下来,如果type是圆形就获取图片宽高中的小值,然后根据测量宽高获取scale = circleWidth * 1.0 / bSize; bSize取小值是为了让缩放后的图片要大于imageView宽高,否则绘制时,可能出现在平移图片后,部分ImageView空白,拉伸影响图片质量问题。type是方形的差不多也是这样的原因。然后是平移图片的逻辑,其中bitmapWidth * viewHeight > bitmapHeight * viewWidth用来判断是水平平移还是竖直平移,大家可以自行画图测试下(贫道也是想了半天不懂,画图画懂的)

中间黑色是ImageView如果bitmapWidth * viewHeight > bitmapHeight * viewWidth,那么bitmap就代表红色的方块,dx = (float) ((viewWidth - bitmapWidth*scale)*0.5f),就是平移dx,否则是绿色方块,就平移dy。大家不难发现sx,dy计算的都是负值,这两做的效果就是不让中间这块空白,否则拉伸图片。最后就是通过matrix平移和缩放图片。然后将bitmapShader赋给画笔进行相应的绘制。

还有一个recf没有介绍,看下代码

@Overrideprotected void onSizeChanged(int w, int h, int oldw, int oldh) {super.onSizeChanged(w, h, oldw, oldh);if(type == ROUND){recf = new RectF(0, 0 , getWidth(), getHeight());}}

很简单在onSizeChanged中实例化RectF并赋给recf,好了客官,最后整个过程就基本结束了。

使用

引入自定义属性的命名空间

xmlns:app="/apk/res-auto"

在布局中引用属性:

<com.chen.demo.view.CircleImageViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:src="@mipmap/a1"app:borderRadio="5dp"app:type="round"/><com.chen.demo.view.CircleImageViewandroid:layout_marginTop="20dp"android:layout_width="200dp"android:layout_height="200dp"android:src="@mipmap/a2"app:borderRadio="5dp"app:type="circle"/>

最后

啰嗦了半天,其实这些逻辑还是挺简单的,但是我记得我第一次看自定义圆形图片时没看懂,然后直接copy的,不得不说知识还是靠积累和沉淀的,不是一下就会有很大提高的,大神除外。这篇文章还只是很简单的自定义,还可以为其加上边框等效果,这些我以后整理好了,再重新发布一篇文章,今天就到这里。

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。