Android自定义View之三角,五角星,圆形,心形图片实现

转载请标明出处:http://blog.csdn.NET/joker_ya/article/details/38589677

好吧,写之前扯扯。如果是大神的话,可以忽略此文档。有兴趣的话也可以看看。这是本人的第一篇技术博客吧(话说也谈不上是什么特别的技术哭哭哭)!因为之前在写一个项目需要用到图片。但是把一张图片原封不动的src入ImageView里面去,看起来怪别扭的。因此不想走平民路线,于是就冒出来想把图片弄成三角形的,五角星或圆形的想法。说干就干,所以赶紧上网查了查怎么实现该想法。在此过程中也发现了很多问题,所以今天写出来和大家分享一下。本文是根据大牛鸿洋和alan_biao的博客编写粗来的。原理都和他们的一样,只是在图形上改了改,改成能画出三角形,五角星,心形的形状。大家可以去看看他们得博客,写的都很不错的。鸿洋的博客:http://blog.csdn.net/lmj623565791?viewmode=contents alan_biao的博客:http://blog.csdn.net/alan_biao?viewmode=contents 好吧就扯到这里吧!!写这篇博客的目的一个是为了和大家分享,另一个就是记录自己的收获和成长。

接下来就是如何实现的了。啥也不说了,先上图:

首先是原图:

接下来就是效果图了:


怎么样?是不是比什么都不弄直接src进去的要好呢?根据该方法大家可以实现最新版QQ的消息列表界面:

说了那么多了,还没给你们讲讲是怎么样的一个原理呢!接下来就给大家讲解一下实现该功能的原理:

其实主要是靠画笔paint中的一个方法:paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));来实现的。

下面简单的介绍下Xfermode和PorterDuffXfermode:


该Mode是设置两张图片相交时的模式。
在正常的情况下,在已有的图像上绘图将会在其上面添加一层新的形状。如果新的Paint是完全不透明的, 那么它将完全遮挡住下面的Paint;如果它是部分透明的,那么它将会被染上下面的颜色。
而setXfermode就可以来解决这个问题 .

[java]  view plain  copy
  1. Canvas canvas = new Canvas(dstBitmap);    
  2. paint.setXfermode(new PorterDuffXfermode(Mode.SRC_IN));      
  3. canvas.drawBitmap(srcBitmap, 0f, 0f, paint);     
canvas原有的图片可以理解为背景,就是dst;
新画上去的图片可以理解为前景,就是src。

下图可以让大家更好的理解PorterDuffXfermode的Mode:

从上面我们可以看到PorterDuff.Mode为枚举类,一共有16个枚举值:
1.PorterDuff.Mode.CLEAR  
  所绘制不会提交到画布上。
2.PorterDuff.Mode.SRC
    显示上层绘制图片
3.PorterDuff.Mode.DST
  显示下层绘制图片
4.PorterDuff.Mode.SRC_OVER
  正常绘制显示,上下层绘制叠盖。
5.PorterDuff.Mode.DST_OVER
  上下层都显示。下层居上显示。
6.PorterDuff.Mode.SRC_IN
    取两层绘制交集。显示上层。
7.PorterDuff.Mode.DST_IN
  取两层绘制交集。显示下层。
8.PorterDuff.Mode.SRC_OUT
  取上层绘制非交集部分。
9.PorterDuff.Mode.DST_OUT
  取下层绘制非交集部分。
10.PorterDuff.Mode.SRC_ATOP
  取下层非交集部分与上层交集部分
11.PorterDuff.Mode.DST_ATOP
  取上层非交集部分与下层交集部分
12.PorterDuff.Mode.XOR
  异或:去除两图层交集部分
13.PorterDuff.Mode.DARKEN
  取两图层全部区域,交集部分颜色加深
14.PorterDuff.Mode.LIGHTEN
  取两图层全部,点亮交集部分颜色
15.PorterDuff.Mode.MULTIPLY
  取两图层交集部分叠加后颜色
16.PorterDuff.Mode.SCREEN
  取两图层全部区域,交集部分变为透明色

有没有心动了?好了。接下来就是看看如何实现的了。

新建一个名为ShapeViewDemo的项目。目录如下:


在res的文件夹下新建一个名为attrs.xml文件用来定义自定义属性。

[html]  view plain  copy
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <resources>  
  3.     <declare-styleable name="shapeimageview">  
  4.         <attr name="border_size" format="dimension" />  
  5.         <attr name="in_border_color" format="color" />  
  6.         <attr name="out_border_color" format="color"/>  
  7.         <attr name="shape_type" format="string"/>  
  8.     </declare-styleable>  
  9. </resources>  

