需求:
1:实现圆形图片底部添加标签,标签分为直线标签和圆弧标签
2:圆角图片,以及圆弧图片
思路:
- 1:为了方便扩展继承 ShapeableImageView(方便处理图片居中以及切割展示)
- 2:采用ViewOutlineProvider方案切割圆形圆角(支持一切View控件,限制版本为>=21)
- 3:绘制半圆和圆形,然后平移位置实现圆弧标签(椭圆方案,圆弧形状不好控制,放弃)
目标版本:>=21
代码:
/**
*@Desc:圆形头像以及标签 和圆角图片
*
*@Author: wkq
*
*@Time: 2024/11/7 9:48
*
*/
class TipsIconView : ShapeableImageView {
var mContext: Context
var mPaint: Paint
var textPaint: TextPaint
private var mRadius = 0f
private var mBottomSize = 0.70f
private var mTextSize = 12f
private var mTextColor = Color.WHITE
private var mBottomColor = Color.BLACK
//圆形
private var TYPE_CYCLEE = 0
//圆形标签直线
private var TYPE_CYCLE_LINE = 1
//圆形标签圆弧
private var TYPE_CYCLE_ELLIPSE = 2
//圆角矩形
private var TYPE_CORNER_RECT = 3
//圆弧矩形
private var TYPE_CIRCLE_RECT = 4
private var mType = 0
private var mText = "直播中"
constructor(context: Context) : this(context, null)
constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, -1)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(
context, attrs, defStyleAttr
) {
mContext = context
val tp = mContext.obtainStyledAttributes(attrs, R.styleable.TipsIconViewStyle)
//圆弧矩形角度
mRadius = tp.getFloat(R.styleable.TipsIconViewStyle_mRectRadius, 0f)
//底部标签开始位置(高度的百分比)
mBottomSize = tp.getFloat(R.styleable.TipsIconViewStyle_mBottomSize, 0.75f)
//类型
mType = tp.getInteger(R.styleable.TipsIconViewStyle_mType, 0)
//文字颜色
mTextColor = tp.getColor(R.styleable.TipsIconViewStyle_mTextColor, Color.WHITE)
//文字大小
mTextSize = tp.getDimension(R.styleable.TipsIconViewStyle_mTextSize, 12f)
//底部标签颜色
mBottomColor = tp.getColor(R.styleable.TipsIconViewStyle_mBottomColor, Color.BLACK)
tp.recycle()
mPaint = Paint()
mPaint.isAntiAlias = true
mPaint.style = Paint.Style.FILL
mPaint.color = mBottomColor
//文字画笔
textPaint = TextPaint(Paint.ANTI_ALIAS_FLAG)
textPaint.setTypeface(Typeface.create(SERIF, Typeface.NORMAL));
textPaint.density = mContext.resources.getDisplayMetrics().density
textPaint.isAntiAlias = true
textPaint.color = mTextColor
textPaint.textSize
textPaint.textSize = mTextSize
//文字虚化以及 ViewOutlineProvider 需要配置
setLayerType(LAYER_TYPE_SOFTWARE, null);
initView()
}
private fun initView() {
if (Build.VERSION.SDK_INT >= 21) {
val outlineProvider: ViewOutlineProvider = object : ViewOutlineProvider() {
override fun getOutline(view: View, outline: Outline) {
val width = view.width
val height = view.height
if (mType == TYPE_CYCLEE || mType == TYPE_CYCLE_LINE || mType == TYPE_CYCLE_ELLIPSE) {
//圆形
val rect: Rect = Rect(0, 0, width, height)
outline.setOval(rect) // API>=21
} else if (mType == TYPE_CIRCLE_RECT) {
// 圆形矩形
val rect: Rect = Rect(0, 0, width, height)
outline.setRoundRect(rect, height.toFloat() / 2)
} else {
//圆角
val rect: Rect = Rect(0, 0, width, height)
outline.setRoundRect(rect, mRadius)
}
}
}
clipToOutline = true
setOutlineProvider(outlineProvider)
}
}
override fun draw(canvas: Canvas) {
super.draw(canvas)
if (mType == TYPE_CYCLE_LINE) {
processLineTranslateY(canvas)
} else if (mType == TYPE_CYCLE_ELLIPSE) {
processTranslateCycle(canvas)
}
}
/**
* 平移方式实现
* @param canvas Canvas
*/
private fun processLineTranslateY(canvas: Canvas) {
canvas.save()
canvas.translate(0f, (mBottomSize - 0.5f) * height)
canvas.saveLayerAlpha(
0f, 0f, (width).toFloat(), (height).toFloat(), 255, Canvas.ALL_SAVE_FLAG
)
val left = 0f
val top = 0f
val right = width.toFloat()
val bottom = height.toFloat()
val halfCycle = RectF(left, top, right, bottom)
canvas.drawArc(halfCycle, 0f, 180f, true, mPaint)
canvas.restore()
drawText(canvas, (1 - mBottomSize) / 2 + 0.5f)
}
fun setTips(tips:String){
mText=tips
invalidate()
}
/**
* 平移方式实现
* @param canvas Canvas
*/
private fun processTranslateCycle(canvas: Canvas) {
canvas.save()
canvas.translate(0f, (mBottomSize) * height)
canvas.saveLayerAlpha(
0f, 0f, (width).toFloat(), (height).toFloat(), 255, Canvas.ALL_SAVE_FLAG
)
canvas.drawCircle(
(width / 2.0).toFloat(), (height / 2.0).toFloat(), (height / 2.0).toFloat(), mPaint
)
canvas.restore()
drawText(canvas, (1 - mBottomSize) / 2)
}
/**
* 椭圆方式实现
* @param canvas Canvas
*/
private fun processEllipse(canvas: Canvas) {
val left = 0f
val top = mBottomSize * height
val right = width.toFloat()
val bottom = height.toFloat()
val ty = RectF(left, top, right, bottom)
val mPaint = Paint()
mPaint.isAntiAlias = true
mPaint.style = Paint.Style.FILL
mPaint.color = mBottomColor
val path = Path()
path.addOval(ty, Path.Direction.CCW);
canvas.drawPath(path, mPaint)
drawText(canvas, (1 - mBottomSize) / 2 + mBottomSize)
}
/**
* 绘制圆弧上的文字
* @param canvas Canvas
*/
private fun drawText(canvas: Canvas, textHSize: Float) {
var text =
TextUtils.ellipsize(mText, textPaint as TextPaint, 200f, TextUtils.TruncateAt.END)
.toString()
// 文字X轴起始位置
val mWidth = textPaint.measureText(text) //获取文字宽度
val X = (getWidth() - mWidth) / 2
// 文字baseline位置
val fontMetrics = textPaint.fontMetrics
val textHSize = textHSize
val baseline =
(height * textHSize).toFloat() - (fontMetrics.descent + fontMetrics.ascent) / 2
//裁剪的范围(矩形的四条边)
val left = X.toInt()
val top = 0
val right = (left + width).toInt()
val bottom = height
val rect = Rect(left, top, right, bottom)
canvas.clipRect(rect) //掏空
canvas.drawText(text, X, baseline, textPaint)
}
}
自定义属性:
<declare-styleable name="TipsIconViewStyle">
<attr name="mRectRadius" format="float"/>
<attr name="mType" format="integer">
<!--圆形-->
<enum name="circle" value="0"/>
<enum name="circle_tips_line" value="1"/>
<enum name="circle_tips_ellipse" value="2"/>
<!--圆角矩形-->
<enum name="corner_rect" value="3"/>
<!--圆形矩形-->
<enum name="circle_rect" value="4"/>
</attr>
<attr name="mTextSize" format="dimension"/>
<attr name="mTextColor" format="color"/>
<attr name="mBottomColor" format="color"/>
<attr name="mBottomSize" format="float"/>
</declare-styleable>
总结:
采用ViewOutlineProvider思路,实现了圆形和圆角形状,以及圆形头像标签的功能.基于此方式可以实现其他View的圆形以及圆角展示