博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Andorid显示圆形图片的4种方式
阅读量:5834 次
发布时间:2019-06-18

本文共 15074 字,大约阅读时间需要 50 分钟。

这篇博客主要讲解了Android实现圆形图片的4种方式。

Android中并没有一个原生的控件,可以显示圆形或圆角图片,因此需要我们自己去定义这样一个控件。

实现圆形/圆角图片的核心思想,就是按照一定形状切割/绘制我们的原始控件,大概有以下4种方法:

  • 利用canvas.clipPath方法,按照自定义的Path图形去切割控件
  • 利用canvas.setBitmapShader,按照自定义的BitmapShader去重新绘制控件
  • 利用view.setOutlineProvider/setClipToOutline,按照自定义的Outline去切割控件
  • 利用Glide的Transformation变换,显示圆形图片

关于ImageView的几个知识点:

  • ImageView显示图片,底层是通过Canvas将我们的图片资源画到View控件上实现的;
    因此,要让其显示圆形图片,只需要对Canvas进行相应的变化,比如切割圆形、绘制圆形。
  • 编写自定义控件时,要继承AppCompatImageView,而不是ImageView,
    因为AppCompatImageView拥有ImageView没有的功能,比如Tinting

尊重原创,转载请注明出处

本文出自

Path切割

思路

我们可以定义一个圆形Path路径,然后调用canvas.clipPath,将图片切割成圆形

缺陷

但是这种方法有2个限制:

  • cliptPath不支持硬件加速,因此在调用前必须禁用硬件加速,
    setLayerType(View.LAYER_TYPE_SOFTWARE, null)
  • 这种方式剪裁的是Canvas图形,View的实际形状是不变的,
    因此只能对src属性有效,对background属性是无效的。

1.定义Radius属性,用来设置圆角半径

注意事项:

  • 我们定义radius为dimension,这是一个带单位的值(float不带单位)
  • radius:值默认或者<0,表示圆形图;>0表示圆角图

2.定义RoundImageView自定义圆形控件

注意事项

  • 设置圆形:path.addCircle
  • 设置圆角:path.addRoundRect
  • canvas.clipPath:不支持硬件加速,所以在使用前需要禁止硬件加速
    setLayerType(View.LAYER_TYPE_SOFTWARE, null)
  • clipPath要在super.onDraw方法前,调用,否则无效(canvas已经被设置给View了)
  • 在onSizeChanged方法中,获取宽高
public class RoundImageView extends AppCompatImageView {    private RectF mRect;    private Path mPath;    private float mRadius;    public RoundImageView(Context context) {        this(context, null);    }    public RoundImageView(Context context, AttributeSet attrs) {        this(context, attrs, -1);    }    public RoundImageView(Context context, AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);        getAttributes(context, attrs);        initView(context);    }    /**     * 获取属性     */    private void getAttributes(Context context, AttributeSet attrs) {        TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.RoundImageView);        mRadius = ta.getDimension(R.styleable.RoundImageView_radius, -1);        ta.recycle();    }    /**     * 初始化     */    private void initView(Context context) {        mRect = new RectF();        mPath = new Path();        setLayerType(LAYER_TYPE_SOFTWARE, null);        // 禁用硬件加速    }    @Override    protected void onSizeChanged(int w, int h, int oldw, int oldh) {        super.onSizeChanged(w, h, oldw, oldh);        if (mRadius < 0) {            clipCircle(w, h);        } else {            clipRoundRect(w, h);        }    }    /**     * 圆角     */    private void clipRoundRect(int width, int height) {        mRect.left = 0;        mRect.top = 0;        mRect.right = width;        mRect.bottom = height;        mPath.addRoundRect(mRect, mRadius, mRadius, Path.Direction.CW);    }    /**     * 圆形     */    private void clipCircle(int width, int height) {        int radius = Math.min(width, height)/2;        mPath.addCircle(width/2, height/2, radius, Path.Direction.CW);    }    @Override    protected void onDraw(Canvas canvas) {        canvas.clipPath(mPath);        super.onDraw(canvas);    }}

BitmapShader绘制

思路

通过Canvas.drawCircle自己去绘制一个圆形图片,并设置给ImageView;

