项目中,有时候我们会有需要圆角,或者是圆形的ImageView,自身的ImageView不带啊,不像Button可以利用shape来简单的实现圆角啊。。啊。。不要急,小司机来和大家一起实现你的需求。
今天小司机就和大家来利用BitmapShader来实现圆角、圆形ImageView,
本篇博客将会继续按照自定义View四大步骤来写,将会直接继承ImageView结合BitmapShader。开始我们的旅途吧。
BitmapShader是Shader的子类,可以通过Paint.setShader(Shader shader)进行设置、
这里我们只关注BitmapShader,构造方法:
mBitmapShader = new BitmapShader(bitmap, TileMode.CLAMP, TileMode.CLAMP);
参数1:bitmap
参数2,参数3:TileMode;
TileMode的取值有三种:
如果大家给电脑屏幕设置屏保的时候,如果图片太小,可以选择重复、拉伸、镜像;
重复:就是横向、纵向不断重复这个bitmap
镜像:横向不断翻转重复,纵向不断翻转重复;
拉伸:这个和电脑屏保的模式应该有些不同,这个拉伸的是图片最后的那一个像素;
横向的最后一个横行像素,不断的重复,纵项的那一列像素,不断的重复;
现在大概明白了,BitmapShader通过设置给mPaint,然后用这个mPaint绘图时,就会根据你设置的TileMode,对绘制区域进行着色。
这里需要注意一点:就是BitmapShader是从你的画布的左上角开始绘制的,不在view的右下角绘制个正方形,它不会在你正方形的左上角开始。
values/attr.xml
<resources>
<declare-styleable name="RoundImageView">
<attr name="borderRadius" format="reference|dimension"/>
<attr name="type">
<enum name="circle" value="0"/>
<enum name="round" value="1"/>
attr>
declare-styleable>
resources>
基本都加了注释;然后在构造方法中获取了我们的自定义属性,以及部分变量的初始化。
public class RoundImageView extends ImageView {
private static final Bitmap.Config BITMAP_CONFIG = Bitmap.Config.ARGB_8888;
private static final int COLORDRAWABLE_DIMENSION = 2;
private int type; //picture type
public static final int TYPE_CIRCLE = 0;
public static final int TYPE_ROUND = 1;
private static final int BODER_RADIUS_DEFAULT = 10; //default rectange border radius
private int mBorderRadius;
private Paint mBitmapPaint;
private int mRadius; //circle radius
private Matrix mMatrix; //matrix for scale
private BitmapShader mBitmapShader; //bitmapshader
private int mWidth; //views width
private RectF mRoundRect;
public RoundImageView(Context context) {
this(context,null);
}
public RoundImageView(Context context, AttributeSet attrs) {
this(context, attrs,0);
}
public RoundImageView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray array = context.obtainStyledAttributes(attrs,
R.styleable.RoundImageView);
mBorderRadius = array.getDimensionPixelSize(
R.styleable.RoundImageView_borderRadius, (int) TypedValue
.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
BODER_RADIUS_DEFAULT, getResources()
.getDisplayMetrics()));// default is 10
type = array.getInt(R.styleable.RoundImageView_type, TYPE_CIRCLE);// circle default
array.recycle();
// init paint and matrix
mMatrix = new Matrix();
mBitmapPaint = new Paint();
mBitmapPaint.setAntiAlias(true);
}
代码如下:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
/**
* if the shape is circle ,choose the min in width and height
*/
if (type == TYPE_CIRCLE) {
mWidth = Math.min(getMeasuredWidth(), getMeasuredHeight());
mRadius = mWidth / 2;
setMeasuredDimension(mWidth, mWidth);
}
}
代码如下:
// init bitmapshader
private void initBitmapShader() {
Drawable drawable = getDrawable();
if (drawable == null) {
return;
}
Bitmap bitmap = bitmapToDrawable(drawable);
if (bitmap == null) {
invalidate();
return;
}
// use bitmap Aas a shader, is drawn in the specified area
mBitmapShader = new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
float scale = 1.0f;
if (type == TYPE_CIRCLE) {
// get the min of bitmap width or height
int bSize = Math.min(bitmap.getWidth(), bitmap.getHeight());
scale = mWidth * 1.0f / bSize;
} else if (type == TYPE_ROUND) {
if (!(bitmap.getWidth() == getWidth() && bitmap.getHeight() == getHeight())) {
/*If the width of the picture or the width of the view does not match
the width of the need to calculate the need to scale the scale; zoom picture
after the width and height, must be greater than the width of our view;
so we take a large value*/
scale = Math.max(getWidth() * 1.0f / bitmap.getWidth(),
getHeight() * 1.0f / bitmap.getHeight());
}
}
// shader transformation matrix, we mainly used here to zoom in or out
mMatrix.setScale(scale, scale);
// set the transformation matrix
mBitmapShader.setLocalMatrix(mMatrix);
// set shader
mBitmapPaint.setShader(mBitmapShader);
}
首先对drawable转化为我们的bitmap;
然后初始化mBitmapShader = new BitmapShader(bmp, TileMode.CLAMP, TileMode.CLAMP);
接下来,根据类型以及bitmap和view的宽高,计算scale;
关于scale的计算:
圆形时:取bitmap的宽或者高的小值作为基准,如果采用大值,缩放后肯定不能填满我们的圆形区域。然后,view的mWidth/bSize ; 得到的就是scale。
圆角时:因为设计到宽/高比例,我们分别getWidth() * 1.0f / bmp.getWidth() 和 getHeight() * 1.0f / bmp.getHeight() ;最终取大值,因为我们要让最终缩放完成的图片一定要大于我们的view的区域,有点类似centerCrop;
比如:view的宽高为10*20;图片的宽高为5*100 ; 最终我们应该按照宽的比例放大,而不是按照高的比例缩小;因为我们需要让缩放后的图片,自定大于我们的view宽高,并保证原图比例。
有了scale,就可以设置给我们的matrix;
然后使用mBitmapShader.setLocalMatrix(mMatrix);
最后将bitmapShader设置给paint。
关于drawable转bitmap的代码:
/**
* @param drawable used for convert Bitmap to Drawable
*/
private Bitmap bitmapToDrawable(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 (Exception e) {
e.printStackTrace();
return null;
}
}
最后在onDraw里面调用initBitmapShader(),然后进行绘制。
最后一步绘制了,范围,缩放都完成了,只剩下绘制了。
代码如下:
@Override
protected void onDraw(Canvas canvas) {
if (getDrawable() == null) {
return;
}
initBitmapShader();
if (type == TYPE_ROUND) {
canvas.drawRoundRect(mRoundRect, mBorderRadius, mBorderRadius,
mBitmapPaint);
} else // circle
{
canvas.drawCircle(mRadius, mRadius, mRadius, mBitmapPaint);
}
}
防止突发事件,用来存储他的状态
存储当前的type以及mBorderRadius
private static final String STATE_INSTANCE = "state_instance";
private static final String STATE_TYPE = "state_type";
private static final String STATE_BORDER_RADIUS = "state_border_radius";
@Override
protected Parcelable onSaveInstanceState()
{
Bundle bundle = new Bundle();
bundle.putParcelable(STATE_INSTANCE, super.onSaveInstanceState());
bundle.putInt(STATE_TYPE, type);
bundle.putInt(STATE_BORDER_RADIUS, mBorderRadius);
return bundle;
}
@Override
protected void onRestoreInstanceState(Parcelable state)
{
if (state instanceof Bundle)
{
Bundle bundle = (Bundle) state;
super.onRestoreInstanceState(((Bundle) state)
.getParcelable(STATE_INSTANCE));
this.type = bundle.getInt(STATE_TYPE);
this.mBorderRadius = bundle.getInt(STATE_BORDER_RADIUS);
} else
{
super.onRestoreInstanceState(state);
}
}
此自定义的ImageView,提供了两个方法,用于动态修改圆角大小和type
/*Provide the usual setting method*/
public void setBorderRadius(int borderRadius)
{
int pxVal = dp2px(borderRadius);
if (this.mBorderRadius != pxVal)
{
this.mBorderRadius = pxVal;
invalidate();
}
}
public void setType(int type)
{
if (this.type != type)
{
this.type = type;
if (this.type != TYPE_ROUND && this.type != TYPE_CIRCLE)
{
this.type = TYPE_CIRCLE;
}
requestLayout();
}
}
public int dp2px(int dpVal)
{
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
dpVal, getResources().getDisplayMetrics());
}
"1.0" encoding="utf-8"?>
"http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:custom="http://schemas.android.com/apk/res-auto"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="com.shanlovana.rcimageview.MainActivity">
<com.shanlovana.rcimageview.views.RoundImageView
android:id="@+id/roundone"
android:layout_width="100dp"
android:layout_height="100dp"
android:src="@drawable/damimi"
custom:type="circle"/>
<com.shanlovana.rcimageview.views.RoundImageView
android:id="@+id/roundtwo"
android:layout_marginTop="20dp"
android:layout_below="@+id/roundone"
android:layout_width="100dp"
android:layout_height="100dp"
android:src="@drawable/pangdi"
custom:type="round"/>
<com.shanlovana.rcimageview.views.RoundImageView
android:id="@+id/roundthree"
android:layout_marginTop="20dp"
android:layout_below="@+id/roundtwo"
android:layout_width="100dp"
android:layout_height="100dp"
custom:borderRadius="20dp"
android:src="@drawable/luozhengying"
custom:type="round"/>
"@+id/roundfour"
android:layout_marginTop="20dp"
android:layout_below="@+id/roundthree"
android:layout_width="100dp"
android:layout_height="100dp"
android:scaleType="fitXY"
android:src="@drawable/pangdi"
/>
存在bug和不足之处,欢迎指出,多谢。
Github地址:https://github.com/Shanlovana/RCImageView