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

来源:互联网 发布:破解 经淘宝排查认定 编辑:程序博客网 时间:2024/05/19 20:42
转载请标明出处: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

[html] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <resources>  
  3.   
  4.     <attr name="borderRadius" format="dimension" />  
  5.     <attr name="type">  
  6.         <enum name="circle" value="0" />  
  7.         <enum name="round" value="1" />  
  8.     </attr>  
  9.     
  10.   
  11.     <declare-styleable name="RoundImageView">  
  12.         <attr name="borderRadius" />  
  13.         <attr name="type" />  
  14.     </declare-styleable>  
  15.   
  16. </resources>  

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


2、获取自定义属性

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

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


3、onMeasure

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. @Override  
  2.     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)  
  3.     {  
  4.         Log.e("TAG""onMeasure");  
  5.         super.onMeasure(widthMeasureSpec, heightMeasureSpec);  
  6.   
  7.         /** 
  8.          * 如果类型是圆形,则强制改变view的宽高一致,以小值为准 
  9.          */  
  10.         if (type == TYPE_CIRCLE)  
  11.         {  
  12.             mWidth = Math.min(getMeasuredWidth(), getMeasuredHeight());  
  13.             mRadius = mWidth / 2;  
  14.             setMeasuredDimension(mWidth, mWidth);  
  15.         }  
  16.   
  17.     }  

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

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


4、设置BitmapShader

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

在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的代码:

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  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(00, w, h);  
  19.         drawable.draw(canvas);  
  20.         return bitmap;  
  21.     }  

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

5、绘制

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

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. @Override  
  2.     protected void onDraw(Canvas canvas)  
  3.     {  
  4.         if (getDrawable() == null)  
  5.         {  
  6.             return;  
  7.         }  
  8.         setUpShader();  
  9.   
  10.         if (type == TYPE_ROUND)  
  11.         {  
  12.             canvas.drawRoundRect(mRoundRect, mBorderRadius, mBorderRadius,  
  13.                     mBitmapPaint);  
  14.         } else  
  15.         {  
  16.             canvas.drawCircle(mRadius, mRadius, mRadius, mBitmapPaint);  
  17.             // drawSomeThing(canvas);  
  18.         }  
  19.     }  
  20.       
  21.     @Override  
  22.     protected void onSizeChanged(int w, int h, int oldw, int oldh)  
  23.     {  
  24.         super.onSizeChanged(w, h, oldw, oldh);  
  25.         // 圆角图片的范围  
  26.         if (type == TYPE_ROUND)  
  27.             mRoundRect = new RectF(00, getWidth(), getHeight());  
  28.     }  

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

5、状态的存储与恢复