接下来新建一个ShapeImageView.Java

[java]  view plain  copy
  1. package com.example.shapeimageviewdemo;  
  2.   
  3. import android.content.Context;  
  4. import android.content.res.TypedArray;  
  5. import android.graphics.Bitmap;  
  6. import android.graphics.Canvas;  
  7. import android.graphics.Paint;  
  8. import android.graphics.Path;  
  9. import android.graphics.PorterDuff;  
  10. import android.graphics.PorterDuffXfermode;  
  11. import android.graphics.Bitmap.Config;  
  12. import android.graphics.drawable.BitmapDrawable;  
  13. import android.graphics.drawable.Drawable;  
  14. import android.graphics.drawable.NinePatchDrawable;  
  15. import android.util.AttributeSet;  
  16. import android.widget.ImageView;  
  17.   
  18. /** 
  19.  *  
  20.  * @author Joker_Ya 
  21.  *  
  22.  */  
  23. public class ShapeImageView extends ImageView {  
  24.   
  25.     private Context mContext;  
  26.   
  27.     private int border_size = 0;// 边框厚度  
  28.     private int in_border_color = 0;// 内圆边框颜色  
  29.     private int out_border_color = 0;// 外圆边框颜色  
  30.     private int defColor = 0xFFFFFFFF;// 默认颜色  
  31.   
  32.     private int width = 0;// 控件的宽度  
  33.     private int height = 0;// 控件的高度  
  34.   
  35.     private String shape_type;// 形状的类型  
  36.   
  37.     public ShapeImageView(Context context) {  
  38.         super(context);  
  39.         // TODO Auto-generated constructor stub  
  40.         this.mContext = context;  
  41.     }  
  42.   
  43.     public ShapeImageView(Context context, AttributeSet attrs) {  
  44.         super(context, attrs);  
  45.         // TODO Auto-generated constructor stub  
  46.         this.mContext = context;  
  47.         setAttributes(attrs);  
  48.     }  
  49.   
  50.     public ShapeImageView(Context context, AttributeSet attrs, int defStyle) {  
  51.         super(context, attrs, defStyle);  
  52.         // TODO Auto-generated constructor stub  
  53.         this.mContext = context;  
  54.         setAttributes(attrs);  
  55.     }  
  56.   
  57.     /** 
  58.      * 获得自定义属性 
  59.      *  
  60.      * @param attrs 
  61.      */  
  62.     private void setAttributes(AttributeSet attrs) {  
  63.         // TODO Auto-generated method stub  
  64.         TypedArray mArray = mContext.obtainStyledAttributes(attrs,  
  65.                 R.styleable.shapeimageview);  
  66.         // 得到边框厚度,否则返回0  
  67.         border_size = mArray.getDimensionPixelSize(  
  68.                 R.styleable.shapeimageview_border_size, 0);  
  69.         // 得到内边框颜色,否则返回默认颜色  
  70.         in_border_color = mArray.getColor(  
  71.                 R.styleable.shapeimageview_in_border_color, defColor);  
  72.         // 得到外边框颜色,否则返回默认颜色  
  73.         out_border_color = mArray.getColor(  
  74.                 R.styleable.shapeimageview_out_border_color, defColor);  
  75.         // 得到形状的类型  
  76.         shape_type = mArray.getString(R.styleable.shapeimageview_shape_type);  
  77.   
  78.         mArray.recycle();// 回收mArray  
  79.     }  
  80.   
  81.     @Override  
  82.     protected void onDraw(Canvas canvas) {  
  83.         // TODO Auto-generated method stub  
  84.         // super.onDraw(canvas); 必须去掉该行或注释掉,否则会出现两张图片  
  85.         // 得到传入的图片  
  86.         Drawable drawable = getDrawable();  
  87.         if (drawable == null) {  
  88.             return;  
  89.         }  
  90.         if (getWidth() == 0 || getHeight() == 0) {  
  91.             return;  
  92.         }  
  93.         this.measure(00);  
  94.         if (drawable.getClass() == NinePatchDrawable.class) {// 如果该传入图片是.9格式的图片  
  95.             return;  
  96.         }  
  97.   
  98.         // 将图片转为位图  
  99.         Bitmap mBitmap = ((BitmapDrawable) drawable).getBitmap();  
  100.   
  101.         Bitmap cpBitmap = mBitmap.copy(Bitmap.Config.ARGB_8888, true);  
  102.         // 得到画布宽高  
  103.         width = getWidth();  
  104.         height = getHeight();  
  105.   
  106.         int radius = 0;//  
  107.         // 判断是否是圆形  
  108.         if ("round".equals(shape_type)) {  
  109.             // 如果内圆边框和外圆边框的颜色不等于默认颜色,则说明该圆有两个边框  
  110.             if (in_border_color != defColor && out_border_color != defColor) {  
  111.                 // 计算出半径  
  112.                 radius = (width < height ? width : height) / 2 - 2  
  113.                         * border_size;  
  114.                 // 画内圆边框  
  115.                 drawCircleBorder(canvas, radius + border_size / 2,  
  116.                         in_border_color);  
  117.                 // 画外圆边框  
  118.                 drawCircleBorder(canvas,  
  119.                         radius + border_size + border_size / 2,  
  120.                         out_border_color);  
  121.             }// 如果内圆边框颜色不等于默认颜色,则说明该圆有一个边框  
  122.             else if (in_border_color != defColor  
  123.                     && out_border_color == defColor) {  
  124.                 radius = (width < height ? width : height) / 2 - border_size;  
  125.   
  126.                 drawCircleBorder(canvas, radius + border_size / 2,  
  127.                         in_border_color);  
  128.             }// 如果外圆边框颜色不等于默认颜色,则说明该圆有一个边框  
  129.             else if (in_border_color == defColor  
  130.                     && out_border_color != defColor) {  
  131.                 radius = (width < height ? width : height) / 2 - border_size;  
  132.   
  133.                 drawCircleBorder(canvas, radius + border_size / 2,  
  134.                         out_border_color);  
  135.             } else {// 没有边框  
  136.                 radius = (width < height ? width : height) / 2;  
  137.             }  
  138.         } else {  
  139.             radius = (width < height ? width : height) / 2;  
  140.         }  
  141.   
  142.         Bitmap shapeBitmap = drawShapeBitmap(cpBitmap, radius);  
  143.         canvas.drawBitmap(shapeBitmap, width / 2 - radius, height / 2 - radius,  
  144.                 null);  
  145.     }  
  146.   
  147.     /** 
  148.      * 画出指定形状的图片 
  149.      *  
  150.      * @param cpBitmap 
  151.      * @param radius 
  152.      * @return 
  153.      */  
  154.     private Bitmap drawShapeBitmap(Bitmap bmp, int radius) {  
  155.         // TODO Auto-generated method stub  
  156.         Bitmap squareBitmap;// 根据传入的位图截取合适的正方形位图  
  157.         Bitmap scaledBitmap;// 根据diameter对截取的正方形位图进行缩放  
  158.         int diameter = radius * 2;  
  159.         // 传入位图的宽高  
  160.         int w = bmp.getWidth();  
  161.         int h = bmp.getHeight();  
  162.         // 为了防止宽高不相等,造成圆形图片变形,因此截取长方形中处于中间位置最大的正方形图片  
  163.         int squarewidth = 0, squareheight = 0;// 矩形的宽高  
  164.         int x = 0, y = 0;  
  165.         if (h > w) {// 如果高>宽  
  166.             squarewidth = squareheight = w;  
  167.             x = 0;  
  168.             y = (h - w) / 2;  
  169.             // 截取正方形图片  
  170.             squareBitmap = Bitmap.createBitmap(bmp, x, y, squarewidth,  
  171.                     squareheight);  
  172.         } else if (h < w) {// 如果宽>高  
  173.             squarewidth = squareheight = h;  
  174.             x = (w - h) / 2;  
  175.             y = 0;  
  176.             squareBitmap = Bitmap.createBitmap(bmp, x, y, squarewidth,  
  177.                     squareheight);  
  178.         } else {  
  179.             squareBitmap = bmp;  
  180.         }  
  181.         // 对squareBitmap进行缩放为diameter边长的正方形位图  
  182.         if (squareBitmap.getWidth() != diameter  
  183.                 || squareBitmap.getHeight() != diameter) {  
  184.             scaledBitmap = Bitmap.createScaledBitmap(squareBitmap, diameter,  
  185.                     diameter, true);  
  186.         } else {  
  187.             scaledBitmap = squareBitmap;  
  188.         }  
  189.   
  190.         Bitmap outputbmp = Bitmap.createBitmap(scaledBitmap.getWidth(),  
  191.                 scaledBitmap.getHeight(), Config.ARGB_8888);  
  192.         Canvas canvas = new Canvas(outputbmp);// 创建一个相同大小的画布  
  193.         Paint paint = new Paint();// 定义画笔  
  194.         paint.setAntiAlias(true);// 设置抗锯齿  
  195.         paint.setFilterBitmap(true);  
  196.         paint.setDither(true);  
  197.         canvas.drawARGB(0000);  
  198.   
  199.         if ("star".equals(shape_type)) {// 如果绘制的形状为五角星形  
  200.             Path path = new Path();  
  201.             float radian = degree2Radian(36);// 36为五角星的角度  
  202.             float radius_in = (float) (radius * Math.sin(radian / 2) / Math  
  203.                     .cos(radian)); // 中间五边形的半径  
  204.   
  205.             path.moveTo((float) (radius * Math.cos(radian / 2)), 0);// 此点为多边形的起点  
  206.             path.lineTo((float) (radius * Math.cos(radian / 2) + radius_in  
  207.                     * Math.sin(radian)),  
  208.                     (float) (radius - radius * Math.sin(radian / 2)));  
  209.             path.lineTo((float) (radius * Math.cos(radian / 2) * 2),  
  210.                     (float) (radius - radius * Math.sin(radian / 2)));  
  211.             path.lineTo((float) (radius * Math.cos(radian / 2) + radius_in  
  212.                     * Math.cos(radian / 2)),  
  213.                     (float) (radius + radius_in * Math.sin(radian / 2)));  
  214.             path.lineTo(  
  215.                     (float) (radius * Math.cos(radian / 2) + radius  
  216.                             * Math.sin(radian)), (float) (radius + radius  
  217.                             * Math.cos(radian)));  
  218.             path.lineTo((float) (radius * Math.cos(radian / 2)),  
  219.                     (float) (radius + radius_in));  
  220.             path.lineTo(  
  221.                     (float) (radius * Math.cos(radian / 2) - radius  
  222.                             * Math.sin(radian)), (float) (radius + radius  
  223.                             * Math.cos(radian)));  
  224.             path.lineTo((float) (radius * Math.cos(radian / 2) - radius_in  
  225.                     * Math.cos(radian / 2)),  
  226.                     (float) (radius + radius_in * Math.sin(radian / 2)));  
  227.             path.lineTo(0, (float) (radius - radius * Math.sin(radian / 2)));  
  228.             path.lineTo((float) (radius * Math.cos(radian / 2) - radius_in  
  229.                     * Math.sin(radian)),  
  230.                     (float) (radius - radius * Math.sin(radian / 2)));  
  231.   
  232.             path.close();// 使这些点构成封闭的多边形  
  233.             canvas.drawPath(path, paint);  
  234.         } else if ("triangle".equals(shape_type)) {// 如果绘制的形状为三角形  
  235.             Path path = new Path();  
  236.   
  237.             path.moveTo(00);  
  238.             path.lineTo(diameter / 2, diameter);  
  239.             path.lineTo(diameter, 0);  
  240.   
  241.             path.close();   
  242.             canvas.drawPath(path, paint);  
  243.         } else if ("heart".equals(shape_type)) {// 如果绘制的形状为心形  
  244.             Path path = new Path();  
  245.   
  246.             path.moveTo(diameter / 2, diameter / 5);  
  247.             path.quadTo(diameter, 0, diameter / 2, diameter / 1.0f);  
  248.             path.quadTo(00, diameter / 2, diameter / 5);  
  249.   
  250.             path.close();  
  251.             canvas.drawPath(path, paint);  
  252.         } else {// 这是默认形状,圆形  
  253.             // 绘制圆形  
  254.             canvas.drawCircle(scaledBitmap.getWidth() / 2,  
  255.                     scaledBitmap.getHeight() / 2, scaledBitmap.getWidth() / 2,  
  256.                     paint);  
  257.         }  
  258.         // 设置Xfermode的Mode  
  259.         paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));  
  260.         canvas.drawBitmap(scaledBitmap, 00, paint);  
  261.   
  262.         bmp = null;  
  263.         squareBitmap = null;  
  264.         scaledBitmap = null;  
  265.         return outputbmp;  
  266.   
  267.     }  
  268.   
  269.     /** 
  270.      * 角度转弧度公式 
  271.      *  
  272.      * @param degree 
  273.      * @return 
  274.      */  
  275.     private float degree2Radian(int degree) {  
  276.         // TODO Auto-generated method stub  
  277.         return (float) (Math.PI * degree / 180);  
  278.     }  
  279.   
  280.     /** 
  281.      * 如果图片为圆形,这该方法为画出圆形图片的有色边框 
  282.      *  
  283.      * @param canvas 
  284.      * @param radius 边框半径 
  285.      * @param color 边框颜色 
  286.      */  
  287.     private void drawCircleBorder(Canvas canvas, int radius, int color) {  
  288.         // TODO Auto-generated method stub  
  289.         Paint paint = new Paint();  
  290.   
  291.         paint.setAntiAlias(true);// 抗锯齿  
  292.         paint.setFilterBitmap(true);  
  293.         paint.setDither(true);  
  294.         paint.setColor(color);// 设置画笔颜色  
  295.         paint.setStyle(Paint.Style.STROKE);// 设置画笔的style为STROKE:空心  
  296.         paint.setStrokeWidth(border_size);// 设置画笔的宽度  
  297.         // 画出空心圆,也就是边框  
  298.         canvas.drawCircle(width / 2, height / 2, radius, paint);  
  299.     }  
  300.   
  301. }  
