关闭 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和不足之处,欢迎您的指出,共同进步。

    最后的效果图:

    源码点击下载

    上一篇返回首页 下一篇

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

    别人在看

    电脑屏幕不小心竖起来了?别慌,快捷键搞定

    Destoon 模板存放规则及语法参考

    Destoon系统常量与变量

    Destoon系统目录文件结构说明

    Destoon 系统安装指南

    Destoon会员公司主页模板风格添加方法

    Destoon 二次开发入门

    Microsoft 将于 2026 年 10 月终止对 Windows 11 SE 的支持

    Windows 11 存储感知如何设置?了解Windows 11 存储感知开启的好处

    Windows 11 24H2 更新灾难:系统升级了,SSD固态盘不见了...

    IT头条

    Synology 更新 ActiveProtect Manager 1.1 以增强企业网络弹性和合规性

    00:43

    新的 Rubrik Agent Cloud 加速了可信的企业 AI 代理部署

    00:34

    宇树科技 G1人形机器人,拉动一辆重达1.4吨的汽车

    00:21

    Cloudera 调查发现,96% 的企业已将 AI 集成到核心业务流程中,这表明 AI 已从竞争优势转变为强制性实践

    02:05

    投资者反对马斯克 1 万亿美元薪酬方案,要求重组特斯拉董事会

    01:18

    技术热点

    大型网站的 HTTPS 实践(三):基于协议和配置的优化

    ubuntu下右键菜单添加新建word、excel文档等快捷方式

    Sublime Text 简明教程

    用户定义SQL Server函数的描述

    怎么在windows 7开始菜单中添加下载选项?

    SQL Server 2016将有哪些功能改进?

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

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