要件の説明:
角が丸い imageView を実装するには、カスタム ビュー メソッドが必要です。
需要分析:
ビューをカスタマイズして ImageView を継承し、ImageView の onDraw() メソッドを書き直すだけで済みます。丸みを帯びた角とは、コントロールの上部の 4 つの角をカットする必要があることを意味します。imageview本体の通常の描画には影響しないため、super.onDraw()メソッドを実行する前にキャンバスを切り取ることができると考えられます。このようにして、次に描画されるコントロールはクリッピング範囲内にのみ収まります。したがって、私たちのニーズを満たします。
コード:
プロセスステップの実装
1. カスタム ビューで使用される属性を定義します。
2. ビューをカスタマイズし、ImageView を継承させ、そのコンストラクターでカスタム属性値を取得します。
3. ImageView の onDraw() メソッドを書き換え、Path を使用してパス オブジェクトを作成してから super.onDraw() を実行し、キャンバスを使用してパス パスに沿ってカットします。
4. super.onDraw() を呼び出して、imageview のメイン コンテンツを描画します。
1. カスタム ビュー属性を定義します。
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>
ここで注意すべきことの 1 つは、コントロールのサイズ関連のプロパティの型は、int または float の代わりに次元を使用する必要があるということです。
2.ビューをカスタマイズして属性値を取得し、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: カスタム ビュー コントロールの幅は、(ビューの左半分の最大半径とビューの右半分の最大半径の合計) より大きい必要があります。
条件を満たす必要があります:
幅: ビューの幅 > (maxLeft + maxRight)
身長:同じ
//左半边的最大半径
val maxLeft = Math.max(leftTopRadius, leftBottomRadius)
//右半边的最大半径
val maxRight = Math.max(rightTopRadius, rightBottomRadius)
//maxLeft + maxRight 就是view需要满足的最小宽度,同理height
4. キャンバスを切り取るパスを記述する Path オブジェクトを作成します。canvas.clipPath()を実行すると、キャンバスへの描画操作はこの部分の領域のみに描画されます。
ここでは、角を丸くするためにベジエ曲線が使用されていることが強調されています (まだ触れていない場合は、ベジエ曲線の簡単な紹介を参照してください)。
3. xml でカスタム ビューを参照する
<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. 描画可能な画像のピクセル値が 419 x 418 であることがわかります。
2. xml の RoundImageView の幅と高さは wrap_content に設定されます。これは、コントロールの幅と高さが、コントロール内にロードされたコンテンツのサイズに従って決定されることを意味します。onDraw メソッドでは、RoundImageView の幅と高さ、つまり画像の幅と高さが測定されています (システムは onMeasure で既にサイズを測定しています)。xmlで設定した4方向の半径値はプログラムの条件、つまりビューの幅>(maxLeft+maxRight)===>419>(70+60)×2、ここでは掛け算xml は長さの単位に dp を使用しているため 2 ずつ、テスト マシンの 1dp=2px で、高さは同じです。したがって、RoundImageView の上 4 隅がトリミングされます。