当然了,如果内存不足,而恰好我们的Activity置于后台,不幸被重启,或者用户旋转屏幕造成Activity重启,我们的View应该也能尽可能的去保存自己的属性。
状态保存什么用处呢?比如,现在一个的圆角大小是10dp,用户点击后变成50dp;当用户旋转以后,或者长时间置于后台以后,返回我们的Activity应该还是50dp;
我们简单的存储一下,当前的type以及mBorderRadius
[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  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.   
  5.     @Override  
  6.     protected Parcelable onSaveInstanceState()  
  7.     {  
  8.         Bundle bundle = new Bundle();  
  9.         bundle.putParcelable(STATE_INSTANCE, super.onSaveInstanceState());  
  10.         bundle.putInt(STATE_TYPE, type);  
  11.         bundle.putInt(STATE_BORDER_RADIUS, mBorderRadius);  
  12.         return bundle;  
  13.     }  
  14.   
  15.     @Override  
  16.     protected void onRestoreInstanceState(Parcelable state)  
  17.     {  
  18.         if (state instanceof Bundle)  
  19.         {  
  20.             Bundle bundle = (Bundle) state;  
  21.             super.onRestoreInstanceState(((Bundle) state)  
  22.                     .getParcelable(STATE_INSTANCE));  
  23.             this.type = bundle.getInt(STATE_TYPE);  
  24.             this.mBorderRadius = bundle.getInt(STATE_BORDER_RADIUS);  
  25.         } else  
  26.         {  
  27.             super.onRestoreInstanceState(state);  
  28.         }  
  29.   
  30.     }  

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

同时我们也对外公布了两个方法,用于动态修改圆角大小和type
[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  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.   
  11.     public void setType(int type)  
  12.     {  
  13.         if (this.type != type)  
  14.         {  
  15.             this.type = type;  
  16.             if (this.type != TYPE_ROUND && this.type != TYPE_CIRCLE)  
  17.             {  
  18.                 this.type = TYPE_CIRCLE;  
  19.             }  
  20.             requestLayout();  
  21.         }  
  22.   
  23.     }  
  24.   
  25.     public int dp2px(int dpVal)  
  26.     {  
  27.         return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,  
  28.                 dpVal, getResources().getDisplayMetrics());  
  29.     }  

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

6、调用

布局文件:
[html] view plain copy
 在CODE上查看代码片派生到我的代码片
  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.   
  7.     <LinearLayout  
  8.         android:layout_width="match_parent"  
  9.         android:layout_height="match_parent"  
  10.         android:orientation="vertical" >  
  11.   
  12.         <com.zhy.view.RoundImageView  
  13.             android:id="@+id/id_qiqiu"  
  14.             android:layout_width="wrap_content"  
  15.             android:layout_height="wrap_content"  
  16.             android:layout_margin="10dp"  
  17.             android:src="@drawable/qiqiu" >  
  18.         </com.zhy.view.RoundImageView>  
  19.   
  20.         <com.zhy.view.RoundImageView  
  21.             android:layout_width="200dp"  
  22.             android:layout_height="200dp"  
  23.             android:layout_margin="10dp"  
  24.             android:src="@drawable/aa" >  
  25.         </com.zhy.view.RoundImageView>  
  26.   
  27.         <com.zhy.view.RoundImageView  
  28.             android:layout_width="wrap_content"  
  29.             android:layout_height="wrap_content"  
  30.             android:layout_margin="10dp"  
  31.             android:src="@drawable/icon" >  
  32.         </com.zhy.view.RoundImageView>  
  33.   
  34.         <com.zhy.view.RoundImageView  
  35.             android:id="@+id/id_meinv"  
  36.             android:layout_width="wrap_content"  
  37.             android:layout_height="wrap_content"  
  38.             android:layout_margin="10dp"  
  39.             android:src="@drawable/aa"  
  40.             zhy:borderRadius="20dp"  
  41.             zhy:type="round" >  
  42.         </com.zhy.view.RoundImageView>  
  43.   
  44.         <com.zhy.view.RoundImageView  
  45.             android:layout_width="wrap_content"  
  46.             android:layout_height="wrap_content"  
  47.             android:layout_margin="10dp"  
  48.             android:src="@drawable/icon"  
  49.             zhy:borderRadius="40dp"  
  50.             zhy:type="round" >  
  51.         </com.zhy.view.RoundImageView>  
  52.   
  53.         <com.zhy.view.RoundImageView  
  54.             android:layout_width="wrap_content"  
  55.             android:layout_height="wrap_content"  
  56.             android:layout_margin="10dp"  
  57.             android:src="@drawable/qiqiu"  
  58.             zhy:borderRadius="60dp"  
  59.             zhy:type="round" >  
  60.         </com.zhy.view.RoundImageView>  
  61.     </LinearLayout>  
  62.   
  63. </ScrollView>  

没撒,ScrollView里面一个线性布局,里面一堆RoundImageView。

MainActivity
[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. package com.zhy.variousshapeimageview;  
  2.   
  3. import android.app.Activity;  
  4. import android.os.Bundle;  
  5. import android.view.View;  
  6. import android.view.View.OnClickListener;  
  7.   
  8. import com.zhy.view.RoundImageView;  
  9.   
  10. public class MainActivity extends Activity  
  11. {  
  12.     private RoundImageView mQiQiu;  
  13.     private RoundImageView mMeiNv ;   
  14.   
  15.     @Override  
  16.     protected void onCreate(Bundle savedInstanceState)  
  17.     {  
  18.         super.onCreate(savedInstanceState);  
  19.         setContentView(R.layout.activity_main);  
  20.           
  21.         mQiQiu = (RoundImageView) findViewById(R.id.id_qiqiu);  
  22.         mMeiNv = (RoundImageView) findViewById(R.id.id_meinv);  
  23.           
  24.         mQiQiu.setOnClickListener(new OnClickListener()  
  25.         {  
  26.             @Override  
  27.             public void onClick(View v)  
  28.             {  
  29.                 mQiQiu.setType(RoundImageView.TYPE_ROUND);  
  30.             }  
  31.         });  
  32.           
  33.         mMeiNv.setOnClickListener(new OnClickListener()  
  34.         {  
  35.               
  36.             @Override  
  37.             public void onClick(View v)  
  38.             {  
  39.                 mMeiNv.setBorderRadius(90);  
  40.             }  
  41.         });  
  42.     }  
  43.   
  44. }  

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


最后的效果图:



 

源码点击下载


0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 亿盛国际出金后不到账怎么办 玩游戏的界面很大怎么办电脑 文件缺失导致电脑无法启动怎么办 眼睛里拉出白丝怎么办 脚扭了脚面肿了怎么办 伪音唱歌嗓子疼怎么办 小孩吊水手肿了怎么办 棉质地的裙子起褶子怎么办 孕妇吃了马苋菜怎么办 三非黑人抓住了怎么办 33岁了写字好丑怎么办 裤子熨焦了发亮怎么办 黑裤子熨亮了怎么办 。P手机没有钤声怎么办 处处被小人其欠负怎么办??? 衣服开了一个口怎么办 脸上长痘痘留下的坑怎么办 脸部被打得皮肤怎么办 打脸引起耳朵疼怎么办 被打了耳痛耳鸣怎么办 苹果6视频锁屏怎么办 抠耳朵抠疼了怎么办 图库的相片没了怎么办 遇到打假牌的人怎么办 部队保障卡丢了怎么办 廊坊武警学院取消现役学员怎么办 孩子去当兵联系不上怎么办 军训戴眼镜晒痕怎么办 想进部队体检没过怎么办 大腿跟小腿不直怎么办 腿被车门夹了怎么办 脚出汗穿凉鞋滑怎么办 玩游戏手出汗屏幕滑怎么办 新买的鞋子臭怎么办 当公民利益受到侵犯怎么办 唇钉里面长肉怎么办 宝宝舔了一口酒怎么办 头被玻璃门撞了怎么办 30多了还一事无成 未来怎么办 27岁失业了该怎么办 无业证明不给开怎么办