您的位置:首页 > 移动开发 > Android开发

Android BitmapShader实现圆角、圆形ImageView

2017-02-19 15:16 453 查看
项目中,有时候我们会有需要圆角,或者是圆形的ImageView,自身的ImageView不带啊,不像Button可以利用shape来简单的实现圆角啊。。啊。。不要急,小司机来和大家一起实现你的需求。

今天小司机就和大家来利用BitmapShader来实现圆角、圆形ImageView,



本篇博客将会继续按照自定义View四大步骤来写,将会直接继承ImageView结合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实战

1、自定义属性

values/attr.xml

<?xml version="1.0" encoding="utf-8"?>
<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>


2、代码中获取自定义属性

基本都加了注释;然后在构造方法中获取了我们的自定义属性,以及部分变量的初始化。

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);

}


3、重写onMeasure

代码如下:

@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);
}

}


4、初始化BitmapShader

代码如下:

// 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(),然后进行绘制。

5、重写onDraw()

最后一步绘制了,范围,缩放都完成了,只剩下绘制了。

代码如下:

@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);
}
}


6、状态的存储与恢复

防止突发事件,用来存储他的状态

存储当前的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());
}


7、使用方法

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="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"/>
<ImageView
android:id="@+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"
/>

</RelativeLayout>


存在bug和不足之处,欢迎指出,多谢。

Github地址:https://github.com/Shanlovana/RCImageView
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  imageview android