转:http://blog.csdn.net/lmj623565791/article/details/41967509,本文出自:【张鸿洋的博客】

1、概述

记得初学那会写过一篇博客Android 完美实现图片圆角和圆形(对实现进行分析),主要是个自定View加上使用Xfermode实现的。其实实现圆角图片的方法应该很多,常见的就是利用Xfermode,Shader。本篇博客会直接继承直接继承ImageView,使用BitmapShader实现圆角的绘制,大家如果耐着性子看完,我估计什么形状都能绘制出来。

2、效果图

这是圆角的一个演示图~~这个没什么说的,直接设置的圆角的大小就行;

这是圆形的显示图,这里需要注意下,因为设置的图片可能是长方形,例如上图:有两个长方形,一个宽比较大,一个高比较大;

那么我们希望显示成圆形,我们可能就要对其进行放大或者缩小(因为图片的宽可能不满足设置的边长,而高超出,此时我们就需要放大其宽度)。

这个一张图,中间是正常尺寸;上下分别为特大特小,主要可以当尺寸大于或者小于设置尺寸,我们需要对其放大或者缩小;

圆角时如果图片与view的宽高不一致,也需要进行放大缩小,这里就不截图了,代码里面看吧。

3、浅谈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充分的了解,请参考爱歌的神作: 自定义控件其实很简单1/3 。

对于我们的圆角,以及圆形,我们设置的模式都是CLAMP ,但是你会不会会有一个疑问:

view的宽或者高大于我们的bitmap宽或者高岂不是会拉伸?

嗯,我们会为BitmapShader设置一个matrix,去适当的放大或者缩小图片,不会让“ view的宽或者高大于我们的bitmap宽或者高 ”此条件成立的。

到此我们的原理基本介绍完毕了,拿到drawable转化为bitmap,然后直接初始化BitmapShader,画笔设置Shader,最后在onDraw里面进行画圆就行了。

4、BitmapShader实战

首先就来看看利用BitmapShader实现的圆形或者圆角。

我们这里直接继承ImageView,这样大家设置图片的代码会比较熟悉;但是我们需要支持两种模式,那么就需要自定义属性了:

1、自定义属性

values/attr.xml

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <resources>
  3. <attr name="borderRadius" format="dimension" />
  4. <attr name="type">
  5. <enum name="circle" value="0" />
  6. <enum name="round" value="1" />
  7. </attr>
  8. <declare-styleable name="RoundImageView">
  9. <attr name="borderRadius" />
  10. <attr name="type" />
  11. </declare-styleable>
  12. </resources>

我们定义了一个枚举和一个圆角的大小borderRadius。

