在一些显示用户头像的时候,大多数都是显示圆形的,而不是显示一个正方形或者长方形,这样显得很呆板,视觉效果也不好看,今天就用二种方式实现图片圆形显示的效果,在先讲这个效果实现之前,讲下BitmapShader到底能干嘛,android有几个关于渲染的类,使用它能实现一些不错的效果,比如今天讲的BitmapShader的就是关于图像渲染,还有其他4种,这都会再以后都会以博客的形式写出来,今天就单独讲讲BitmapShader,进入BitmapShader源码发现这是你见过最简单的系统类源码了
package android.graphics; public class BitmapShader extends android.graphics.Shader { public BitmapShader(android.graphics.Bitmap bitmap, android.graphics.Shader.TileMode tileX, android.graphics.Shader.TileMode tileY) { /* compiled code */ } }这构造函数中就三个参数,简单说明下:
第一个bitmap:就是你渲染图像对象
第二个参数tileX:就是你x轴方向渲染模式
第三个参数tileY:就是你y轴方向渲染模式
关于渲染模式有三种:
从上面的代码中知道BitmapShader是继承了Shader类,而模式就是定义在Shader中的一个枚举而已
package android.graphics; public class Shader { public Shader() { /* compiled code */ } public boolean getLocalMatrix(android.graphics.Matrix localM) { /* compiled code */ } public void setLocalMatrix(android.graphics.Matrix localM) { /* compiled code */ } protected void finalize() throws java.lang.Throwable { /* compiled code */ } public static enum TileMode { CLAMP, MIRROR, REPEAT; private TileMode() { /* compiled code */ } } }从代码中我们就清晰的看到定义的TileMode有三种
CLAMP:当view控件宽或者高大于你渲染图形的大小时,拉伸最后一个像素去铺满剩下的地方
MIRROR:当view控件宽或者高大于你渲染图形的大小时,通过镜像翻转铺满剩下的地方
REPEAT:当view控件宽或者高大于你渲染图形的大小时,重复图片平铺整个画面(电脑设置壁纸)
等下会说这个三个模式,先把第一种实现圆形的方式代码贴下:
package com.bitmapshaderdemo; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.BitmapShader; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Shader; import android.graphics.drawable.ShapeDrawable; import android.graphics.drawable.shapes.OvalShape; import android.util.AttributeSet; import android.util.Log; import android.view.View; /** * Created by admin on 2016/11/22. */ public class RoundHeadView extends View { private static final String TAG ="RoundHeadView" ; private Paint mPaint; private ShapeDrawable shapeDrawable; private BitmapShader bitmapShaderp; public RoundHeadView(Context context) { this(context,null); } public RoundHeadView(Context context, AttributeSet attrs) { this(context, attrs,0); } public RoundHeadView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); initPaint(); init(); setBackgroundColor(Color.RED); } private void init() { Bitmap bitmap = BitmapFactory.decodeResource(getResources(),R.mipmap.gird); Log.e(TAG,"宽-"+bitmap.getWidth()+"-"+"高"+bitmap.getHeight()); shapeDrawable = new ShapeDrawable(new OvalShape()); bitmapShaderp = new BitmapShader(bitmap, Shader.TileMode.CLAMP,Shader.TileMode.CLAMP); shapeDrawable.getPaint().setShader(bitmapShaderp); } private void initPaint() { mPaint = new Paint(); mPaint.setAntiAlias(true); mPaint.setStyle(Paint.Style.STROKE); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); shapeDrawable.draw(canvas); } }运行起来看效果:
发现屏幕一片空白,这是为什么呢? 是因为你没给图片显示设置位置,哪可能会说我我这个自定义view的宽和高就是屏幕的宽和高,但是你必须要给shapeDrawable设置显示区域,
@Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); shapeDrawable.setBounds(200,200,400,400); shapeDrawable.draw(canvas); }我们在onDraw()方法中指定了显示的区域,就是一个矩形区域,现在运行看效果
ok,一个美女头像出来了,这是一种实现方式,下面不使用ShapeDrawable再实现下:
package com.bitmapshaderdemo; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.BitmapShader; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Shader; import android.util.AttributeSet; import android.util.Log; import android.view.View; /** * Created by admin on 2016/11/22. */ public class RoundHeadView extends View { private static final String TAG ="RoundHeadView" ; private Paint mPaint; private BitmapShader bitmapShaderp; public RoundHeadView(Context context) { this(context,null); } public RoundHeadView(Context context, AttributeSet attrs) { this(context, attrs,0); } public RoundHeadView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); initPaint(); init(); } private void init() { Bitmap bitmap = BitmapFactory.decodeResource(getResources(),R.mipmap.gird); Log.e(TAG,"宽-"+bitmap.getWidth()+"-"+"高"+bitmap.getHeight()); bitmapShaderp = new BitmapShader(bitmap, Shader.TileMode.CLAMP,Shader.TileMode.CLAMP); mPaint.setShader(bitmapShaderp); } private void initPaint() { mPaint = new Paint(); mPaint.setAntiAlias(true); mPaint.setStyle(Paint.Style.FILL); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); canvas.drawCircle(200,200,160,mPaint); } }这就是使用Paint中的setShader()方法把图片 渲染成一个圆形,因为这个渲染是作用于这个画笔上的也就是代码中的mPaint,而我们又用这个画笔去画圆,这就是为啥一个长方形的图片变成圆形的,要变成圆角矩形也很简单,就一行代码的事:
@Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); // canvas.drawCircle(200,200,160,mPaint); canvas.drawRoundRect(100,100,400,300,15,15,mPaint); }效果:
上面二种做法都有问题,我们知道显示一个圆,其实是一个矩形的内切圆,如果这个图片不是正方形是长方形,那么这个内切圆肯定是有问题的,认真看下上面几个圆形的头像就会发现问题,在这先把上面遗留的渲染模式讲下:
我绘制圆以后再使用这个画笔绘制一个矩形
@Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); canvas.drawCircle(200,200,160,mPaint); canvas.drawRect(new Rect(0, 0, 400, 400), mPaint); }设置圆形图片的资源:
这是整张图,在绘制了一个400*400的矩形后效果如下:
没绘制矩形之前效果:
现在把这二个图片放在一起对比:
还有二种模式,这样吧,把图片设置成圆形的不太好看效果,就设置成长方形吧,
@Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); canvas.drawRect(100,100,300,300,mPaint); canvas.drawRect(new Rect(0, 0, 400, 400), mPaint); }
bitmapShaderp = new BitmapShader(bitmap, Shader.TileMode.MIRROR,Shader.TileMode.MIRROR);模式改成了MIRROR,效果如下:
MIRROR其实就是我们生活中照镜子一样,这个很好理解:
现在把x轴方向设置成重复的模式
bitmapShaderp = new BitmapShader(bitmap, Shader.TileMode.REPEAT,Shader.TileMode.MIRROR);效果:
通过上面的例子把三种模式都讲了下,现在把上面说到的如果图片不是正方形的问题解决下,解决方案有二种,一种是是使用最小的边把图片切割,一种是放大
方案一:
解决思路:以图片最小的边去切一个正方形,比如这张图:
那么现在开始按照这个思路解决刚才问题
private Bitmap getBitmap() { Canvas canvas = new Canvas(); Bitmap tempBp = null; Paint paint = new Paint(); if(width>height){ tempBp = Bitmap.createBitmap(bitmap,(width-height)/2,0,height,height); }else if(width<height){ tempBp = Bitmap.createBitmap(bitmap,0,(height-width)/2,width,width); }else{//等于 tempBp = Bitmap.createBitmap(bitmap,0,0,height,height); } canvas.drawBitmap(tempBp,10,10,paint); return tempBp; }这个就是得到在原来的图片上切一个矩形出来,使用了Bitmap的一个静态函数
public static android.graphics.Bitmap createBitmap(android.graphics.Bitmap source, int x, int y, int width, int height)第一个参数 source:就是在那个图片上进行切
第二个参数在以图片为坐标轴 x轴方向的值
第三个参数:同理第二个参数
第四个参数:新图片的宽度
第五个参数:新图片的高度
如图:
同理宽>高的情况下,相信也可以算出(x,y)坐标
整个代码如下:
package com.bitmapshaderdemo; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.BitmapShader; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Shader; import android.util.AttributeSet; import android.view.View; /** * Created by admin on 2016/11/22. */ public class RoundHeadView extends View { private static final String TAG ="RoundHeadView" ; private Paint mPaint; private BitmapShader bitmapShaderp; private int width;//图片的宽度 private int height;//图片的高度 private Bitmap bitmap; public RoundHeadView(Context context) { this(context,null); } public RoundHeadView(Context context, AttributeSet attrs) { this(context, attrs,0); } public RoundHeadView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); initPaint(); init(); } private void init() { bitmap = BitmapFactory.decodeResource(getResources(),R.mipmap.gird); width = bitmap.getWidth(); height = bitmap.getHeight(); bitmapShaderp = new BitmapShader(getBitmap(), Shader.TileMode.REPEAT,Shader.TileMode.MIRROR); mPaint.setShader(bitmapShaderp); } private void initPaint() { mPaint = new Paint(); mPaint.setAntiAlias(true); mPaint.setStyle(Paint.Style.FILL); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); canvas.drawCircle(width/2,width/2,width/2,mPaint); } private Bitmap getBitmap() { Canvas canvas = new Canvas(); Bitmap tempBp = null; Paint paint = new Paint(); if(width>height){ tempBp = Bitmap.createBitmap(bitmap,(width-height)/2,0,height,height); width = height; }else if(width<height){ tempBp = Bitmap.createBitmap(bitmap,0,(height-width)/2,width,width); height = width; }else{//等于 height = width; tempBp = Bitmap.createBitmap(bitmap,0,0,height,height); } canvas.drawBitmap(tempBp,0,0,paint); return tempBp; } }效果:
但是这个做法也有缺点就是如果用户想看图片上面的内容这个就个做法就不太好了,这也算是个需求
方案二:
周末了虽然这么晚还是想把这博客写完,不然感觉总欠什么,好了,废话不多说,之前解决方案是缩小,而这方案是放大,是通过矩阵的方式放大,那么放大比例是多少呢,是根据最小的边框来计算,就是最大的边框/最小的边框=缩放比例,代码如下:
package com.bitmapshaderdemo; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.BitmapShader; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.Rect; import android.graphics.Shader; import android.util.AttributeSet; import android.util.Log; import android.view.View; /** * Created by admin on 2016/11/22. */ public class RoundHeadView extends View { private static final String TAG ="RoundHeadView" ; private Paint mPaint; private BitmapShader bitmapShaderp; private int width;//图片的宽度 private int height;//图片的高度 private Bitmap bitmap; public RoundHeadView(Context context) { this(context,null); } public RoundHeadView(Context context, AttributeSet attrs) { this(context, attrs,0); } public RoundHeadView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); initPaint(); init(); } private void init() { bitmap = BitmapFactory.decodeResource(getResources(),R.mipmap.gird); width = bitmap.getWidth(); height = bitmap.getHeight(); bitmapShaderp = new BitmapShader(bitmap, Shader.TileMode.REPEAT,Shader.TileMode.MIRROR); mPaint.setShader(bitmapShaderp); } private void initPaint() { mPaint = new Paint(); mPaint.setAntiAlias(true); mPaint.setStyle(Paint.Style.FILL); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); //设置像素矩阵,来调整大小,为了解决宽高不一致的问题。 float scale = Math.max(width, height)*1.0f/Math.min(width, height); Matrix matrix = new Matrix(); matrix.setScale(scale, scale);//缩放比例 bitmapShaderp.setLocalMatrix(matrix); canvas.drawCircle(Math.max(width, height)/2f, scale*Math.max(width, height)/2f, Math.max(width, height)/2f, mPaint); } }效果:
现在实现放大镜的功能
思路:其实将一张图片放大,然后截图这张大图中的部分图片,我先显示一个普通的图片
package com.bitmapshaderdemo; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Paint; import android.util.AttributeSet; import android.view.View; /** * Created by admin on 2016/11/28. */ public class MirrorImageView extends View { private Bitmap bitmap; private Paint paint; public MirrorImageView(Context context, AttributeSet attrs) { super(context, attrs); paint = new Paint(); paint.setStrokeWidth(8); bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.ee); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); canvas.drawBitmap(bitmap,0,0,null); } }效果:
然后创建以这个图片宽和高度都乘以2,
package com.bitmapshaderdemo; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Paint; import android.util.AttributeSet; import android.view.View; /** * Created by admin on 2016/11/28. */ public class MirrorImageView extends View { private Bitmap bitmap; private Paint paint; private int width; private int height; private Bitmap scaleBitmap; private int SCALE = 2;//缩放的倍数 public MirrorImageView(Context context, AttributeSet attrs) { super(context, attrs); paint = new Paint(); paint.setStrokeWidth(8); bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.ee); width = bitmap.getWidth(); height = bitmap.getHeight(); scaleBitmap = Bitmap.createScaledBitmap(bitmap,width*SCALE,height*SCALE,true); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); canvas.drawBitmap(bitmap,0,0,null); canvas.drawBitmap(scaleBitmap,0,0,null); } }效果:
这是图片宽和高都放大2倍后的效果,我现在在这个图片上画一个圆,那么怎么等同和图片没放大的时候位置一样呢?在画板上把2张图片放在一起,然后画个圆看看位置怎么分析:
第二张放大2倍是用于放大镜效果的图片所用,当然在实际的功能中第二张图片肯定不会绘制上去,但是所绘制的圆就是在放大后的图片上去截取的,比如上面的2个圆的半径都是100,圆心为(100,100),在屏幕上所down下去的x,y轴坐标一样,但是由于第二张图片放大了,down下去所点击的区域和原图肯定不一样,这样肯定是不行的,第二个圆心点如果向左平移的话,就能达到和原图的圆心重合,那么x轴平移多少才和原图中的圆心点重合呢?100-x*放大的倍数,平移100-x*放大的倍数 这个结果,就是最终x轴平移的距离,现在根据这个我们绘制一个圆在放大的图片上,随着手指的拖动圆也变动,
完整的代码如下:
package com.bitmapshaderdemo; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.BitmapShader; import android.graphics.Canvas; import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.Shader; import android.graphics.drawable.ShapeDrawable; import android.graphics.drawable.shapes.OvalShape; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; /** * Created by admin on 2016/11/28. */ public class MirrorImageView extends View { private Bitmap bitmap; private Paint paint; private int width; private int height; private Bitmap scaleBitmap; private int SCALE = 2;//缩放的倍数 private ShapeDrawable drawable; private int radius = 75;//圆的半径 private Matrix matrix; public MirrorImageView(Context context, AttributeSet attrs) { super(context, attrs); matrix = new Matrix(); paint = new Paint(); paint.setStrokeWidth(8); bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.dd); width = bitmap.getWidth(); height = bitmap.getHeight(); scaleBitmap = Bitmap.createScaledBitmap(bitmap,width*SCALE,height*SCALE,true); BitmapShader shader = new BitmapShader(scaleBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP); drawable = new ShapeDrawable(new OvalShape()); drawable.getPaint().setShader(shader); //设置drawable显示的区域,这个上面已经说过,不设置区域 图片是显示不出来的 drawable.setBounds(0, 0, radius*2, radius*2); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); canvas.drawBitmap(bitmap,0,0,null); drawable.draw(canvas); } @Override public boolean onTouchEvent(MotionEvent event) { int x = (int) event.getX(); int y = (int) event.getY(); matrix.setTranslate(radius - x*SCALE, radius - y*SCALE);
//平移到绘制shader的起始位置drawable.getPaint().getShader().setLocalMatrix( matrix) ; drawable.setBounds(x- radius , y- radius , x+ radius , y+ radius) ; invalidate() ; return true; }} 效果:
突然发现美女如果胸不大的话,使用这个功能都不用去韩国了,如果我下面的底层图片都不绘制的话,
@Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); // canvas.drawBitmap(bitmap,0,0,null);//注释这段代码 drawable.draw(canvas); }看看效果:
ok,我看有些人就把底层图片也不绘制出来,效果就是这样的,总算写完了!