自定义ImageView实现圆形图片

前言

一直想封装一个圆形图片的ImageView,正好这两天看见郭霖推送的文章,且讲的正好是我想学习的,于是马上把他的文章看了一遍(文章地址http://www.wtoutiao.com/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(TypedValue.COMPLEX_UNIT_DIP,dp , getResources().getDisplayMetrics());
    }

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

2 . onMeasure测量

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

    @Override
    protected 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充分的了解,请参考爱歌的神作: http://blog.csdn.net/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没有介绍,看下代码

 @Override
    protected 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="http://schemas.android.com/apk/res-auto"

在布局中引用属性:

<com.chen.demo.view.CircleImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@mipmap/a1"
        app:borderRadio="5dp"
        app:type="round"
        />

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

最后

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

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值