2、获取自定义属性

  1. public class RoundImageView extends ImageView
  2. {
  3. /**
  4. * 图片的类型,圆形or圆角
  5. */
  6. private int type;
  7. private static final int TYPE_CIRCLE = 0;
  8. private static final int TYPE_ROUND = 1;
  9. /**
  10. * 圆角大小的默认值
  11. */
  12. private static final int BODER_RADIUS_DEFAULT = 10;
  13. /**
  14. * 圆角的大小
  15. */
  16. private int mBorderRadius;
  17. /**
  18. * 绘图的Paint
  19. */
  20. private Paint mBitmapPaint;
  21. /**
  22. * 圆角的半径
  23. */
  24. private int mRadius;
  25. /**
  26. * 3x3 矩阵,主要用于缩小放大
  27. */
  28. private Matrix mMatrix;
  29. /**
  30. * 渲染图像,使用图像为绘制图形着色
  31. */
  32. private BitmapShader mBitmapShader;
  33. /**
  34. * view的宽度
  35. */
  36. private int mWidth;
  37. private RectF mRoundRect;
  38. public RoundImageView(Context context, AttributeSet attrs)
  39. {
  40. super(context, attrs);
  41. mMatrix = new Matrix();
  42. mBitmapPaint = new Paint();
  43. mBitmapPaint.setAntiAlias(true);
  44. TypedArray a = context.obtainStyledAttributes(attrs,
  45. R.styleable.RoundImageView);
  46. mBorderRadius = a.getDimensionPixelSize(
  47. R.styleable.RoundImageView_borderRadius, (int) TypedValue
  48. .applyDimension(TypedValue.COMPLEX_UNIT_DIP,
  49. BODER_RADIUS_DEFAULT, getResources()
  50. .getDisplayMetrics()));// 默认为10dp
  51. type = a.getInt(R.styleable.RoundImageView_type, TYPE_CIRCLE);// 默认为Circle
  52. a.recycle();
  53. }

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

3、onMeasure

  1. @Override
  2. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
  3. {
  4. Log.e("TAG", "onMeasure");
  5. super.onMeasure(widthMeasureSpec, heightMeasureSpec);
  6. /**
  7. * 如果类型是圆形,则强制改变view的宽高一致,以小值为准
  8. */
  9. if (type == TYPE_CIRCLE)
  10. {
  11. mWidth = Math.min(getMeasuredWidth(), getMeasuredHeight());
  12. mRadius = mWidth / 2;
  13. setMeasuredDimension(mWidth, mWidth);
  14. }
  15. }

我们复写了onMeasure方法,主要用于当设置类型为圆形时,我们强制让view的宽和高一致。

接下来只剩下设置BitmapShader和绘制了

4、设置BitmapShader

  1. /**
  2. * 初始化BitmapShader
  3. */
  4. private void setUpShader()
  5. {
  6. Drawable drawable = getDrawable();
  7. if (drawable == null)
  8. {
  9. return;
  10. }
  11. Bitmap bmp = drawableToBitamp(drawable);
  12. // 将bmp作为着色器,就是在指定区域内绘制bmp
  13. mBitmapShader = new BitmapShader(bmp, TileMode.CLAMP, TileMode.CLAMP);
  14. float scale = 1.0f;
  15. if (type == TYPE_CIRCLE)
  16. {
  17. // 拿到bitmap宽或高的小值
  18. int bSize = Math.min(bmp.getWidth(), bmp.getHeight());
  19. scale = mWidth * 1.0f / bSize;
  20. } else if (type == TYPE_ROUND)
  21. {
  22. // 如果图片的宽或者高与view的宽高不匹配,计算出需要缩放的比例;缩放后的图片的宽高,一定要大于我们view的宽高;所以我们这里取大值;
  23. scale = Math.max(getWidth() * 1.0f / bmp.getWidth(), getHeight()
  24. * 1.0f / bmp.getHeight());
  25. }
  26. // shader的变换矩阵,我们这里主要用于放大或者缩小
  27. mMatrix.setScale(scale, scale);
  28. // 设置变换矩阵
  29. mBitmapShader.setLocalMatrix(mMatrix);
  30. // 设置shader
  31. mBitmapPaint.setShader(mBitmapShader);
  32. }

在setUpShader中,首先对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的代码:

  1. /**
  2. * drawable转bitmap
  3. *
  4. * @param drawable
  5. * @return
  6. */
  7. private Bitmap drawableToBitamp(Drawable drawable)
  8. {
  9. if (drawable instanceof BitmapDrawable)
  10. {
  11. BitmapDrawable bd = (BitmapDrawable) drawable;
  12. return bd.getBitmap();
  13. }
  14. int w = drawable.getIntrinsicWidth();
  15. int h = drawable.getIntrinsicHeight();
  16. Bitmap bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
  17. Canvas canvas = new Canvas(bitmap);
  18. drawable.setBounds(0, 0, w, h);
  19. drawable.draw(canvas);
  20. return bitmap;
  21. }

最后我们会在onDraw里面调用setUpShader(),然后进行绘制。

5、绘制

到此,就剩下最后一步绘制了,因为我们的范围,以及缩放都完成了,所以真的只剩下绘制了。

  1. @Override
  2. protected void onDraw(Canvas canvas)
  3. {
  4. if (getDrawable() == null)
  5. {
  6. return;
  7. }
  8. setUpShader();
  9. if (type == TYPE_ROUND)
  10. {
  11. canvas.drawRoundRect(mRoundRect, mBorderRadius, mBorderRadius,
  12. mBitmapPaint);
  13. } else
  14. {
  15. canvas.drawCircle(mRadius, mRadius, mRadius, mBitmapPaint);
  16. // drawSomeThing(canvas);
  17. }
  18. }
  19. @Override
  20. protected void onSizeChanged(int w, int h, int oldw, int oldh)
  21. {
  22. super.onSizeChanged(w, h, oldw, oldh);
  23. // 圆角图片的范围
  24. if (type == TYPE_ROUND)
  25. mRoundRect = new RectF(0, 0, getWidth(), getHeight());
  26. }

绘制就很简单了,画个圆,圆角矩形什么的。圆角矩形的限定范围mRoundRect在onSizeChanged里面进行了初始化。

5、状态的存储与恢复

当然了,如果内存不足,而恰好我们的Activity置于后台,不幸被重启,或者用户旋转屏幕造成Activity重启,我们的View应该也能尽可能的去保存自己的属性。
状态保存什么用处呢?比如,现在一个的圆角大小是10dp,用户点击后变成50dp;当用户旋转以后,或者长时间置于后台以后,返回我们的Activity应该还是50dp;
我们简单的存储一下,当前的type以及mBorderRadius
  1. private static final String STATE_INSTANCE = "state_instance";
  2. private static final String STATE_TYPE = "state_type";
  3. private static final String STATE_BORDER_RADIUS = "state_border_radius";
  4. @Override
  5. protected Parcelable onSaveInstanceState()
  6. {
  7. Bundle bundle = new Bundle();
  8. bundle.putParcelable(STATE_INSTANCE, super.onSaveInstanceState());
  9. bundle.putInt(STATE_TYPE, type);
  10. bundle.putInt(STATE_BORDER_RADIUS, mBorderRadius);
  11. return bundle;
  12. }
  13. @Override
  14. protected void onRestoreInstanceState(Parcelable state)
  15. {
  16. if (state instanceof Bundle)
  17. {
  18. Bundle bundle = (Bundle) state;
  19. super.onRestoreInstanceState(((Bundle) state)
  20. .getParcelable(STATE_INSTANCE));
  21. this.type = bundle.getInt(STATE_TYPE);
  22. this.mBorderRadius = bundle.getInt(STATE_BORDER_RADIUS);
  23. } else
  24. {
  25. super.onRestoreInstanceState(state);
  26. }
  27. }

代码比较简单。我们文章中的demo中,第一个,第四个是可以点击的,点击后会发生变化,你可以点击后,然后旋转屏幕进行测试。

 
同时我们也对外公布了两个方法,用于动态修改圆角大小和type
  1. public void setBorderRadius(int borderRadius)
  2. {
  3. int pxVal = dp2px(borderRadius);
  4. if (this.mBorderRadius != pxVal)
  5. {
  6. this.mBorderRadius = pxVal;
  7. invalidate();
  8. }
  9. }
  10. public void setType(int type)
  11. {
  12. if (this.type != type)
  13. {
  14. this.type = type;
  15. if (this.type != TYPE_ROUND && this.type != TYPE_CIRCLE)
  16. {
  17. this.type = TYPE_CIRCLE;
  18. }
  19. requestLayout();
  20. }
  21. }
  22. public int dp2px(int dpVal)
  23. {
  24. return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
  25. dpVal, getResources().getDisplayMetrics());
  26. }