  • 通过drawable资源获取Bitmap资源
  • 根据Bitmap,创建一个BitmapShader着色器
  • 对BitmapShader做矩阵变化,调整着色器大小至合适的尺寸
  • 将作色器设置给画笔Paint
  • 调用canvas.drawCircle让canvas根据画笔,去绘制一个圆形图片

缺陷

这种方式有个限制,就是如果要定义一个圆角图片,必须调用canvas.drawRoundRect进行绘制,但是这个方法要求API>=21

这里,我们可以看到,ImageView底层显示图片的原理,就是利用Canvas将我们的图片资源给绘制到View控件上

1. 从图片资源中,获取Bitmap

Drawable转Bitmap的2种方式

  • 直接从BitmapDrawable中获取
  • 利用Canvas去创建一个Bitmap,然后调用drawable.draw(canvas),自己去绘制Bitmap

注意事项:

  • Drawable不能从构造方法中,获取,这个时候获取到的是null
  • Drawable分srcbackground
private void initBitmap() {    Drawable drawable1 = getDrawable();    Drawable drawable2 = getBackground();    Drawable drawable = drawable1==null ? drawable2 : drawable1;          // 不能在构造方法中获取drawable,为null    if (drawable instanceof BitmapDrawable) {        BitmapDrawable bitmapDrawable = (BitmapDrawable) drawable;        mBitmap = bitmapDrawable.getBitmap();    } else {        int width = drawable.getIntrinsicWidth();       // 图片的原始宽度        int height = drawable.getIntrinsicHeight();     // 图片的原始高度        mBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);        Canvas canvas = new Canvas(mBitmap);//            drawable.setBounds(0,0,width,height);        drawable.draw(canvas);     }}

2. 根据Bitmap,创建着色器BitmapShader

BitmapShader着色器

  • TileMode瓷砖类型:当Canvas的宽高大于Bitmap的尺寸时,采取的重复策略

    • TileMode.MIRROR:图片镜像铺开
    • TileMode.REPEAT:图片重复铺开
    • TileMode.CLAMP:复用最后一个像素点
  • setLocalMatrix:对着色器中的Bitmap进行矩阵变化
private void initShader(Bitmap bitmap) {    mShader = new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);    int bitmapWidth = bitmap.getWidth();    int bitmapHeight = bitmap.getHeight();    float sx = mWidth * 1.0f / bitmapWidth;    float sy = mHeight * 1.0f / bitmapHeight;    float scale = Math.max(sx, sy);    Matrix matrix = new Matrix();    matrix.setScale(scale, scale);    mShader.setLocalMatrix(matrix);}

3. 将着色器BitmapShader,设置给Paint

mPaint.setShader(mShader);

4. 利用Canvas,自己绘制圆形/圆角图

注意点:

