• Android源码分析--CircleImageView 源码详解


    源码地址为 https://github.com/hdodenhof/CircleImageView

    实际上就是一个圆形的imageview 的自定义控件。代码写的很优雅,实现效果也很好,

    特此分析。源码其实不难 主要就是一个类,可以把我的这个加了注释的源码放到你自己的工程里直接替换

    然后run,这样效果更佳。

      1 package de.hdodenhof.circleimageview;
      2 
      3 import android.content.Context;
      4 import android.content.res.TypedArray;
      5 import android.graphics.Bitmap;
      6 import android.graphics.BitmapShader;
      7 import android.graphics.Canvas;
      8 import android.graphics.Color;
      9 import android.graphics.ColorFilter;
     10 import android.graphics.Matrix;
     11 import android.graphics.Paint;
     12 import android.graphics.RectF;
     13 import android.graphics.Shader;
     14 import android.graphics.drawable.BitmapDrawable;
     15 import android.graphics.drawable.ColorDrawable;
     16 import android.graphics.drawable.Drawable;
     17 import android.net.Uri;
     18 import android.support.annotation.ColorRes;
     19 import android.support.annotation.DrawableRes;
     20 import android.util.AttributeSet;
     21 import android.util.Log;
     22 import android.widget.ImageView;
     23 
     24 /**
     25  * 实际上整体思路还是比较简单的,利用BitmapShader 来把imageview里的图片分割成圆形
     26  * 画出圆形来以后 再画描边。
     27  * 这个开源控件做的比较出色的地方是updateShaderMatrix 函数会帮忙做图片修正,使切割出来的图片损失度最小.
     28  * 此外就是各种情况考虑的比较多,流程控制的比较严谨,其中主要是多次调用setup函数 来完成imageview的及时刷新
     29  */
     30 public class CircleImageView extends ImageView {
     31 
     32     private static final ScaleType SCALE_TYPE = ScaleType.CENTER_CROP;
     33 
     34     private static final Bitmap.Config BITMAP_CONFIG = Bitmap.Config.ARGB_8888;
     35     private static final int COLORDRAWABLE_DIMENSION = 2;
     36 
     37     private static final int DEFAULT_BORDER_WIDTH = 0;
     38     private static final int DEFAULT_BORDER_COLOR = Color.BLACK;
     39     private static final boolean DEFAULT_BORDER_OVERLAY = false;
     40 
     41     private final RectF mDrawableRect = new RectF();
     42     private final RectF mBorderRect = new RectF();
     43 
     44     private final Matrix mShaderMatrix = new Matrix();
     45     //这个画笔最重要的是关联了mBitmapShader 使canvas在执行的时候可以切割原图片(mBitmapShader是关联了原图的bitmap的)
     46     private final Paint mBitmapPaint = new Paint();
     47     //这个描边,则与本身的原图bitmap没有任何关联,
     48     private final Paint mBorderPaint = new Paint();
     49 
     50     //这里定义了 圆形边缘的默认宽度和颜色
     51     private int mBorderColor = DEFAULT_BORDER_COLOR;
     52     private int mBorderWidth = DEFAULT_BORDER_WIDTH;
     53 
     54     private Bitmap mBitmap;
     55     private BitmapShader mBitmapShader;
     56     private int mBitmapWidth;
     57     private int mBitmapHeight;
     58 
     59     private float mDrawableRadius;
     60     private float mBorderRadius;
     61 
     62     private ColorFilter mColorFilter;
     63 
     64     /**
     65      * 初始值都為false
     66      */
     67     private boolean mReady;
     68     private boolean mSetupPending;
     69     private boolean mBorderOverlay;
     70 
     71     public CircleImageView(Context context) {
     72         super(context);
     73 
     74         init();
     75     }
     76 
     77     public CircleImageView(Context context, AttributeSet attrs) {
     78         this(context, attrs, 0);
     79     }
     80 
     81     public CircleImageView(Context context, AttributeSet attrs, int defStyle) {
     82         super(context, attrs, defStyle);
     83         Log.v("CircleImageView", "gou zao han shu");
     84         TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CircleImageView, defStyle, 0);
     85 
     86         //取得我们在xml里定义的参数值
     87         mBorderWidth = a.getDimensionPixelSize(R.styleable.CircleImageView_border_width, DEFAULT_BORDER_WIDTH);
     88         mBorderColor = a.getColor(R.styleable.CircleImageView_border_color, DEFAULT_BORDER_COLOR);
     89         mBorderOverlay = a.getBoolean(R.styleable.CircleImageView_border_overlay, DEFAULT_BORDER_OVERLAY);
     90 
     91         a.recycle();
     92 
     93         init();
     94     }
     95 
     96     /**
     97      * 这个函数 只在构造函数里面调用 他的作用就是 保证setup函数里的流程一定要在
     98      * 构造函数执行完毕的时候去调用 mReady为true setup函数里的代码才能向下执行
     99      */
    100     private void init() {
    101         Log.v("CircleImageView", "init()");
    102         super.setScaleType(SCALE_TYPE);
    103         mReady = true;
    104 
    105         if (mSetupPending) {
    106             setup();
    107             mSetupPending = false;
    108         }
    109     }
    110 
    111     @Override
    112     public ScaleType getScaleType() {
    113         return SCALE_TYPE;
    114     }
    115 
    116     /**
    117      * 这里明确指出 此种imageview 只支持CENTER_CROP 这一种属性
    118      *
    119      * @param scaleType
    120      */
    121     @Override
    122     public void setScaleType(ScaleType scaleType) {
    123         if (scaleType != SCALE_TYPE) {
    124             throw new IllegalArgumentException(String.format("ScaleType %s not supported.", scaleType));
    125         }
    126     }
    127 
    128     @Override
    129     public void setAdjustViewBounds(boolean adjustViewBounds) {
    130         if (adjustViewBounds) {
    131             throw new IllegalArgumentException("adjustViewBounds not supported.");
    132         }
    133     }
    134 
    135     @Override
    136     protected void onDraw(Canvas canvas) {
    137         Log.v("CircleImageView", "onDraw");
    138         if (getDrawable() == null) {
    139             return;
    140         }
    141 
    142         //这行代码就是把imageview 切割成最终的圆形
    143         canvas.drawCircle(getWidth() / 2, getHeight() / 2, mDrawableRadius, mBitmapPaint);
    144         //如果圆形边缘的宽度不为0 我们还要继续画这个描边
    145         if (mBorderWidth != 0) {
    146             canvas.drawCircle(getWidth() / 2, getHeight() / 2, mBorderRadius, mBorderPaint);
    147         }
    148     }
    149 
    150     @Override
    151     protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    152         super.onSizeChanged(w, h, oldw, oldh);
    153         setup();
    154     }
    155 
    156     public int getBorderColor() {
    157         return mBorderColor;
    158     }
    159 
    160     public void setBorderColor(int borderColor) {
    161         if (borderColor == mBorderColor) {
    162             return;
    163         }
    164 
    165         mBorderColor = borderColor;
    166         mBorderPaint.setColor(mBorderColor);
    167         invalidate();
    168     }
    169 
    170     public void setBorderColorResource(@ColorRes int borderColorRes) {
    171         setBorderColor(getContext().getResources().getColor(borderColorRes));
    172     }
    173 
    174     public int getBorderWidth() {
    175         return mBorderWidth;
    176     }
    177 
    178     public void setBorderWidth(int borderWidth) {
    179         if (borderWidth == mBorderWidth) {
    180             return;
    181         }
    182 
    183         mBorderWidth = borderWidth;
    184         setup();
    185     }
    186 
    187     public boolean isBorderOverlay() {
    188         return mBorderOverlay;
    189     }
    190 
    191     public void setBorderOverlay(boolean borderOverlay) {
    192         if (borderOverlay == mBorderOverlay) {
    193             return;
    194         }
    195 
    196         mBorderOverlay = borderOverlay;
    197         setup();
    198     }
    199 
    200     @Override
    201     public void setImageBitmap(Bitmap bm) {
    202         super.setImageBitmap(bm);
    203         mBitmap = bm;
    204         setup();
    205     }
    206 
    207 
    208     /**
    209      * 注意这个函数 是在我们的构造函数调用之前就调用了
    210      *
    211      * @param drawable
    212      */
    213     @Override
    214     public void setImageDrawable(Drawable drawable) {
    215         Log.v("CircleImageView", "setImageDrawable Drawable");
    216         super.setImageDrawable(drawable);
    217         mBitmap = getBitmapFromDrawable(drawable);
    218         setup();
    219     }
    220 
    221     @Override
    222     public void setImageResource(@DrawableRes int resId) {
    223         super.setImageResource(resId);
    224         mBitmap = getBitmapFromDrawable(getDrawable());
    225         setup();
    226     }
    227 
    228     @Override
    229     public void setImageURI(Uri uri) {
    230         super.setImageURI(uri);
    231         mBitmap = getBitmapFromDrawable(getDrawable());
    232         setup();
    233     }
    234 
    235     @Override
    236     public void setColorFilter(ColorFilter cf) {
    237         if (cf == mColorFilter) {
    238             return;
    239         }
    240 
    241         mColorFilter = cf;
    242         mBitmapPaint.setColorFilter(mColorFilter);
    243         invalidate();
    244     }
    245 
    246     private Bitmap getBitmapFromDrawable(Drawable drawable) {
    247         Log.v("CircleImageView", "getBitmapFromDrawable");
    248         if (drawable == null) {
    249             Log.v("CircleImageView", "drawable==null");
    250             //這種情況一般不會發生
    251             return null;
    252         }
    253 
    254         if (drawable instanceof BitmapDrawable) {
    255             Log.v("CircleImageView", "drawable instanceof BitmapDrawable");
    256             //通常来说 我们的代码就是执行到这里就返回了。返回的就是我们最原始的bitmap
    257             return ((BitmapDrawable) drawable).getBitmap();
    258         }
    259         Log.v("CircleImageView", "drawable is not instanceof BitmapDrawable");
    260 
    261         try {
    262             Bitmap bitmap;
    263 
    264             if (drawable instanceof ColorDrawable) {
    265                 Log.v("CircleImageView", "drawable  instanceof ColorDrawable");
    266                 bitmap = Bitmap.createBitmap(COLORDRAWABLE_DIMENSION, COLORDRAWABLE_DIMENSION, BITMAP_CONFIG);
    267             } else {
    268                 Log.v("CircleImageView", "drawable  is not instanceof ColorDrawable");
    269                 bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), BITMAP_CONFIG);
    270             }
    271 
    272             Canvas canvas = new Canvas(bitmap);
    273             drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
    274             drawable.draw(canvas);
    275             return bitmap;
    276         } catch (OutOfMemoryError e) {
    277             return null;
    278         }
    279     }
    280 
    281     /**
    282      * 这个函数比较关键,就是在进行一些重绘参数的初始化
    283      */
    284     private void setup() {
    285         Log.v("CircleImageView", "setup()");
    286         Log.v("CircleImageView", "mReady==" + mReady + "   mSetupPending==" + mSetupPending);
    287         //这个地方要注意mReady的默认值为false,也就是说第一次进这个函数的时候 因为为false 所以直接进入括号
    288         //体内然后返回,后面的代码并没有执行。 同时也能知道,mReady的值更改成true 是在init函数里面做的
    289         if (!mReady) {
    290             mSetupPending = true;
    291             return;
    292         }
    293 
    294         //防止空指针异常
    295         if (mBitmap == null) {
    296             return;
    297         }
    298 
    299         //参数值就代表 如果图片太小的话 就直接拉伸,repeat参数代表 图片大小的话就重复放图片 mirror就是镜像对着放图片的意思 跟大家设置pc 屏保时候其实是一样的
    300         mBitmapShader = new BitmapShader(mBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
    301 
    302         mBitmapPaint.setAntiAlias(true);
    303         mBitmapPaint.setShader(mBitmapShader);
    304 
    305 
    306         mBorderPaint.setStyle(Paint.Style.STROKE);
    307         mBorderPaint.setAntiAlias(true);
    308         mBorderPaint.setColor(mBorderColor);
    309         mBorderPaint.setStrokeWidth(mBorderWidth);
    310 
    311         //这个地方是取的原图片的大小
    312         mBitmapHeight = mBitmap.getHeight();
    313         mBitmapWidth = mBitmap.getWidth();
    314 
    315         //注意这个地方取的是imageview的实际大小,也就是说这个地方画了一个和imageview实际大小一致的方形图
    316         mBorderRect.set(0, 0, getWidth(), getHeight());
    317         Log.v("CircleImageView", "mBitmapHeight==" + mBitmapHeight + "  mBitmapWidth==" + mBitmapWidth);
    318         Log.v("CircleImageView", "getWidth()" + getWidth() + "  getHeight()==" + getHeight());
    319         //这个地方就是算最小半径的,注意是要减去边缘宽度的 因为这里计算的是 圆形边缘部分的最小半径
    320         mBorderRadius = Math.min((mBorderRect.height() - mBorderWidth) / 2, (mBorderRect.width() - mBorderWidth) / 2);
    321 
    322         mDrawableRect.set(mBorderRect);
    323         if (!mBorderOverlay) {
    324             mDrawableRect.inset(mBorderWidth, mBorderWidth);
    325         }
    326         //这里计算的是圆形内部的最小半径,其实很好理解,因为这个自定义控件提供了设置圆形边缘宽度的属性方法,所以在这里对于一个圆形边缘有宽度的图形来说
    327         //半径就是有2个,一个是外部半径,一个内部半径,上面的mBorderRadius就是内部半径 而这里是外部半径 一般来说 mDrawableRadius>=mBorderRadius
    328         mDrawableRadius = Math.min(mDrawableRect.height() / 2, mDrawableRect.width() / 2);
    329         updateShaderMatrix();
    330         //手动触发ondraw()函数 完成最终的绘制
    331         invalidate();
    332     }
    333 
    334     /**
    335      * 这个函数很好理解,就是做平移变换 放大或者缩小图片 所使用的,尽量保证 我们切割出来的图片 损失度最小。‘
    336      * <p/>
    337      * 这里面的算法可以好好研读一下 此方法能保证你每次切割出来的图片都是 原始图片正中央的那一部分
    338      */
    339     private void updateShaderMatrix() {
    340         Log.v("CircleImageView", "updateShaderMatrix()");
    341         float scale;
    342         float dx = 0;
    343         float dy = 0;
    344 
    345         mShaderMatrix.set(null);
    346 
    347         if (mBitmapWidth * mDrawableRect.height() > mDrawableRect.width() * mBitmapHeight) {
    348 
    349             //此缩放策略就是y轴缩放 x轴平移
    350             scale = mDrawableRect.height() / (float) mBitmapHeight;
    351             dx = (mDrawableRect.width() - mBitmapWidth * scale) * 0.5f;
    352         } else {
    353             //此缩放策略是 x轴缩放 y轴平移
    354             scale = mDrawableRect.width() / (float) mBitmapWidth;
    355             dy = (mDrawableRect.height() - mBitmapHeight * scale) * 0.5f;
    356         }
    357 
    358         mShaderMatrix.setScale(scale, scale);
    359         mShaderMatrix.postTranslate((int) (dx + 0.5f) + mDrawableRect.left, (int) (dy + 0.5f) + mDrawableRect.top);
    360 
    361         mBitmapShader.setLocalMatrix(mShaderMatrix);
    362     }
    363 
    364 }
  • 相关阅读:
    Angular 1 进阶
    【Ubuntu 16.04 使用日志】更改deb源
    【Ubuntu 16.04 使用日志】Linux下软件安装方法汇总
    【Ubuntu 16.04 使用日志】LOG
    【每日算法】桶排序算法
    【每日算法】基数排序算法
    【每日算法】计数排序算法
    【每日算法】归并排序算法
    【每日算法】交换排序算法之快速排序
    【每日算法】交换排序算法之鸡尾酒排序/双向冒泡排序
  • 原文地址:https://www.cnblogs.com/punkisnotdead/p/4624517.html
Copyright © 2020-2023  润新知