最后贴一下我们的布局文件和MainActivity。

6、调用

布局文件:
  1. <ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
  2. xmlns:tools="http://schemas.android.com/tools"
  3. xmlns:zhy="http://schemas.android.com/apk/res/com.zhy.variousshapeimageview"
  4. android:layout_width="match_parent"
  5. android:layout_height="wrap_content" >
  6. <LinearLayout
  7. android:layout_width="match_parent"
  8. android:layout_height="match_parent"
  9. android:orientation="vertical" >
  10. <com.zhy.view.RoundImageView
  11. android:id="@+id/id_qiqiu"
  12. android:layout_width="wrap_content"
  13. android:layout_height="wrap_content"
  14. android:layout_margin="10dp"
  15. android:src="@drawable/qiqiu" >
  16. </com.zhy.view.RoundImageView>
  17. <com.zhy.view.RoundImageView
  18. android:layout_width="200dp"
  19. android:layout_height="200dp"
  20. android:layout_margin="10dp"
  21. android:src="@drawable/aa" >
  22. </com.zhy.view.RoundImageView>
  23. <com.zhy.view.RoundImageView
  24. android:layout_width="wrap_content"
  25. android:layout_height="wrap_content"
  26. android:layout_margin="10dp"
  27. android:src="@drawable/icon" >
  28. </com.zhy.view.RoundImageView>
  29. <com.zhy.view.RoundImageView
  30. android:id="@+id/id_meinv"
  31. android:layout_width="wrap_content"
  32. android:layout_height="wrap_content"
  33. android:layout_margin="10dp"
  34. android:src="@drawable/aa"
  35. zhy:borderRadius="20dp"
  36. zhy:type="round" >
  37. </com.zhy.view.RoundImageView>
  38. <com.zhy.view.RoundImageView
  39. android:layout_width="wrap_content"
  40. android:layout_height="wrap_content"
  41. android:layout_margin="10dp"
  42. android:src="@drawable/icon"
  43. zhy:borderRadius="40dp"
  44. zhy:type="round" >
  45. </com.zhy.view.RoundImageView>
  46. <com.zhy.view.RoundImageView
  47. android:layout_width="wrap_content"
  48. android:layout_height="wrap_content"
  49. android:layout_margin="10dp"
  50. android:src="@drawable/qiqiu"
  51. zhy:borderRadius="60dp"
  52. zhy:type="round" >
  53. </com.zhy.view.RoundImageView>
  54. </LinearLayout>
  55. </ScrollView>
 
