Android开源项目——自定义圆形图片组件CircularImageView
来源:互联网 发布:javascript 数组clear 编辑:程序博客网 时间:2024/06/08 02:32
项目github地址:点击打开链接
控件功能:可以将任意图片裁剪成圆形,控件的大小可以自定义,可以指定图片中心点和半径,也可以添加圆形边框并设置边框的颜色。
使用方法:和使用其他自定义控件没什么区别,这里只需要使用attr.xml中的属性和CircularImageView类文件即可。
下面我列举几种使用情况
1.设置控件大小和图片资源
<com.example.circularimageview.CircularImageView android:layout_width="200dp" android:layout_height="200dp" app:CircularImageSrc="@drawable/pic"/>效果如下:
2.设置控件大小,图片资源和圆心
<com.example.circularimageview.CircularImageView android:layout_width="200dp" android:layout_height="200dp" app:CenterScaleX="0.3" app:CenterScaleY="0.2" app:CircularImageSrc="@drawable/pic1"/>效果如下:
3.设置控件大小,图片资源,圆心以及半径比例
(注意半径比例不是指实际圆形图片的半径,而是在原始图片上裁剪区域的半径,与上面效果2比较即可看出差别,控件的实际半径我们一般在xml中通过layout_width/layout_height来设置。)
<com.example.circularimageview.CircularImageView android:layout_width="200dp" android:layout_height="200dp" app:CenterScaleX="0.3" app:CenterScaleY="0.4" app:CenterRadius="0.2" app:CircularImageSrc="@drawable/pic1"/>效果如下:
4.设置控件大小,图片资源和边界宽度和颜色
默认宽度是一个像素,颜色是黑色。
<com.example.circularimageview.CircularImageView android:layout_width="200dp" android:layout_height="200dp" app:BorderWidth="5dp" app:BorderColor="#DA70D6" app:CircularImageSrc="@drawable/pic"/>效果如下:
5.设置控件大小,图片资源和三层边界的宽度和颜色,默认颜色由内到外分别是黑,白,黑。
(个人感觉设置多层边界这个功能不太实用)
<com.example.circularimageview.CircularImageView android:layout_width="200dp" android:layout_height="200dp" app:InBorderWidth="2dp" app:BetweenWidth="3dp" app:OutBorderWidth="2dp" app:BetweenColor="#1265D6" app:InBorderColor="#645616" app:OutBorderColor="#D369D6" app:CircularImageSrc="@drawable/pic"/>效果如下:
6.设置控件大小,图片资源和两层边界的宽度和颜色
<com.example.circularimageview.CircularImageView android:layout_width="200dp" android:layout_height="200dp" app:InBorderWidth="5dp" app:BetweenWidth="8dp" app:BetweenColor="#1265D6" app:InBorderColor="#645616" app:CircularImageSrc="@drawable/pic"/>效果如下:
我们都知道在控件的使用大概分两种:xml中添加和在代码中动态添加,我也提供了这两种使用方式。
1.在xml中设置部分属性后,在代码中再设置或修改。(使用setImageBitmap来为控件设置新的Bitmap)
下面的例子我只设置了控件的大小,没有设置控件的图片资源,在代码中设置。
<com.example.circularimageview.CircularImageView android:id="@+id/image" android:layout_width="200dp" android:layout_height="200dp"/>CircularImageView image=(CircularImageView)findViewById(R.id.image);Bitmap bm=BitmapFactory.decodeResource(getResources(), R.drawable.pic4);image.setImageBitmap(bm);那么效果就是显示我们设置的Bitmap的圆形图片。
2.在代码中创建CircularImageView对象,然后在代码中添加到View。
Bitmap bm=BitmapFactory.decodeResource(getResources(), R.drawable.pic4);CircularImageView image1=new CircularImageView(this);image1.setImageBitmap(bm);MyLinearLayout linearLayout=new MyLinearLayout(this);linearLayout.addView(image1, new LayoutParams(400, 400));setContentView(linearLayout);这里的MyLinearLayout是一个我自定义的父容器,继承自LinearLayout,我把控件image1动态添加到了该容器中。那么效果也是按指定的大小显示我们设置的圆形图片。
在CirculatImageView的使用方法上就介绍到这里,下面我来详细描述该控件的实现过程。
CirculatImageView继承自View,重载了View的onMeasurce,onSizeChanged和onDraw函数,来自定义控件的测量和绘制。
CirculatImageView有如下属性,这些属性在attr.xml中设置。
<declare-styleable name="CircularImage"> <attr name="CircularImageSrc" format="reference"/> <attr name="BorderWidth" format="dimension"/> <attr name="InBorderWidth" format="dimension"/> <attr name="BetweenWidth" format="dimension"/> <attr name="OutBorderWidth" format="dimension"/> <attr name="BorderColor" format="color"/> <attr name="InBorderColor" format="color"/> <attr name="BetweenColor" format="color|reference"/> <attr name="OutBorderColor" format="color"/> <attr name="CenterScaleX" format="float"/> <attr name="CenterScaleY" format="float"/> <attr name="CenterRadius" format="float"/></declare-styleable>下面是具体属性的介绍:
1.圆形头像的属性CircularImageSrc,设置圆形头像的图片资源,我们在该图片的基础上处理得到圆形头像。
2.圆形头像周边可添加圆环,该圆环可以设置其宽度,该圆环包括三个区域:内圆InBorderWidth,环瓤BetweenWidth和外圆OutBorderWidth。,可以分别设置其宽度。圆环的总宽度在BorderWidth中设置。
在使用上,若只设置了圆环总宽度BorderWidth属性,那么只绘制环瓤,不会绘制内圆和外圆。如果未设置内圆宽InBorderWidth和外圆宽OutBorderWidth,那么环瓤BetweenWidth和BorderWidth的作用是一样的。若BetweenWidth和BorderWidth都设置了,使用BorderWidth的值,前者不会起作用的。若只设置了内圆宽InBorderWidth或外圆宽OutBorderWidth,即BetweenWidth和BorderWidth都未设置,那么不会绘制圆环。若只设置了环瓤的宽度和内圆宽度(或外圆宽度),也是会绘制圆环的,两层而已。
3.圆环颜色的设置分为内圆颜色InBorderColor,环瓤颜色BetweenColor和外圆颜色OutBorderColor,如果这三个属性都未设置,那么使用默认的BorderColor值。
4.在将原始图片转换成圆形图片时,可以指定圆形头像中心点,也可以指定半径,这样可以截取原始图片中某个区域用于绘制圆形图片。若不指定半径,那么头像的半径就由图片资源的width/height及中心点与原始图片边界最短距离来共同决定了。
首先来分析一下自定义控件的大小测绘的问题,这是每个自定义控件都会遇到的。
在该控件中有两个大小,一个是所设置原始图片的大小,另一个是该控件的实际大小,我们要做到肯定就是将原始图片进行缩放来满足控件的实际大小。
@Overridepublic void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {int resultWidth = 0;int modeWidth = MeasureSpec.getMode(widthMeasureSpec);int sizeWidth = MeasureSpec.getSize(widthMeasureSpec);if (modeWidth == MeasureSpec.EXACTLY) {resultWidth = sizeWidth;} else {if(srcBitmap!=null){// 如果为wrap_content,则比较圆形头像大小和父容器给的最大值sizeWidthint length = radius * 2;if (modeWidth == MeasureSpec.AT_MOST) {resultWidth = Math.min(sizeWidth, length);}}else{//动态添加resultWidth=sizeWidth;}}int resultHeight = 0;int modeHeight = MeasureSpec.getMode(heightMeasureSpec);int sizeHeight = MeasureSpec.getSize(heightMeasureSpec);if (modeHeight == MeasureSpec.EXACTLY) {resultHeight = sizeHeight;} else {if(srcBitmap!=null){// 如果为wrap_content,则比较圆形头像大小和父容器给的最大值sizeHeightint length = radius * 2;if (modeHeight == MeasureSpec.AT_MOST) {resultHeight = Math.min(sizeHeight, length);}}else{//动态添加resultHeight=sizeHeight;}}setMeasuredDimension(resultWidth, resultHeight);}因为在View大小测量中,当我们设置属性layout_width/layout_height的值是wrap_content时,默认的实现是充满父容器。这里我要实现的是,若设置为wrap_content,则控件的大小是原始图片的进过裁剪后的大小,不进行缩放。当我们在xml中设置为具体的值时,MeasureSpec.getMode等于MeasureSpec.EXACTLY,使控件大小即为所设置的值。
我们在绘制之前先在onSizeChanged中获得实际控件的大小。
@Overridepublic void onSizeChanged(int w, int h, int oldw, int oldh) {super.onSizeChanged(w, h, oldw, oldh);width = w;height = h;Log.i(TAG, "width:" + width);Log.i(TAG, "height:" + height);create();}public void create() {// 在此处获得裁剪后的BitmapdstBitmap = createDstBitmap();// 这里的scaleBitmap本该是圆形的,但可能在createScaledBitmap执行时被width,height拉伸变换if(dstBitmap!=null){scaleBitmap = Bitmap.createScaledBitmap(dstBitmap, width, height, true);}}在上述代码中,我将控件的实际大小保存到变量width,height中,然后执行create函数,该函数调用createDstBitmap来裁剪原始图片。先跳过原始图片的实现裁剪过程,得到裁剪后圆形位图dstBitmap。
scaleBitmap = Bitmap.createScaledBitmap(dstBitmap, width, height, true);//将位图缩放到控件的实际大小最后在onDraw中重绘即可。
@Overridepublic void onDraw(Canvas canvas) {super.onDraw(canvas);if(scaleBitmap!=null){canvas.drawBitmap(scaleBitmap, 0, 0, null);}}以上就是控件的大概实现过程,下面我们来看一下核心的圆形图片的裁剪过程(保存圆形遮罩和圆环边界两部分)。
1.使用图形混合模式来绘制圆形遮罩。
PorterDuff.Mode.DST_IN模式可以只显示图像重叠区域的下层,PorterDuff.Mode.SRC_IN模式可以只显示图像重叠区域的上层,如下图所示。
我们通过设置画笔的Paint的图形混合模式来裁剪出圆形区域,还涉及到Canvas中图层的应用。
// 通过内接矩形来创建圆形遮罩,public Bitmap createMask() {Bitmap bm = Bitmap.createBitmap(radius * 2, radius * 2,Bitmap.Config.ARGB_8888);Canvas canvas = new Canvas(bm);Paint paint = new Paint(1);paint.setColor(getResources().getColor(android.R.color.holo_blue_light));// 圆所内切的矩形RectF rectF = new RectF(0, 0, dstBitmap.getWidth(),dstBitmap.getHeight());canvas.drawArc(rectF, 0, 360, true, paint);return bm;}首先根据要绘制圆形区域的大小radius来创建存放圆形图片的Bitmap。然后使用Bitmap的Canvas来绘制矩形的内切圆。
这样就得到了一个圆形区域图像。在Canvas图层中将该Bitmap与原始Bitmap重叠以取出重叠区域图像。
// 获得画布后在Canvas进行裁剪int j = myCanvas.saveLayer(0, 0, radius * 2, radius * 2, null,Canvas.ALL_SAVE_FLAG);int x = centerX - radius;int y = centerY - radius;Bitmap bm = Bitmap.createBitmap(srcBitmap, x, y, radius * 2, radius * 2);myCanvas.drawBitmap(bm, 0, 0, mPaint);mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));myCanvas.drawBitmap(createMask(), 0, 0, mPaint);mPaint.setXfermode(null);myCanvas.restoreToCount(j);上述代码中,mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));来设置画笔的图形混合模式,myCanvas.drawBitmap(bm, 0, 0, mPaint);将得到的圆形区域图像绘制在位图上,那么canvas内部就会完成重叠区域的裁剪。我们的Bitmap bm就成了圆形的了。
2.环形边界的绘制(我这里大概描述绘制思路,至于内部的宽度,半径及圆心等计算就不详细陈述了)
这里是在上一步的基础上绘制的,思路和简单,就是直接将空心圆绘制在上面已经绘制好的Canvas上。
if (betweenWidth != 0 || borderWidth != 0) {mPaint.setStyle(Paint.Style.STROKE);myCanvas.drawBitmap(createBorder(), 0, 0, mPaint);}当然绘制环形边界的前提是设置了相应的边界宽度属性。
public Bitmap createBorder() {Bitmap bm = Bitmap.createBitmap(radius * 2, radius * 2,Bitmap.Config.ARGB_8888);Canvas canvas = new Canvas(bm);// 未设置内圆和外圆,只绘制环瓤,且该函数的被调用的前提是betweenWidth和borderWidth不全为0if (outBorderWidth == 0 && inBorderWidth == 0) {if (betweenWidth != 0 && borderWidth == 0) {borderWidth = betweenWidth;}// 使用borderWidth来画环,半径radius=realRadius-borderWidth/2borderPaint.setColor(borderColor);borderPaint.setStrokeWidth(borderWidth);canvas.drawCircle(radius, radius, radius - borderWidth / 2,borderPaint);return bm;}// 设置了内圆或外圆,且该函数的被调用的前提是betweenWidth和borderWidth不全为0if (outBorderWidth != 0 || inBorderWidth != 0) {// 内圆,环瓤和外圆都要绘制if (outBorderWidth != 0 && inBorderWidth != 0) {// 如果环瓤的宽度未设置,则用总宽度borderWidth减去外圆和内圆的宽度// 若设置了环瓤的宽度,那么总宽度borderWidth的限制就不会起作用了。if (betweenWidth == 0) {betweenWidth = borderWidth - inBorderWidth - outBorderWidth;}// 外圆的绘制borderPaint.setColor(outBorderColor);borderPaint.setStrokeWidth(outBorderWidth);canvas.drawCircle(radius, radius, radius - outBorderWidth / 2,borderPaint);// 环瓤的绘制borderPaint.setColor(betweenColor);borderPaint.setStrokeWidth(betweenWidth);canvas.drawCircle(radius, radius, radius - outBorderWidth- betweenWidth / 2, borderPaint);// 内圆的绘制borderPaint.setColor(inBorderColor);borderPaint.setStrokeWidth(inBorderWidth);// 宽为2canvas.drawCircle(radius, radius, radius - outBorderWidth- betweenWidth - inBorderWidth / 2, borderPaint);return bm;}// 只绘制环瓤和外圆if (outBorderWidth != 0 && inBorderWidth == 0) {if (betweenWidth == 0) {betweenWidth = borderWidth - outBorderWidth;}// 外圆的绘制borderPaint.setColor(outBorderColor);borderPaint.setStrokeWidth(outBorderWidth);canvas.drawCircle(radius, radius, radius - outBorderWidth / 2,borderPaint);// 环瓤的绘制borderPaint.setColor(betweenColor);borderPaint.setStrokeWidth(betweenWidth);canvas.drawCircle(radius, radius, radius - outBorderWidth- betweenWidth / 2, borderPaint);return bm;}// 只绘制环瓤和内圆if (outBorderWidth == 0 && inBorderWidth != 0) {if (betweenWidth == 0) {betweenWidth = borderWidth - inBorderWidth;}// 环瓤的绘制borderPaint.setColor(betweenColor);borderPaint.setStrokeWidth(betweenWidth);canvas.drawCircle(radius, radius, radius - betweenWidth / 2,borderPaint);// 内圆的绘制borderPaint.setColor(inBorderColor);borderPaint.setStrokeWidth(inBorderWidth);// 宽为2canvas.drawCircle(radius, radius, radius - betweenWidth- inBorderWidth / 2, borderPaint);return bm;}}return null;}在代码实现中针对不同的属性设置做出了很详细的分类讨论。
接口设计
我这里只设计了修改圆形图片位图资源的接口,后面如果有需要,我会添加一些其他接口,比如修改边界颜色和宽度等。
public void setImageBitmap(Bitmap bm){if(bm!=null){//重新测量和绘制srcBitmap=bm;init();super.requestLayout();invalidate();}}
把新的Bitmap赋值给srcBitmap,在init()函数中重新计算新的半径和圆心等值,然后通过调用View的requestLayout函数来使其重新测量measure和布局layout,然后调用invalidate进行重新绘制视图。
我记得好像在github上也有一个开源的圆形头像控件,但是它是通过继承ImageView实现的,且可拓展性也不好。
到这里整个自定义圆形图片控件就完成了,如果有需要改进的地方请各位提出,
- Android开源项目——自定义圆形图片组件CircularImageView
- CircularImageView 一 自定义圆形图片组件。
- Android自定义组件之圆形图片
- Android自定义圆形图片
- Android自定义圆形图片
- Android自定义圆形图片
- Android自定义圆形图片
- Android 自定义圆形图片
- Android自定义圆形图片
- [Android]自定义圆形图片
- Android自定义圆形图片
- Android自定义圆形图片
- android 自定义圆形图片
- android圆形图片,圆形背景文字的CircleTextImageView开源组件
- android圆形图片,圆形背景文字的CircleTextImageView开源组件
- android圆形图片,圆形背景文字的CircleTextImageView开源组件
- android 自定义组件圆形边框
- android 自定义圆形头像组件
- Subsets -- leetcode
- template放置位置错误导致backbone报错
- win10不能连接vpn解决方法
- 仿ios滚动 有弹性的ScrollView
- Proguard打包混淆报错:can't find superclass or interface
- Android开源项目——自定义圆形图片组件CircularImageView
- Linked List Cycle【leetcode】
- 数据结构与算法分析 C语言描述 单链表的实现
- Java自带的性能监测工具用法简介——jstack、jconsole、jinfo、jmap、jdb、jsta、jvisualvm
- sqlite数据查询速度慢的可能原因
- C/C++--strcpy函数实现
- 网络状态检测方法测试结果
- Java 局部内部类
- [LeetCode] Maximum Product Subarray