好了 ShapeImageView.java写完了,有没有发现其原理很简单呢?在此过程中有一点大家要注意一下,那就是我们重写Ondraw(Canvas canvas)方法时一定要把super.onDraw(canvas);注释掉或去掉,否则会出现两张图片叠在一起。不要问我为什么。

最后附上Activity_main.xml

[html]  view plain  copy
  1. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  2.     xmlns:myview="http://schemas.android.com/apk/res-auto"  
  3.     xmlns:tools="http://schemas.android.com/tools"  
  4.     android:layout_width="match_parent"  
  5.     android:layout_height="match_parent"  
  6.     android:paddingBottom="@dimen/activity_vertical_margin"  
  7.     android:paddingLeft="@dimen/activity_horizontal_margin"  
  8.     android:paddingRight="@dimen/activity_horizontal_margin"  
  9.     android:paddingTop="@dimen/activity_vertical_margin"  
  10.     android:orientation="vertical"  
  11.     tools:context=".MainActivity" >  
  12.     <!-- 没有设置shape_type,默认为无边框圆形 -->  
  13.     <com.example.shapeimageviewdemo.ShapeImageView   
  14.         android:layout_width="60dip"  
  15.         android:layout_height="60dip"  
  16.         android:src="@drawable/girl"  
  17.         />  
  18.     <!-- 令 shape_type="triangle"为三角形-->  
  19.     <com.example.shapeimageviewdemo.ShapeImageView   
  20.         android:layout_width="80dip"  
  21.         android:layout_height="80dip"  
  22.         android:layout_marginTop="10dip"  
  23.         android:src="@drawable/girl"  
  24.         myview:shape_type="triangle"  
  25.         />  
  26.     <!-- 令 shape_type="star"为五角星-->  
  27.     <com.example.shapeimageviewdemo.ShapeImageView   
  28.         android:layout_width="100dip"  
  29.         android:layout_height="100dip"  
  30.         android:layout_marginTop="10dip"  
  31.         android:src="@drawable/girl"  
  32.         myview:shape_type="star"  
  33.         />  
  34.     <!-- 令 shape_type="round"为圆形,并设置边框厚度和内外边框颜色-->  
  35.     <com.example.shapeimageviewdemo.ShapeImageView   
  36.         android:layout_width="100dip"  
  37.         android:layout_height="100dip"  
  38.         android:layout_marginTop="10dip"  
  39.         android:src="@drawable/girl"  
  40.         myview:border_size="2dip"  
  41.         myview:in_border_color="#EE0000"  
  42.         myview:out_border_color="#00EEEE"  
  43.         myview:shape_type="round"  
  44.         />  
  45.     <!-- 令 shape_type="heart"为心形 -->  
  46.     <com.example.shapeimageviewdemo.ShapeImageView   
  47.         android:layout_width="100dip"  
  48.         android:layout_height="100dip"  
  49.         android:layout_marginTop="10dip"  
  50.         android:src="@drawable/girl"  
  51.         myview:shape_type="heart"  
  52.         />  
  53. </LinearLayout>  
由于用到了自定义属性,因此要在主layout里加上xmlns:myview="http://schemas.android.com/apk/res-auto",否则会报错。

至此,如何将一张图片弄成三角,五角,圆形或心形的图片的全部技术就给大家讲解了。希望对大家有所帮助,也希望大家能理解。当然不仅仅是三角,五角,圆形或心形的形状。只要你想的到的都能弄出来(特殊形状的图片除外),就看你敢不敢了。

由于本人是第一次写博客,如果文中有什么地方出现错误或不理解的可以在下面回复中指出来。谢谢

下面是源码下载地址:

ShapeImageViewDemo

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值