最近收到一个开发悬浮球的需求,悬浮球上显示一张图片,一个TextView文本。这个悬浮球被裁切成圆形。
该如何实现呢?如果利用glide 或者SimpleDraweeView单独将图片裁切成圆形,只能将imageView裁切成圆形,其他元素也需要单独裁切,不是一种优雅的处理方式。
于是想到:
定制一个ViewGroup将这个ViewGroup裁切成圆形,ViewGroup内的元素也会被裁切成圆形,不就解决了吗?
效果如下图右下角的圆形悬浮球

目标:将ViewGroup进行圆形裁切
一、方案一
利用clipPath()将ViewGroup的canvas 裁成圆形
class RoundView(context:Context,attr: AttributeSet?):RelativeLayout(context,attr) {
private val mPath: Path = Path()
var mRectF = RectF()
init {
Log.d("feifei","-----setLayerType----")
setLayerType(View.LAYER_TYPE_SOFTWARE, null)
}
constructor(context: Context):this(context,null){
}
override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
super.onLayout(changed, l, t, r, b)
Log.d("feifei","onLayout changed:"+changed+",left:"+l+",top:"+t+",right:"+r+",bottom:"+b)
}
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
Log.d("feifei","onSizeChanged:"+w+",h:"+h)
mPath.reset();
mRectF.set(0F, 0F, w.toFloat(), h.toFloat());
mPath.addRoundRect(mRectF, w.toFloat()/2.0F, h.toFloat()/2.0F, Path.Direction.CW);
}
override fun dispatchDraw(canvas: Canvas?) {
tryDrawCircleWithClipPath(canvas)
}
/**
* 通过ClipPath的方式绘制遮罩
*/
fun tryDrawCircleWithClipPath(canvas: Canvas?){
canvas?.save()
Log.d("feifei","dispatchDraw:")
canvas?.clipPath(mPath)
super.dispatchDraw(canvas)
canvas?.restore()
}
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
Log.d("feifei","onDraw:")
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
Log.d("feifei","onMeasure:")
}
}
使用方式:
<com.sogou.teemo.busi_camera.menu.suspendball.RoundView
android:id="@+id/rl_RoundView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
>
<ImageView
android:id="@+id/iv_logo"
android:layout_width="@dimen/px80"
android:layout_height="@dimen/px80"
android:scaleType="centerCrop"
android:src="@drawable/camera_menu_default"
android:layout_centerHorizontal="true"
/>
<TextView
android:id="@+id/tv_pic_num"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/white"
android:text="20张"
android:textSize="@dimen/px18"
android:gravity="center_horizontal"
android:background="@color/color_99000000"
android:layout_alignBottom="@+id/iv_logo"
android:layout_centerHorizontal="true"
android:layout_alignRight="@+id/iv_logo"
android:layout_alignLeft="@+id/iv_logo"
android:paddingBottom="@dimen/px3"
/>
</com.sogou.teemo.busi_camera.menu.suspendball.RoundView>
方案一 非常方便的实现了圆形ViewGroup,可以将ViewGroup内的所有元素 都裁切成圆形
但是这个方案有一个缺点。
产品设计上RoundView 还要有一个旋转的动画,方案一产生的圆形ViewGroup 再旋转时会导致canvas?.clipPath(mPath)的圆心 和旋转的圆形不重合。旋转过程圆形失效。
于是尝试另一种方案Xfermode
方案二 利用Xfermode 实现
class RoundView(context:Context,attr: AttributeSet?):RelativeLayout(context,attr) {
private val mPath: Path = Path()
var mRectF = RectF()
init {
Log.d("feifei","-----setLayerType----")
setLayerType(View.LAYER_TYPE_SOFTWARE, null)
}
constructor(context: Context):this(context,null){
}
override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
super.onLayout(changed, l, t, r, b)
Log.d("feifei","onLayout changed:"+changed+",left:"+l+",top:"+t+",right:"+r+",bottom:"+b)
}
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
Log.d("feifei","onSizeChanged:"+w+",h:"+h)
mPath.reset();
mRectF.set(0F, 0F, w.toFloat(), h.toFloat());
mPath.addRoundRect(mRectF, w.toFloat()/2.0F, h.toFloat()/2.0F, Path.Direction.CW);
}
override fun dispatchDraw(canvas: Canvas?) {
// tryClipPath(canvas)
tryDrawCircleShade(canvas)
}
/**
* 通过ClipPath的方式绘制遮罩
*/
fun tryClipPath(canvas: Canvas?){
canvas?.save()
Log.d("feifei","dispatchDraw:")
canvas?.clipPath(mPath)
super.dispatchDraw(canvas)
canvas?.restore()
}
//外边框 白边宽度
var mBorderWidth = 2.0F
/**
* 绘制圆形遮罩
*/
fun tryDrawCircleShade(canvas: Canvas?){
//创建新的图层
val sc = canvas?.saveLayer(0F, 0F, canvas.getWidth().toFloat(), canvas.getHeight().toFloat(), null, Canvas.ALL_SAVE_FLAG)
super.dispatchDraw(canvas)
val paint = Paint(Paint.ANTI_ALIAS_FLAG)
paint.setAntiAlias(true)
paint.setColor(Color.WHITE)
paint.setStyle(Paint.Style.FILL)
val mask = Bitmap.createBitmap(measuredWidth, measuredHeight, Bitmap.Config.ARGB_8888)
val mNewCanvas = Canvas(mask)
mNewCanvas?.drawRoundRect(mRectF, measuredWidth/2.0f, measuredHeight/2.0f, paint)
paint.setXfermode(PorterDuffXfermode(PorterDuff.Mode.DST_IN))// 设置Xfermode模式
canvas?.drawBitmap(mask, 0F, 0F, paint)
paint?.setXfermode(null)
//绘制外层的圆圈
drawBorderCircle(canvas,paint)
//将新建图层内容绘制到 原有图层
canvas?.restoreToCount(sc!!)
}
fun drawBorderCircle(canvas: Canvas?,paint: Paint){
//画白色圆边
paint.setColor(Color.WHITE)
paint.setStyle(Paint.Style.STROKE)
paint.strokeWidth = mBorderWidth
canvas?.drawCircle(measuredWidth/2.0f,measuredHeight/2.0f,((measuredWidth-mBorderWidth)/2.0F).toFloat(),paint)
paint.setStyle(Paint.Style.FILL)
}
fun tryDST_IN_1(canvas: Canvas?){
val sc = canvas?.saveLayer(0F, 0F, canvas.getWidth().toFloat(), canvas.getHeight().toFloat(), null, Canvas.ALL_SAVE_FLAG)
val paint = Paint(Paint.ANTI_ALIAS_FLAG)
paint.setAntiAlias(true)
paint.setColor(Color.BLUE)
paint.setStyle(Paint.Style.FILL)
canvas?.drawRoundRect(mRectF, measuredWidth/2.0f, measuredHeight/2.0f, paint)
paint.setXfermode(PorterDuffXfermode(PorterDuff.Mode.SRC_IN))// 设置Xfermode模式
super.dispatchDraw(canvas)
// canvas?.drawBitmap(createMask(paint), 0F, 0F, paint)
canvas?.restoreToCount(sc!!)
paint?.setXfermode(null)
}
private fun createMask(paint:Paint): Bitmap {
val maskWidth = measuredWidth
val maskHeight = measuredHeight
val mask = Bitmap.createBitmap(maskWidth, maskHeight, Bitmap.Config.ARGB_8888)
val mNewCanvas = Canvas(mask)
mNewCanvas?.drawRoundRect(mRectF, measuredWidth/2.0f, measuredHeight/2.0f, paint)
return mask
}
/**
* 生成圆形Bitmap
* @return
*/
private fun makeCircle(): Bitmap {
val bitmap = Bitmap.createBitmap(measuredWidth, measuredHeight, Bitmap.Config.ARGB_8888)
val canvas = Canvas(bitmap)
val paint = Paint(Paint.ANTI_ALIAS_FLAG)
paint.color = Color.BLUE
paint.style = Paint.Style.FILL
val radius = measuredHeight / 2
canvas.drawCircle(measuredHeight.toFloat() / 2, measuredHeight.toFloat() / 2, radius.toFloat(), paint)
return bitmap
}
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
Log.d("feifei","onDraw:")
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
Log.d("feifei","onMeasure:")
}
}
使用方式:
<com.sogou.teemo.busi_camera.menu.suspendball.RoundView
android:id="@+id/rl_RoundView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
>
<ImageView
android:id="@+id/iv_logo"
android:layout_width="@dimen/px80"
android:layout_height="@dimen/px80"
android:scaleType="centerCrop"
android:src="@drawable/camera_menu_default"
android:layout_centerHorizontal="true"
/>
<TextView
android:id="@+id/tv_pic_num"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/white"
android:text="20张"
android:textSize="@dimen/px18"
android:gravity="center_horizontal"
android:background="@color/color_99000000"
android:layout_alignBottom="@+id/iv_logo"
android:layout_centerHorizontal="true"
android:layout_alignRight="@+id/iv_logo"
android:layout_alignLeft="@+id/iv_logo"
android:paddingBottom="@dimen/px3"
/>
</com.sogou.teemo.busi_camera.menu.suspendball.RoundView>
Xfermode 裁切图片原理 Xfermode
非常完美的实现了 圆形ViewGroup的裁切。
网友评论