需求描述:
实现一个带圆角的imageView,要求自定义view方式实现。
需求分析:
我们可以自定义一个view,继承ImageView,并只需要重写ImageView的onDraw()方法即可。带圆角意味着要把控件的四个顶角做一个裁切。由于不能影响imageview主体的正常绘制, 想到了可以在执行super.onDraw()方法之前进行对画布canvas的裁切。 这样接下来绘制的控件就只会在裁切范围内。从而实现了我们的需求。
代码实现:
实现流程步骤
1、定义好自定义view用到的的属性;
2、自定义view,使之继承ImageView,并在其构造函数中获取自定义属性值;
3、重写ImageView的onDraw()方法, 在执行super.onDraw()之前利用Path创建一个路径对象,然后使用画布沿着path路径裁切;
4、调用super.onDraw()绘制imageview的主体内容
1、定义自定义view属性:
在attrs.xml中声明如下
<resources>
<declare-styleable name="RoundImageView">
<attr name="radius" format="dimension"/>
<attr name="leftTopRadius" format="dimension"/>
<attr name="rightTopRadius" format="dimension"/>
<attr name="leftBottomRadius" format="dimension"/>
<attr name="rightBottomRadius" format="dimension"/>
</declare-styleable>
...
</resources>
这里有一点需要注意下,控件的尺寸相关属性的类型需要使用dimension,而不是int或float。
2、自定义view获取属性值 & 重写onDraw方法
class RoundImageView: AppCompatImageView {
private var rightBottomRadius: Int
private var leftBottomRadius: Int
private var rightTopRadius: Int
private var leftTopRadius: Int
private var radius: Int =0
constructor(context: Context): this(context, null)
constructor(context: Context, attributeSet: AttributeSet?): this(context, attributeSet, 0)
constructor(context: Context, attributeSet: AttributeSet?, defStyleAttr: Int)
:super(context, attributeSet, defStyleAttr) {
var typeArray = context.obtainStyledAttributes(attributeSet, R.styleable.RoundImageView)
val defaultRadius = 0
radius = typeArray.getDimensionPixelOffset(R.styleable.RoundImageView_radius, defaultRadius)
leftTopRadius = typeArray.getDimensionPixelOffset(
R.styleable.RoundImageView_leftTopRadius,
defaultRadius
)
rightTopRadius = typeArray.getDimensionPixelOffset(
R.styleable.RoundImageView_rightTopRadius,
defaultRadius
)
leftBottomRadius = typeArray.getDimensionPixelOffset(
R.styleable.RoundImageView_leftBottomRadius,
defaultRadius
)
rightBottomRadius = typeArray.getDimensionPixelOffset(
R.styleable.RoundImageView_rightBottomRadius,
defaultRadius
)
if (radius != 0) {
if (leftTopRadius == 0) {
leftTopRadius = radius
}
if (rightTopRadius == 0) {
rightTopRadius = radius;
}
if (leftBottomRadius == 0) {
leftBottomRadius = radius;
}
if (rightBottomRadius == 0) {
rightBottomRadius = radius;
}
}
typeArray.recycle()
}
override fun onDraw(canvas: Canvas?) {
// 保证图片宽高大于圆角宽高, 获取圆角的宽高
// 取横着大的长度
val maxLeft = Math.max(leftTopRadius, leftBottomRadius)
val maxRight = Math.max(rightTopRadius, rightBottomRadius)
val minWidth = maxLeft + maxRight
// 取竖着大的长度
val maxTop = Math.max(leftTopRadius, rightTopRadius)
val maxBottom = Math.max(leftBottomRadius, rightBottomRadius)
val minHeight = maxTop + maxBottom
if (width > minWidth && height > minHeight) {
val path = Path()
//四个角:右上,右下,左下,左上
path.moveTo(leftTopRadius.toFloat(), 0F)
path.lineTo((width - rightTopRadius).toFloat() , 0F)
path.quadTo(width.toFloat(), 0F, width.toFloat(), rightTopRadius.toFloat())
path.lineTo(width.toFloat(), (height - rightBottomRadius).toFloat())
path.quadTo(width.toFloat(), height.toFloat(), (width - rightBottomRadius).toFloat(), height.toFloat())
path.lineTo(leftBottomRadius.toFloat(), height.toFloat())
path.quadTo(0F, height.toFloat(), 0F, (height - leftBottomRadius).toFloat())
path.lineTo(0F, leftTopRadius.toFloat())
path.quadTo(0F, 0F, leftTopRadius.toFloat(), 0F)
canvas!!.clipPath(path)
/*val paint = Paint()
paint.setColor(Color.BLUE)
paint.style = Paint.Style.STROKE
paint.strokeWidth = 2f
canvas!!.drawPath(path, paint)*/
}
super.onDraw(canvas)
}
}
1、当声明了radius属性,并且某个方向的radiusX属性没有声明时, 则使用radius的值替代该放下的radiusX;
2、画布裁切需要在super.onDraw(canvas)之前执行;
3、本程序对裁切进行了一些限制(假如不符合你的需求,你可以去掉这些限制并写上你的限制条件)
限制1:自定义view控件的宽度必须大于(view左半边最大半径和view右半边最大半径之和),即
必须满足条件:
宽度 :view的width > (maxLeft + maxRight)
高度 :同理
//左半边的最大半径
val maxLeft = Math.max(leftTopRadius, leftBottomRadius)
//右半边的最大半径
val maxRight = Math.max(rightTopRadius, rightBottomRadius)
//maxLeft + maxRight 就是view需要满足的最小宽度,同理height
4、创建一个Path路径对象来描述画布应该沿着什么路径进行裁切。执行canvas.clipPath(),之后在canvas上进行的绘制操作就只会在这部分区域进行绘制了。
这里强调一下绘制圆角使用的是贝塞尔曲线(没接触过的话可以参考下贝塞尔曲线简单介绍)。
3、xml中引用自定义view
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center"
>
<com.example.helloworld.views.RoundImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:leftTopRadius="70dp"
app:leftBottomRadius="60dp"
app:rightTopRadius="25dp"
app:rightBottomRadius="60dp"
android:src="@drawable/maps"
/>
</LinearLayout>
原图:
程序效果:
解释一下:
1、可以看到drawable图片的像素值为419 x 418;文章来源:https://www.toymoban.com/news/detail-427977.html
2、xml中RoundImageView的宽高都是设置为wrap_content, 这意味着控件的宽高是根据控件内部装载的内容大小来决定的。在onDraw方法中, 已经测量出了RoundImageView的宽高,即为图片的宽高(系统在onMeasure时已经测量出来尺寸)。在xml中设置的四个方向的半径值满足程序的条件,即view的width > (maxLeft + maxRight)===>419 > (70 + 60)x2,这里要乘以2是因为xml使用的是dp作为长度单位,测试机器的1dp=2px,高度同理。 所以会裁切RoundImageView的四个顶角。文章来源地址https://www.toymoban.com/news/detail-427977.html
到了这里,关于Android 自定义ImageView实现圆角的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!