Android 自定义控件-高仿猎豹清理大师自定义内存开口圆环控件
概述
详细
一、概述
看见猎豹清理大师的内存开口圆环比例进度 挺有意思的,于是就是想自己实现下这样的效果,于是反编译了猎豹清理
大师的app看了下,原来是有两张图,于是脑子里就过了下思路,利用上下两张图,旋转上面张图以及使用
PorterDuffXfermode 来设置合适的渲染模式,就可以达到效果。下面看看咱们的效果吧
二、演示效果图
三、Xfermode渲染模式简介:
xfermode影响在Canvas已经有的图像上绘制新的颜色的方式
* 正常的情况下,在图像上绘制新的形状,如果新的Paint不是透明的,那么会遮挡下面的颜色.
* 如果新的Paint是透明的,那么会被染成下面的颜色
下面的Xfermode子类可以改变这种行为:
AvoidXfermode 指定了一个颜色和容差,强制Paint避免在它上面绘图(或者只在它上面绘图)。
PixelXorXfermode 当覆盖已有的颜色时,应用一个简单的像素XOR操作。
PorterDuffXfermode 这是一个非常强大的转换模式,使用它,可以使用图像合成的16条Porter-Duff规则的任意一条来控制Paint如何与已有的Canvas图像进行交互。
这里不得不提到那个经典的图:
上面的16种模式的说明如下:
从上面我们可以看到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
取两图层全部区域,交集部分变为透明色
四、自定义开口圆环View的实现
1、初始化绘制所需的画笔,字体颜色、大小等变量
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | public XCArcProgressBar(Context context, AttributeSet attrs, int defStyleAttr) { super (context, attrs, defStyleAttr); // TODO Auto-generated constructor stub degrees = 0 ; paint = new Paint(); //从attrs.xml中获取自定义属性和默认值 TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.XCRoundProgressBar); textColor =typedArray.getColor(R.styleable.XCRoundProgressBar_textColor, Color.RED); textSize = typedArray.getDimension(R.styleable.XCRoundProgressBar_textSize, 15 ); max = typedArray.getInteger(R.styleable.XCRoundProgressBar_max, 100 ); isDisplayText =typedArray.getBoolean(R.styleable.XCRoundProgressBar_textIsDisplayable, true ); typedArray.recycle(); } |
2、在onDraw()中绘制出来
在onDraw()方法中利用PorterDuffXfermode渲染模式绘制两张开口圆环Bitmap,并计算前景图的旋转角度,从而达到效果图效果。
首先先绘制底部背景图,然后绘制进度前景图,最后利用PorterDuffXfermode的渲染模式和旋转角度比例来进行前景图和背景图的遮罩处理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 | @Override protected void onDraw(Canvas canvas) { // TODO Auto-generated method stub super .onDraw(canvas); int width = getWidth(); int height = getHeight(); int centerX = getWidth() / 2 ; // 获取中心点X坐标 int centerY = getHeight() / 2 ; // 获取中心点Y坐标 Bitmap bitmap = Bitmap.createBitmap(width, height, Config.ARGB_8888); Canvas can = new Canvas(bitmap); // 绘制底部背景图 bmpTemp = Utils.decodeCustomRes(getContext(), R.drawable.arc_bg); float dstWidth = ( float ) width; float dstHeight = ( float ) height; int srcWidth = bmpTemp.getWidth(); int srcHeight = bmpTemp.getHeight(); can.setDrawFilter( new PaintFlagsDrawFilter( 0 , Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG)); // 抗锯齿 Bitmap bmpBg = Bitmap.createScaledBitmap(bmpTemp, width, height, true ); can.drawBitmap(bmpBg, 0 , 0 , null ); // 绘制进度前景图 Matrix matrixProgress = new Matrix(); matrixProgress.postScale(dstWidth / srcWidth, dstHeight / srcWidth); bmpTemp = Utils.decodeCustomRes(getContext(), R.drawable.arc_progress); Bitmap bmpProgress = Bitmap.createBitmap(bmpTemp, 0 , 0 , srcWidth, srcHeight, matrixProgress, true ); degrees = progress * 270 / max - 270 ; //遮罩处理前景图和背景图 can.save(); can.rotate(degrees, centerX, centerY); paint.setAntiAlias( true ); paint.setXfermode( new PorterDuffXfermode(Mode.SRC_ATOP)); can.drawBitmap(bmpProgress, 0 , 0 , paint); can.restore(); if ((-degrees) >= 85 ) { int posX = 0 ; int posY = 0 ; if ((-degrees) >= 270 ) { posX = 0 ; posY = 0 ; } else if ((-degrees) >= 225 ) { posX = centerX / 2 ; posY = 0 ; } else if ((-degrees) >= 180 ) { posX = centerX; posY = 0 ; } else if ((-degrees) >= 135 ) { posX = centerX; posY = 0 ; } else if ((-degrees) >= 85 ) { posX = centerX; posY = centerY; } if ((-degrees) >= 225 ) { can.save(); Bitmap dst = bitmap .createBitmap(bitmap, 0 , 0 , centerX, centerX); paint.setAntiAlias( true ); paint.setXfermode( new PorterDuffXfermode(Mode.SRC_ATOP)); Bitmap src = bmpBg.createBitmap(bmpBg, 0 , 0 , centerX, centerX); can.drawBitmap(src, 0 , 0 , paint); can.restore(); can.save(); dst = bitmap.createBitmap(bitmap, centerX, 0 , centerX, height); paint.setAntiAlias( true ); paint.setXfermode( new PorterDuffXfermode(Mode.SRC_ATOP)); src = bmpBg.createBitmap(bmpBg, centerX, 0 , centerX, height); can.drawBitmap(src, centerX, 0 , paint); can.restore(); } else { can.save(); Bitmap dst = bitmap.createBitmap(bitmap, posX, posY, width - posX, height - posY); paint.setAntiAlias( true ); paint.setXfermode( new PorterDuffXfermode(Mode.SRC_ATOP)); Bitmap src = bmpBg.createBitmap(bmpBg, posX, posY, width - posX, height - posY); can.drawBitmap(src, posX, posY, paint); can.restore(); } } //绘制遮罩层位图 canvas.drawBitmap(bitmap, 0 , 0 , null ); // 画中间进度百分比字符串 paint.reset(); paint.setStrokeWidth( 0 ); paint.setColor(textColor); paint.setTextSize(textSize); paint.setTypeface(Typeface.DEFAULT_BOLD); int percent = ( int ) ((( float ) progress / ( float ) max) * 100 ); // 计算百分比 float textWidth = paint.measureText(percent + "%" ); // 测量字体宽度,需要居中显示 if (isDisplayText && percent != 0 ) { canvas.drawText(percent + "%" , centerX - textWidth / 2 , centerX + textSize / 2 - 25 , paint); } //画底部开口处标题文字 paint.setTextSize(textSize/ 2 ); textWidth = paint.measureText(title); canvas.drawText(title, centerX-textWidth/ 2 , height-textSize/ 2 , paint); } |
3、设置比例进度的同步接口方法,主要供刷新进度比例用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | /** * 设置进度,此为线程安全控件,由于考虑多线的问题,需要同步 * 刷新界面调用postInvalidate()能在非UI线程刷新 * @author caizhiming */ public synchronized void setProgress( int progress) { if (progress < 0 ){ throw new IllegalArgumentException( "progress must more than 0" ); } if (progress > max){ this .progress = progress; } if (progress <= max){ this .progress = progress; postInvalidate(); } } |
四、项目代码目录结构图
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· 使用C#创建一个MCP客户端
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· Windows编程----内核对象竟然如此简单?