没撒,ScrollView里面一个线性布局,里面一堆RoundImageView。

MainActivity

  1. package com.zhy.variousshapeimageview;
  2. import android.app.Activity;
  3. import android.os.Bundle;
  4. import android.view.View;
  5. import android.view.View.OnClickListener;
  6. import com.zhy.view.RoundImageView;
  7. public class MainActivity extends Activity
  8. {
  9. private RoundImageView mQiQiu;
  10. private RoundImageView mMeiNv ;
  11. @Override
  12. protected void onCreate(Bundle savedInstanceState)
  13. {
  14. super.onCreate(savedInstanceState);
  15. setContentView(R.layout.activity_main);
  16. mQiQiu = (RoundImageView) findViewById(R.id.id_qiqiu);
  17. mMeiNv = (RoundImageView) findViewById(R.id.id_meinv);
  18. mQiQiu.setOnClickListener(new OnClickListener()
  19. {
  20. @Override
  21. public void onClick(View v)
  22. {
  23. mQiQiu.setType(RoundImageView.TYPE_ROUND);
  24. }
  25. });
  26. mMeiNv.setOnClickListener(new OnClickListener()
  27. {
  28. @Override
  29. public void onClick(View v)
  30. {
  31. mMeiNv.setBorderRadius(90);
  32. }
  33. });
  34. }
  35. }

好了,到此本篇博客就结束了。大家可以尝试绘制个五边形或者神马的形状;或者加个边框神马的,相信自己修改应该没问题~~代码可能会存在bug和不足之处,欢迎您的指出,共同进步。

最后的效果图:

源码点击下载

