关闭 x
IT技术网
    技 采 号
    ITJS.cn - 技术改变世界
    • 实用工具
    • 菜鸟教程
    IT采购网 中国存储网 科技号 CIO智库

    IT技术网

    IT采购网
    • 首页
    • 行业资讯
    • 系统运维
      • 操作系统
        • Windows
        • Linux
        • Mac OS
      • 数据库
        • MySQL
        • Oracle
        • SQL Server
      • 网站建设
    • 人工智能
    • 半导体芯片
    • 笔记本电脑
    • 智能手机
    • 智能汽车
    • 编程语言
    IT技术网 - ITJS.CN
    首页 » 安卓开发 »Android BitmapShader实现圆形和圆角图片

    Android BitmapShader实现圆形和圆角图片

    2014-12-23 00:00:00 出处:Healtheon的博客
    分享

    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

    < xml version="1.0" encoding="utf-8" >  
    <resources>  
    
        <attr name="borderRadius" format="dimension" />  
        <attr name="type">  
            <enum name="circle" value="0" />  
            <enum name="round" value="1" />  
        </attr>  
    
        <declare-styleable name="RoundImageView">  
            <attr name="borderRadius" />  
            <attr name="type" />  
        </declare-styleable>  
    
    </resources>

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


    2、获取自定义属性

    public class RoundImageView extends ImageView  
    {  
    
        /** 
         * 图片的类型,圆形or圆角 
         */  
        private int type;  
        private static final int TYPE_CIRCLE = 0;  
        private static final int TYPE_ROUND = 1;  
    
        /** 
         * 圆角大小的默认值 
         */  
        private static final int BODER_RADIUS_DEFAULT = 10;  
        /** 
         * 圆角的大小 
         */  
        private int mBorderRadius;  
    
        /** 
         * 绘图的Paint 
         */  
        private Paint mBitmapPaint;  
        /** 
         * 圆角的半径 
         */  
        private int mRadius;  
        /** 
         * 3x3 矩阵,主要用于缩小放大 
         */  
        private Matrix mMatrix;  
        /** 
         * 渲染图像,使用图像为绘制图形着色 
         */  
        private BitmapShader mBitmapShader;  
        /** 
         * view的宽度 
         */  
        private int mWidth;  
        private RectF mRoundRect;  
    
        public RoundImageView(Context context, AttributeSet attrs)  
        {  
            super(context, attrs);  
            mMatrix = new Matrix();  
            mBitmapPaint = new Paint();  
            mBitmapPaint.setAntiAlias(true);  
    
            TypedArray a = context.obtainStyledAttributes(attrs,  
                    R.styleable.RoundImageView);  
    
            mBorderRadius = a.getDimensionPixelSize(  
                    R.styleable.RoundImageView_borderRadius, (int) TypedValue  
                            .applyDimension(TypedValue.COMPLEX_UNIT_DIP,  
                                    BODER_RADIUS_DEFAULT, getResources()  
                                            .getDisplayMetrics()));// 默认为10dp  
            type = a.getInt(R.styleable.RoundImageView_type, TYPE_CIRCLE);// 默认为Circle  
    
            a.recycle();  
        }

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

    3、onMeasure

    @Override  
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)  
        {  
            Log.e("TAG", "onMeasure");  
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);  
    
            /** 
             * 假如类型是圆形,则强制改变view的宽高一致,以小值为准 
             */  
            if (type == TYPE_CIRCLE)  
            {  
                mWidth = Math.min(getMeasuredWidth(), getMeasuredHeight());  
                mRadius = mWidth / 2;  
                setMeasuredDimension(mWidth, mWidth);  
            }  
    
        }

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

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

    4、设置BitmapShader

    /** 
         * 初始化BitmapShader 
         */  
        private void setUpShader()  
        {  
            Drawable drawable = getDrawable();  
            if (drawable == null)  
            {  
                return;  
            }  
    
            Bitmap bmp = drawableToBitamp(drawable);  
            // 将bmp作为着色器,就是在指定区域内绘制bmp  
            mBitmapShader = new BitmapShader(bmp, TileMode.CLAMP, TileMode.CLAMP);  
            float scale = 1.0f;  
            if (type == TYPE_CIRCLE)  
            {  
                // 拿到bitmap宽或高的小值  
                int bSize = Math.min(bmp.getWidth(), bmp.getHeight());  
                scale = mWidth * 1.0f / bSize;  
    
            } else if (type == TYPE_ROUND)  
            {  
                // 假如图片的宽或者高与view的宽高不匹配,计算出需要缩放的比例;缩放后的图片的宽高,一定要大于我们view的宽高;所以我们这里取大值;  
                scale = Math.max(getWidth() * 1.0f / bmp.getWidth(), getHeight()  
                        * 1.0f / bmp.getHeight());  
            }  
            // shader的变换矩阵,我们这里主要用于放大或者缩小  
            mMatrix.setScale(scale, scale);  
            // 设置变换矩阵  
            mBitmapShader.setLocalMatrix(mMatrix);  
            // 设置shader  
            mBitmapPaint.setShader(mBitmapShader);  
        }

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

    /** 
         * drawable转bitmap 
         *  
         * @param drawable 
         * @return 
         */  
        private Bitmap drawableToBitamp(Drawable drawable)  
        {  
            if (drawable instanceof BitmapDrawable)  
            {  
                BitmapDrawable bd = (BitmapDrawable) drawable;  
                return bd.getBitmap();  
            }  
            int w = drawable.getIntrinsicWidth();  
            int h = drawable.getIntrinsicHeight();  
            Bitmap bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);  
            Canvas canvas = new Canvas(bitmap);  
            drawable.setBounds(0, 0, w, h);  
            drawable.draw(canvas);  
            return bitmap;  
        }

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

    5、绘制

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

    @Override  
        protected void onDraw(Canvas canvas)  
        {  
            if (getDrawable() == null)  
            {  
                return;  
            }  
            setUpShader();  
    
            if (type == TYPE_ROUND)  
            {  
                canvas.drawRoundRect(mRoundRect, mBorderRadius, mBorderRadius,  
                        mBitmapPaint);  
            } else  
            {  
                canvas.drawCircle(mRadius, mRadius, mRadius, mBitmapPaint);  
                // drawSomeThing(canvas);  
            }  
        }  
    
        @Override  
        protected void onSizeChanged(int w, int h, int oldw, int oldh)  
        {  
            super.onSizeChanged(w, h, oldw, oldh);  
            // 圆角图片的范围  
            if (type == TYPE_ROUND)  
                mRoundRect = new RectF(0, 0, getWidth(), getHeight());  
        }

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

    5、状态的存储与恢复

    当然了,假如内存不足,而恰好我们的Activity置于后台,不幸被重启,或者用户旋转屏幕造成Activity重启,我们的View应该也能尽可能的去保存自己的属性。

    状态保存什么用处呢?比如,现在一个的圆角大小是10dp,用户点击后变成50dp;当用户旋转以后,或者长时间置于后台以后,返回我们的Activity应该还是50dp;

    我们简单的存储一下,当前的type以及mBorderRadius

    private static final String STATE_INSTANCE = "state_instance";  
        private static final String STATE_TYPE = "state_type";  
        private static final String STATE_BORDER_RADIUS = "state_border_radius";  
    
        @Override  
        protected Parcelable onSaveInstanceState()  
        {  
            Bundle bundle = new Bundle();  
            bundle.putParcelable(STATE_INSTANCE, super.onSaveInstanceState());  
            bundle.putInt(STATE_TYPE, type);  
            bundle.putInt(STATE_BORDER_RADIUS, mBorderRadius);  
            return bundle;  
        }  
    
        @Override  
        protected void onRestoreInstanceState(Parcelable state)  
        {  
            if (state instanceof Bundle)  
            {  
                Bundle bundle = (Bundle) state;  
                super.onRestoreInstanceState(((Bundle) state)  
                        .getParcelable(STATE_INSTANCE));  
                this.type = bundle.getInt(STATE_TYPE);  
                this.mBorderRadius = bundle.getInt(STATE_BORDER_RADIUS);  
            } else  
            {  
                super.onRestoreInstanceState(state);  
            }  
    
        }

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

    同时我们也对外公布了两个方法,用于动态修改圆角大小和type

    public void setBorderRadius(int borderRadius)  
        {  
            int pxVal = dp2px(borderRadius);  
            if (this.mBorderRadius != pxVal)  
            {  
                this.mBorderRadius = pxVal;  
                invalidate();  
            }  
        }  
    
        public void setType(int type)  
        {  
            if (this.type != type)  
            {  
                this.type = type;  
                if (this.type != TYPE_ROUND && this.type != TYPE_CIRCLE)  
                {  
                    this.type = TYPE_CIRCLE;  
                }  
                requestLayout();  
            }  
    
        }  
    
        public int dp2px(int dpVal)  
        {  
            return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,  
                    dpVal, getResources().getDisplayMetrics());  
        }

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

    6、调用

    布局文件:

    <ScrollView xmlns:android="http://schemas.android.com/apk/res/android"  
        xmlns:tools="http://schemas.android.com/tools"  
        xmlns:zhy="http://schemas.android.com/apk/res/com.zhy.variousshapeimageview"  
        android:layout_width="match_parent"  
        android:layout_height="wrap_content" >  
    
        <LinearLayout  
            android:layout_width="match_parent"  
            android:layout_height="match_parent"  
            android:orientation="vertical" >  
    
            <com.zhy.view.RoundImageView  
                android:id="@+id/id_qiqiu"  
                android:layout_width="wrap_content"  
                android:layout_height="wrap_content"  
                android:layout_margin="10dp"  
                android:src="@drawable/qiqiu" >  
            </com.zhy.view.RoundImageView>  
    
            <com.zhy.view.RoundImageView  
                android:layout_width="200dp"  
                android:layout_height="200dp"  
                android:layout_margin="10dp"  
                android:src="@drawable/aa" >  
            </com.zhy.view.RoundImageView>  
    
            <com.zhy.view.RoundImageView  
                android:layout_width="wrap_content"  
                android:layout_height="wrap_content"  
                android:layout_margin="10dp"  
                android:src="@drawable/icon" >  
            </com.zhy.view.RoundImageView>  
    
            <com.zhy.view.RoundImageView  
                android:id="@+id/id_meinv"  
                android:layout_width="wrap_content"  
                android:layout_height="wrap_content"  
                android:layout_margin="10dp"  
                android:src="@drawable/aa"  
                zhy:borderRadius="20dp"  
                zhy:type="round" >  
            </com.zhy.view.RoundImageView>  
    
            <com.zhy.view.RoundImageView  
                android:layout_width="wrap_content"  
                android:layout_height="wrap_content"  
                android:layout_margin="10dp"  
                android:src="@drawable/icon"  
                zhy:borderRadius="40dp"  
                zhy:type="round" >  
            </com.zhy.view.RoundImageView>  
    
            <com.zhy.view.RoundImageView  
                android:layout_width="wrap_content"  
                android:layout_height="wrap_content"  
                android:layout_margin="10dp"  
                android:src="@drawable/qiqiu"  
                zhy:borderRadius="60dp"  
                zhy:type="round" >  
            </com.zhy.view.RoundImageView>  
        </LinearLayout>  
    
    </ScrollView>
    没撒,ScrollView里面一个线性布局,里面一堆RoundImageView。

    MainActivity

    package com.zhy.variousshapeimageview;  
    
    import android.app.Activity;  
    import android.os.Bundle;  
    import android.view.View;  
    import android.view.View.OnClickListener;  
    
    import com.zhy.view.RoundImageView;  
    
    public class MainActivity extends Activity  
    {  
        private RoundImageView mQiQiu;  
        private RoundImageView mMeiNv ;   
    
        @Override  
        protected void onCreate(Bundle savedInstanceState)  
        {  
            super.onCreate(savedInstanceState);  
            setContentView(R.layout.activity_main);  
    
            mQiQiu = (RoundImageView) findViewById(R.id.id_qiqiu);  
            mMeiNv = (RoundImageView) findViewById(R.id.id_meinv);  
    
            mQiQiu.setOnClickListener(new OnClickListener()  
            {  
                @Override  
                public void onClick(View v)  
                {  
                    mQiQiu.setType(RoundImageView.TYPE_ROUND);  
                }  
            });  
    
            mMeiNv.setOnClickListener(new OnClickListener()  
            {  
    
                @Override  
                public void onClick(View v)  
                {  
                    mMeiNv.setBorderRadius(90);  
                }  
            });  
        }  
    
    }

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

    最后的效果图:

    源码点击下载

    上一篇返回首页 下一篇

    声明: 此文观点不代表本站立场;转载务必保留本文链接;版权疑问请联系我们。

    别人在看

    正版 Windows 11产品密钥怎么查找/查看?

    还有3个月,微软将停止 Windows 10 的更新

    Windows 10 终止支持后,企业为何要立即升级?

    Windows 10 将于 2025年10 月终止技术支持,建议迁移到 Windows 11

    Windows 12 发布推迟,微软正全力筹备Windows 11 25H2更新

    Linux 退出 mail的命令是什么

    Linux 提醒 No space left on device,但我的空间看起来还有不少空余呢

    hiberfil.sys文件可以删除吗?了解该文件并手把手教你删除C盘的hiberfil.sys文件

    Window 10和 Windows 11哪个好?答案是:看你自己的需求

    盗版软件成公司里的“隐形炸弹”?老板们的“法务噩梦” 有救了!

    IT头条

    公安部:我国在售汽车搭载的“智驾”系统都不具备“自动驾驶”功能

    02:03

    液冷服务器概念股走强,博汇、润泽等液冷概念股票大涨

    01:17

    亚太地区的 AI 驱动型医疗保健:2025 年及以后的下一步是什么?

    16:30

    智能手机市场风云:iPhone领跑销量榜,华为缺席引争议

    15:43

    大数据算法和“老师傅”经验叠加 智慧化收储粮食尽显“科技范”

    15:17

    技术热点

    SQL汉字转换为拼音的函数

    windows 7系统无法运行Photoshop CS3的解决方法

    巧用MySQL加密函数对Web网站敏感数据进行保护

    MySQL基础知识简介

    Windows7和WinXP下如何实现不输密码自动登录系统的设置方法介绍

    windows 7系统ip地址冲突怎么办?windows 7系统IP地址冲突问题的

      友情链接:
    • IT采购网
    • 科技号
    • 中国存储网
    • 存储网
    • 半导体联盟
    • 医疗软件网
    • 软件中国
    • ITbrand
    • 采购中国
    • CIO智库
    • 考研题库
    • 法务网
    • AI工具网
    • 电子芯片网
    • 安全库
    • 隐私保护
    • 版权申明
    • 联系我们
    IT技术网 版权所有 © 2020-2025,京ICP备14047533号-20,Power by OK设计网

    在上方输入关键词后,回车键 开始搜索。Esc键 取消该搜索窗口。