  • drawRoundRect只适用于Android 21及其以上版本
  • 要删除 super.onDraw(canvas):否则Canvas又会在ImageView中重新绘制,将我们之前的操作都覆盖了
@Overrideprotected void onDraw(Canvas canvas) {    initPaint();    if (mRadius < 0) {        float radius = Math.min(mWidth, mHeight) / 2;        canvas.drawCircle(mWidth/2, mHeight/2, radius, mPaint);    } else {        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {    // 21及其以上            canvas.drawRoundRect(0, 0, mWidth, mHeight, mRadius, mRadius, mPaint);        } else {            super.onDraw(canvas);        }    }    // super.onDraw(canvas);}

完整代码

public class RoundImageView2 extends AppCompatImageView {    private int mWidth;    private int mHeight;    private float mRadius;    private Paint mPaint;    private Bitmap mBitmap;    private BitmapShader mShader;    public RoundImageView2(Context context) {        this(context, null);    }    public RoundImageView2(Context context, AttributeSet attrs) {        this(context, attrs, -1);    }    public RoundImageView2(Context context, AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);        getAttributes(context, attrs);        initView(context);    }    private void getAttributes(Context context, AttributeSet attrs) {        TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.RoundImageView);        mRadius = ta.getDimension(R.styleable.RoundImageView_radius, -1);        ta.recycle();    }    private void initView(Context context) {        mPaint = new Paint();    }    @Override    protected void onSizeChanged(int w, int h, int oldw, int oldh) {        super.onSizeChanged(w, h, oldw, oldh);        mWidth = w;        mHeight = h;    }    @Override    protected void onDraw(Canvas canvas) {        initPaint();        if (mRadius < 0) {            float radius = Math.min(mWidth, mHeight) / 2;            canvas.drawCircle(mWidth/2, mHeight/2, radius, mPaint);        } else {            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {    // 21及其以上                canvas.drawRoundRect(0, 0, mWidth, mHeight, mRadius, mRadius, mPaint);            } else {                super.onDraw(canvas);            }        }//        super.onDraw(canvas);    }    /**     * 设置画笔     */    private void initPaint() {        initBitmap();        initShader(mBitmap);        mPaint.setShader(mShader);    }    /**     * 获取Bitmap     */    private void initBitmap() {        Drawable drawable1 = getDrawable();        Drawable drawable2 = getBackground();        Drawable drawable = drawable1==null ? drawable2 : drawable1;          // 不能在构造方法中获取drawable,为null        if (drawable instanceof BitmapDrawable) {            BitmapDrawable bitmapDrawable = (BitmapDrawable) drawable;            mBitmap = bitmapDrawable.getBitmap();        } else {            int width = drawable.getIntrinsicWidth();                 int height = drawable.getIntrinsicHeight();             mBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);            Canvas canvas = new Canvas(mBitmap);//            drawable.setBounds(0,0,width,height);            drawable.draw(canvas);         }    }    /**     * 获取BitmapShader     */    private void initShader(Bitmap bitmap) {        mShader = new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);        int bitmapWidth = bitmap.getWidth();        int bitmapHeight = bitmap.getHeight();        float sx = mWidth * 1.0f / bitmapWidth;        float sy = mHeight * 1.0f / bitmapHeight;        float scale = Math.max(sx, sy);        Matrix matrix = new Matrix();        matrix.setScale(scale, scale);        mShader.setLocalMatrix(matrix);    }}

OutlineProvider切割

思路

通过view.setOutlineProvider,给我们的View控件设置一个圆形轮廓,然后让View根据轮廓提供者进行切割

这个方法不同于前2种,前面2种方法,都是针对Canvas做文章,因此只能适用于图片的圆形处理;而这个方法是实实在在的对View进行了切割,不仅仅局限于图片,还可以针对任何其他View控件进行剪裁,适用范围更广(比如我们可以将整个页面变成一个圆形显示)

缺陷

但是这个方法有个限制,就是OutlineProvider只能适用于API>=21的版本,无法兼容低版本

OutlineProvider轮廓提供者

OutlineProvider轮廓提供者,可以给View提供一个外轮廓,并且让其根据轮廓进行剪切

  • view.setOutlineProvider:设置轮廓提供者
  • view.setClipToOutline:根据轮廓进行剪切
  • outline.setOval:画一个圆形轮廓
  • outline.setRect:画一个矩形轮廓

注意事项:

  • OutlineProvider要求API必须>=21;
  • OutlineProvider必须重写getOutline方法,其中参数Outline,就是提供给View的轮廓,我们可以根据需要自定义形状

完整代码

public class RoundImageView3 extends AppCompatImageView {    private float mRadius;    public RoundImageView3(Context context) {        this(context, null);    }    public RoundImageView3(Context context, AttributeSet attrs) {        this(context, attrs, -1);    }    public RoundImageView3(Context context, AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);        getAttributes(context, attrs);        initView();    }    private void getAttributes(Context context, AttributeSet attrs) {        TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.RoundImageView);        mRadius = ta.getDimension(R.styleable.RoundImageView_radius, -1);        ta.recycle();    }    private void initView() {        if (android.os.Build.VERSION.SDK_INT >= 21) {            ViewOutlineProvider outlineProvider = new ViewOutlineProvider(){                @Override                public void getOutline(View view, Outline outline) {                    int width = view.getWidth();                    int height = view.getHeight();                    if (mRadius < 0) {                        int radius = Math.min(width, height) / 2;                        Rect rect = new Rect(width/2-radius, height/2-radius, width/2+radius, height/2+radius);                        outline.setOval(rect);  // API>=21                    } else {                        Rect rect = new Rect(0, 0, width, height);                        outline.setRoundRect(rect, mRadius);                    }                }            };            setClipToOutline(true);            setOutlineProvider(outlineProvider);        }    }}

Glide显示圆形/圆角图片

思路

通过Glide图片加载框架实现,我们只需要给RequestOptions添加一个CircleCrop变换,即可实现圆形图片效果;如果要实现圆角图片,则需要自己去定义一个BitmapTransformation

缺陷

没有缺陷

1. Glide实现圆形图片

Glide内置了很多针对图形的Transformation变换,我们可以借助其中的CircleCrop选项非常方便的实现圆形图片的效果。

  • 创建一个RequestOptions选项
  • 给RequestOptions,添加CircleCrop变换
  • 通过apply,将RequestOptions设置给Glide的RequestBuilder

下面2种方式,都可以实现圆形图片的效果,只是写法不一样:

public static void loadCircleImage1(Context context, String url, ImageView imageView) {    Glide.with(context)            .load(url)            .apply(RequestOptions.circleCropTransform())            .into(imageView);}public static void loadCircleImage2(Context context, String url, ImageView imageView) {    RequestOptions options = new RequestOptions()            .circleCrop();    Glide.with(context)            .load(url)            .apply(options)            .into(imageView);}

2. Glide显示圆角图片

Glide并没有像提供CircleCrop那样,提供一个圆角图片的Transformation,因此如果需要显示圆角图片,那么就需要自己去定义一个Transformation。

那么,要怎么去定义一个Transformation呢?我们可以参考Circrop的做法:

  • 写一个类继承BitmapTransformation
  • 重写transformupdateDiskCacheKeyequalshashCode方法

transform:实现变化的具体细节

  • BitmapPool:可以用来快速的获取一个Bitmap的资源池,并且通常要在方法中返回这个获取到的Bitmap
  • toTransform:需要变化的Bitmap原始资源;需要注意的是,这个原始资源并不是最初的Bitmap,在调用这个方法之前Glide已经将原始Bitmap进行了合适的缩放
  • outWidthoutHeight:Bitmap的理想尺寸;需要注意的是,这个尺寸并不是Bitmap的尺寸,也不是ImageView的尺寸,Glide给我们返回的这个尺寸是ImageView的最小宽高值(如果ImageView的宽高都是match_parent,那么返回的是ImageView的最大宽高值)

CircleCrop的源码

public class CircleCrop extends BitmapTransformation {  // The version of this transformation, incremented to correct an error in a previous version.  // See #455.  private static final int VERSION = 1;  private static final String ID = "com.bumptech.glide.load.resource.bitmap.CircleCrop." + VERSION;  private static final byte[] ID_BYTES = ID.getBytes(CHARSET);  public CircleCrop() {    // Intentionally empty.  }  /**   * @deprecated Use {@link #CircleCrop()}.   */  @Deprecated  public CircleCrop(@SuppressWarnings("unused") Context context) {    this();  }  /**   * @deprecated Use {@link #CircleCrop()}   */  @Deprecated  public CircleCrop(@SuppressWarnings("unused") BitmapPool bitmapPool) {    this();  }  // Bitmap doesn't implement equals, so == and .equals are equivalent here.  @SuppressWarnings("PMD.CompareObjectsWithEquals")  @Override  protected Bitmap transform(      @NonNull BitmapPool pool, @NonNull Bitmap toTransform, int outWidth, int outHeight) {    return TransformationUtils.circleCrop(pool, toTransform, outWidth, outHeight);  }  @Override  public boolean equals(Object o) {    return o instanceof CircleCrop;  }  @Override  public int hashCode() {    return ID.hashCode();  }  @Override  public void updateDiskCacheKey(MessageDigest messageDigest) {    messageDigest.update(ID_BYTES);  }}

自定义的一个圆角BitmapTransformation

这个实现细节,与前面的“利用BitmapShader绘制一个圆角图片”基本是一样的。

public class GlideRoundRect extends BitmapTransformation {    private float mRadius;    private static final int VERSION = 1;    private static final String ID = BuildConfig.APPLICATION_ID + ".GlideRoundRect." + VERSION;    private static final byte[] ID_BYTES = ID.getBytes(CHARSET);    @Override    public void updateDiskCacheKey(MessageDigest messageDigest) {        messageDigest.update(ID_BYTES);    }    @Override    public boolean equals(Object o) {        return o instanceof GlideRoundRect;    }    @Override    public int hashCode() {        return ID.hashCode();    }    public GlideRoundRect(float radius) {        super();        mRadius = radius;    }    @Override    protected Bitmap transform(@NonNull BitmapPool pool, @NonNull Bitmap toTransform, int outWidth, int outHeight) {        return roundRectCrop(pool, toTransform);    }    private Bitmap roundRectCrop(BitmapPool pool, Bitmap source) {        if (source == null)            return null;        // 1. 根据source,创建一个BitmapShader        BitmapShader bitmapShader = new BitmapShader(source, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);        Paint paint = new Paint();        paint.setShader(bitmapShader);        // 2. 获取一个新的Bitmap        int sourceWidth = source.getWidth();        int sourceHeight = source.getHeight();        Bitmap bitmap = pool.get(sourceWidth, sourceHeight, Bitmap.Config.ARGB_8888);        // 3. 给新的Bitmap附上图形        Canvas canvas = new Canvas(bitmap);        RectF rect = new RectF(0, 0, sourceWidth, sourceHeight);        canvas.drawRoundRect(rect, mRadius, mRadius, paint);        // 4. 返回Bitmap        return bitmap;    }}
你可能感兴趣的文章
GitHub宣布推出Electron 1.0和Devtron,并将提供无限制的私有代码库
查看>>
论模式在领域驱动设计中的重要性
查看>>
四、配置开机自动启动Nginx + PHP【LNMP安装 】
查看>>
Linux 目录结构及内容详解
查看>>
OCP读书笔记(24) - 题库(ExamD)
查看>>
.net excel利用NPOI导入oracle
查看>>
$_SERVER['SCRIPT_FLENAME']与__FILE__
查看>>
html5纲要,细谈HTML 5新增的元素
查看>>
Android应用集成支付宝接口的简化
查看>>
[分享]Ubuntu12.04安装基础教程(图文)
查看>>
django 目录结构修改
查看>>
win8 关闭防火墙
查看>>
CSS——(2)与标准流盒模型
查看>>
C#中的Marshal
查看>>
linux命令:ls
查看>>
Using RequireJS in AngularJS Applications
查看>>
【SAP HANA】关于SAP HANA中带层次结构的计算视图Cacultation View创建、激活状况下在系统中生成对象的研究...
查看>>
CentOS 7 装vim遇到的问题和解决方法
查看>>
【ros】Create a ROS package:package dependencies报错
查看>>
通过容器编排和服务网格来改进Java微服务的可测性
查看>>