Android_BitmapShader实现圆形、圆角图片的更多相关文章

  1. Android 圆形/圆角图片的方法

    Android 圆形/圆角图片的方法 眼下网上有非常多圆角图片的实例,Github上也有一些成熟的项目.之前做项目,为了稳定高效都是选用Github上的项目直接用.但这样的结束也是Android开发必 ...

  2. 安卓图片载入之使用universalimageloader载入圆形圆角图片

    前言 话说这universalimageloader载入图片对搞过2年安卓程序都是用烂了再熟悉只是了.就是安卓新手也是百度就会有一大堆东西出来,今天为什么这里还要讲使用universalimagelo ...

  3. Android实现圆形圆角图片

    本文主要使用两种方法实现图形圆角图片 自定View加上使用Xfermode实现 Shader实现 自定View加上使用Xfermode实现 /** * 根据原图和变长绘制圆形图片 * * @param ...

  4. CircleImageManager——圆形 / 圆角图片的工具类

    这个类可以实现圆角,或者是圆形图片的操作. CircleImageManager.java package com.kale.utils; import android.content.Context ...

  5. Android BitmapShader 实战 实现圆形、圆角图片

    转载自:http://blog.csdn.net/lmj623565791/article/details/41967509 1.概述 记得初学那会写过一篇博客Android 完美实现图片圆角和圆形( ...

  6. Android Xfermode 实战 实现圆形、圆角图片

    转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/42094215,本文出自:[张鸿洋的博客] 1.概述 其实这篇本来准备Androi ...

  7. [Android] 给图像加入相框、圆形圆角显示图片、图像合成知识

        前一篇文章讲述了Android触屏setOnTouchListener实现突破缩放.移动.绘制和加入水印,继续我的"随手拍"项目完毕给图片加入相框.圆形圆角显示图片和图像合 ...

  8. 【转】Android BitmapShader 实战 实现圆形、圆角图片

    转载自:http://blog.csdn.net/lmj623565791/article/details/41967509 1.概述 记得初学那会写过一篇博客Android 完美实现图片圆角和圆形( ...

  9. Flutter 图片、圆形头像、圆角图片....各种形状

    图片 1. 本地图片 Image.asset 加载项目资源包的图片 //先将图片拷贝到项目 images 目录中,然后在 pubspec.yaml文件配置文件相对路径到 assets Image.as ...

随机推荐

  1. 终极解法According to TLD or attribute directive in tag file, attribute select does not accept any expressions

    3天硬是是把这个问题解决了 有时候突然上个厕所灵感就来了 第一次向用JSTL解析xml 然后我想遍历整个xml文档打印出来 居然不让我输入变量 那让我怎么办啊 在网上各种找答案 说什么<%@ t ...

  2. 找出数组中特定和数字下标(JAVA)

    比如: 输入: numbers={2, 7, 11, 15}, target=9 输出: index1=1, index2=2 public class _003TwoSum { public sta ...

  3. MySql优化-你的SQL命中索引了吗

    在项目开发中SQL是必不可少的,表索也一样.这些SQL的运行性能不知道吗?有多少是命中了索引的?命中哪个索引?索引中有哪个是无效索引?这些无效索引是否会影响系统的性能?带着这些问题我们一起来学习一下. ...

  4. Floyd算法(弗洛伊德算法)

    算法描述: Floyd算法又称为弗洛伊德算法,插点法,是一种用于寻找给定的加权图中顶点间最短路径的算法.从图的带权邻接矩阵A=[a(i,j)] n×n开始,递归地进行n次更新,即由矩阵D(0)=A,按 ...

  5. 使用HTML5的页面资源预加载(Link prefetch)功能加速你的页面加载速度

    不管是浏览器的开发者还是普通web应用的开发者,他们都在做一个共同的努力:让Web浏览有更快的速度感觉.有很多已知的技术都可以让你的网站速度变得更快:使用CSS sprites,使用图片优化工具,使用 ...

  6. MYSQL 关于索引的部分问题!

    1. PRIMARY KEY也可以只指定为KEY.这么做的目的是与其它数据库系统兼容.二来key 是index的同意词! 2. 在UNIQUE索引中,所有的值必须互不相同.如果您在添加新行时使用的关键 ...

  7. 解决Java调用Azure SDK证书错误javax.net.ssl.SSLHandshakeException

    Azure作为微软的公有云平台,提供了非常丰富的SDK和API让开发人员可以非常方便的调用的各项服务,目前除了自家的.NET, Java, Python, nodeJS, Ruby,PHP等语言都提供 ...

  8. ModelMap和ModelAndView(转)

    转自:http://bao1073740756-126-com.iteye.com/blog/1549597 首先介绍ModelMap和ModelAndView的作用 ModelMap ModelMa ...

  9. NEC遥控信号解码(包含完整代码)

    一.遥控器解码说明 1.遥控器的编码格式常见有两种,一种是NEC 格式,一种是RC5 格式.遥控器发出的信号,通过一个红外的接收头之后,信号被送到MCU 的一个中断引脚.通过MCU 来识别不同的时序, ...

  10. jquery $ dollar符号用法总结

    参考:https://github.com/chyingp/blog/blob/master/jquery/jQuery%E6%BA%90%E7%A0%81-%E7%BE%8E%E5%85%83$%E ...