利用Android中BitmapShader制作自带边框的圆形头像
作者:daisy 发布时间:2021-12-25 20:09:11
效果如下:
BitmapShader 的简单介绍
关于 Shader
是什么,Shader
的种类有哪几种以及如何使用不属于本文范畴,对这方面不是很了解的同学,建议先去学习一下 Shader
的基本使用。
BitmapShader
主要的作用就是 通过Paint对象,对 画布进行指定的Bitmap
填充,实现一系列效果,可以有以下三种模式进行选择
1.CLAMP
- 拉伸,这里拉伸的是图片的最后一个元素,不断地重复,这个效果,在图片比较小,而所要画的面积比较大的时候会比较明显。
2.REPEAT
- 重复,横向纵向不断地重复,不同于上一模式,这种模式在图片比较小不能满足要求是,会在横向纵向不断重复绘制图形。
3.MIRROR
- 翻转,这种模式和 REPEAT
是类似的,只不过这里的重复是翻转着重复,和折纸的效果差不多。
而我们将要使用的是 CLAMP
模式,因为只要我们对图形的大小进行控制,就可以避免图像进行拉伸。
具体实现介绍
为了自定义 图像,边框宽度和颜色,我们首先在 res/values 目录下,新建一个 attrs.xml文件,里面要书写的内容如下所示
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="MyCustomView">
<attr name="mborder_color" format="color"></attr>
<attr name="mborder_width" format="dimension"></attr>
<attr name="msrc" format="reference"></attr>
</declare-styleable>
</resources>
当然,在这里还可以添加一些其他的特性。既然定义了我们想要使用的特性,那么我们就要在自定义View
里面 解析这些属性并且加以利用,解析过程如下所示
TypedArray type = context.obtainStyledAttributes(attrs, R.styleable.MyCustomView);
mBorderColor = type.getColor(R.styleable.MyCustomView_mborder_color,0);
mDrawable = type.getDrawable(R.styleable.MyCustomView_msrc);
//将获得的 Drawable 转换成 Bitmap
BitmapDrawable bitmapDrawable = (BitmapDrawable) mDrawable;
mBitmap = bitmapDrawable.getBitmap();
mBorderWidth = type.getDimensionPixelSize(R.styleable.MyCustomView_mborder_width, 2);
值得注意的是 mSrc
属性的解析,由于获得是 Drawable
对象,所以我们需要将其转换为 Bitmap
对象。
下面就利用我们获得的 Bitmap
对象进行圆形头像的绘制,对 BitmapShader
和 Paint
的初始化如下所示
mSrcBitmap = Bitmap.createScaledBitmap(mBitmap, mWidth, mHeight, false);
mShader = new BitmapShader(mSrcBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
mPaint = new Paint();
mPaint.setShader(mShader);
mRadius = (mWidth - mBorderWidth * 2 - 4) / 2;
mCircleX = (mWidth) / 2;
mCircleY = (mHeight) / 2;
mSrcBitmap是对获得的图像进行适当的缩小或者放大,以适应我们对图形的要求,而这里的 mWidth
和 mHeight
又是什么呢?实际上就是我们在 定义视图在 layout_width
和 layout_height
中传递进来的值,不过在这里我对他们进行了处理,也就是选取最小值操作,这样的话就可以避免由于宽大于高或者高大于宽而造成图像填充不满指定区域的现象。值得注意的是,自定义视图,需要对 wrap_content
进行特殊处理,否则系统对该属性的视图不予以显示。至于如何进行处理,可以看看本例的源码,很简单,相信很多人一看就知道或者说早就了然于胸。
还有一点需要强调的是这里的 mRadius
,也就是将要绘制的圆的半径,为什么要减去边框的宽度 乘 2 呢? 要知道,我们的圆是根据 视图指定的宽度或者高度来画的,如果我们所画 的圆恰好是指定视图的内切圆,那么边框放在哪里呢?它肯定要被画在视图的外面,那样我们就看不到完整的边框了。所以,这么减去的意义在于为边框腾出空间。
经过以上操作,我们就已经将圆形头像绘制出来了,下面来绘制边框,其实非常简单,我只不过是又定义了一个 Paint
对象,并且利用它画了一个圆而已,画笔的初始化操作如下所示
mBorderPaint = new Paint();
mBorderPaint.setStyle(Paint.Style.STROKE);
mBorderPaint.setStrokeWidth(mBorderWidth);
mBorderPaint.setColor(mBorderColor);
mBorderPaint.setStrokeCap(Paint.Cap.ROUND);
好了,下面就可以在onDraw()
函数中,进行绘制了,如下所示
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawCircle(mCircleX, mCircleY, mRadius, mPaint);
canvas.drawCircle(mCircleX, mCircleY, mRadius, mBorderPaint);
}
这样,整个效果就算实现完毕了。下面来看看如何使用
<com.example.hwaphon.patheffecttest.MyView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:layout_marginRight="8dp"
app:mborder_color="@android:color/holo_green_light"
app:mborder_width="4dp"
app:msrc="@drawable/myview_test"/>
注意,一定要在容器中加上这么一句
xmlns:app=http://schemas.android.com/apk/res-auto
具体实现的核心代码
package com.example.hwaphon.patheffecttest;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Shader;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.View;
/**
* Created by Hwaphon on 2016/5/12.
*/
public class MyView extends View {
private Bitmap mBitmap;
private Drawable mDrawable;
private Bitmap mSrcBitmap;
private BitmapShader mShader;
private Paint mPaint;
private int mWidth, mHeight;
private int mRadius;
private int mCircleX, mCircleY;
private int mBorderColor;
private Paint mBorderPaint;
private int mBorderWidth;
public MyView(Context context) {
this(context, null);
}
public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray type = context.obtainStyledAttributes(attrs, R.styleable.MyCustomView);
mBorderColor = type.getColor(R.styleable.MyCustomView_mborder_color,0);
mDrawable = type.getDrawable(R.styleable.MyCustomView_msrc);
//将获得的 Drawable 转换成 Bitmap
BitmapDrawable bitmapDrawable = (BitmapDrawable) mDrawable;
mBitmap = bitmapDrawable.getBitmap();
mBorderWidth = type.getDimensionPixelSize(R.styleable.MyCustomView_mborder_width, 2);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
mWidth = measureWidth(widthMeasureSpec);
mHeight = measureHeight(heightMeasureSpec);
int temp = mWidth > mHeight ? mHeight : mWidth;
mWidth = mHeight = temp;
initView();
setMeasuredDimension(mWidth, mHeight);
}
private int measureHeight(int heightMeasureSpec) {
int size = MeasureSpec.getSize(heightMeasureSpec);
int sizeMode = MeasureSpec.getMode(heightMeasureSpec);
int result = 0;
if (sizeMode == MeasureSpec.EXACTLY) {
result = size;
} else {
result = 200;
if (sizeMode == MeasureSpec.AT_MOST) {
result = Math.min(result, size);
}
}
return result;
}
private int measureWidth(int widthMeasureSpec) {
int size = MeasureSpec.getSize(widthMeasureSpec);
int sizeMode = MeasureSpec.getMode(widthMeasureSpec);
int result = 0;
if (sizeMode == MeasureSpec.EXACTLY) {
result = size;
} else {
result = 200;
if (sizeMode == MeasureSpec.AT_MOST) {
result = Math.min(result, size);
}
}
return result;
}
private void initView() {
mSrcBitmap = Bitmap.createScaledBitmap(mBitmap, mWidth, mHeight, false);
mShader = new BitmapShader(mSrcBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
mPaint = new Paint();
mPaint.setShader(mShader);
mRadius = (mWidth - mBorderWidth * 2) / 2;
mCircleX = (mWidth) / 2;
mCircleY = (mHeight) / 2;
mBorderPaint = new Paint();
mBorderPaint.setStyle(Paint.Style.STROKE);
mBorderPaint.setStrokeWidth(mBorderWidth);
mBorderPaint.setColor(mBorderColor);
mBorderPaint.setStrokeJoin(Paint.Join.ROUND);
mBorderPaint.setStrokeCap(Paint.Cap.ROUND);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawCircle(mCircleX, mCircleY, mRadius, mPaint);
canvas.drawCircle(mCircleX, mCircleY, mRadius, mBorderPaint);
}
}
总结


