Android BitmapShader实现圆角、圆形ImageView

项目中,有时候我们会有需要圆角,或者是圆形的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

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值