猜你喜欢
- 资源加载器使用Java,您可以使用当前线程的classLoader并尝试加载文件,但是Spring Framework为您提供了更为优雅的解
- 基于这段时间折腾redis遇到了各种问题,想着整理一下。本文主要介绍基于Spring+Mybatis以注解的形式整合Redis。废话少说,进
- Android API Demos中有很多非常Nice的例子,这些例子的代码都写的很出色,如果大家把API Demos中的每个例子研究透了,
- 群主发普通红包,某群有多名成员,群主给成员发普通红包,普通红包的规则:群主的一笔金额,从群主余额中扣除,平均分成n等份,让成员领取;成员领取
- 在移动应用满天飞的时代,随着移动支付的盛行,很多应用中都集成了支付功能。之前的支付一直不是我负责,近期这个项目我负责订单模块少不了要做支付,
- 项目中用到WebView加上进度条放在顶部,让用户知道加载进度情况,可以提高用户体验:效果:布局:<RelativeLayoutand
- 前言Basic编码是标准的BASE64编码,用于处理常规的需求:输出的内容不添加换行符,而且输出的内容由字母加数字组成。最近做了个Web模版
- 前言最近业务开发部门因为开发环境和测试环境共用一个maven私仓,导致他们开发环境的API包和测试环境的API包发生了覆盖现象。于是他们向我
- 首先定义两个示例类ClassA,ClassB,用于后续的示例演示package cn.lzrabbit;public class Class
- C# 自带的HttpWebRequest效率太低,对于自组HTTP封包不好操作。在写超级SQL注入工具时,研究了很长一段时间如何使用Sock
- 在平时的开发中,我们会经常遇到这样一个需求,要在页面通过一个『导出』按钮把查询出的数据导出到 Excel 表格中。本文即为实现上述需求的一个
- @Autowired注入static接口问题@Autowired自动注入普通service很方便如:@Componentpublic cla
- 我们都知道Android应用软件基本上都会用到登录注册功能,那么对一个一个好的登录注册模块进行封装就势在必行了。这里给大家介绍一下我的第一个
- 根据UGUI的射线检测机制获取当前鼠标下的UI:/// <summary> /// 获取鼠标停留处UI
- 这篇文章既介绍一个技术,又记录一个逐渐探索发现的过程,以供大家参考。缘起注意到Java的依赖注入DI规范(起初以为是CDI规范,然后发现是D
- 概述在Winform中从后台添加控件相对比较容易,但是在WPF中,我们知道界面是通过XAML编写的,如何把后台写好的控件动态添加到前台呢?本
- 前言大家都知道Android Studio目前已经更新到2.0 Preview 6了,作为Google大力推崇的开发工具,相对于Eclips
- Android横竖屏要解决的问题应该就两个:一。布局问题;二。重新载入问题。1.布局问题:如果不想让软件在横竖屏之间切换,最简单的办法就是在
- 在C#中,在处理字符串拼接的时候,使用StringBuilder的效率会比硬拼接字符串高很多。到底有多高,如下:static void Ma
- 本文实例为大家分享了Android实现毛玻璃效果弹出菜单动画的具体代码,供大家参考,具体内容如下仿ios上屏幕下方向上滑出来的一个模糊菜单,