在Android中使用Java Canvas的Squircle

时间:2018-02-27 02:35:27

标签: java android xml canvas shape

我被要求研究这种称为Squircle的形状。它非常类似圆角矩形或带圆角的正方形。然而,这不是理想的形状。我怎样才能做到这一点?

2 个答案:

答案 0 :(得分:1)

https://github.com/MicroRJ/Android-Canvas-Squircle

我想出了自己的解决方案。我没有使用任何技巧,即使对于RecyclerViews,此方法也非常有效。

这就是我的做法。

首先,我制作了一个类,其中包含绘图的所有逻辑,并且具有 该过程更加有效。

object SuperEllipsePerformanceCalculations {

private val bitmapForSizes = ArrayList<Pair<Bitmap, Int>>()

fun getBitmap(w: Int, h: Int, p: Int, paint: Paint): Bitmap {

    bitmapForSizes.forEach {
        /**
         * Check if there are other bitmaps with the same specifications, to avoid creating
         * and recalculating, this increases performance drastically.
         */
        if (it.first.width == w && it.first.height == h && it.second == p) {
            return it.first
        }
    }

    /**
     * If no Bitmaps were found create a new one, and store for other views
     * that might use it
     */
    val newB = getSquircleBitmapBackground(w, h, p, paint)
    bitmapForSizes.add(Pair(newB, p))

    log("New bitmap added Total: ${bitmapForSizes.size}")
    return newB

}


private fun getSquircleBitmapBackground(w: Int, h: Int, p: Int, paint: Paint): Bitmap {
    val b = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888)
    val c = Canvas(b)
    c.translate(w / 2f, h / 2f)
    c.drawPath(getPath((w / 2) - p, (h / 2) - p), paint)
    return b
}


//todo fix path not closing
private fun getPath(radX: Int, radY: Int): Path {
    val corners = 0.6

    var l = 0.0

    var angle: Double

    val path = Path()

    for (i in 0 until 360) {
        angle = Math.toRadians(l)
        val x = getX(radX, angle, corners)
        val y = getY(radY, angle, corners)
        path.moveTo(x, y)
        l++
        angle = Math.toRadians(l)
        val x2 = getX(radX, angle, corners)
        val y2 = getY(radY, angle, corners)
        path.lineTo(x2, y2)
    }

    path.close()

    return path

}

private fun getX(radX: Int, angle: Double, corners: Double) =
    (Math.pow(abs(cos(angle)), corners) * radX * sgn(cos(angle))).toFloat()

private fun getY(radY: Int, angle: Double, corners: Double) =
    (Math.pow(abs(sin(angle)), corners) * radY * sgn(sin(angle))).toFloat()

private fun sgn(value: Double) = if (value > 0.0) 1.0 else if (value < 0.0) -1.0 else 0.0

/**
 * I suggest you call this method on the activity's on destroy event
 */
fun release() {
    bitmapForSizes.forEach { it.first.recycle() }
    bitmapForSizes.clear()
}

然后我创建了一个自定义ImageView

open class SuperEllipseImageView : ImageView {

private val paint = Paint()

private var shapePadding = 0
private var w = 0
private var h = 0

private var squircleBitmap: Bitmap? = null


override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
    super.onSizeChanged(w, h, oldw, oldh)
    this.w = w
    this.h = h

    /**
     * Request a new squircle bitmap, this is a smart method and it will only return a new Bitmap
     * instance if it cannot find a previously created Bitmap with the same size (width and height),
     * this will increase performance
     */
    squircleBitmap = getBitmap(this.h, this.w, shapePadding, paint)

    postInvalidate()
}


init {

    /**
     * You can change this to any color you want
     */
    val typedValue = TypedValue()
    val theme = context!!.theme
    theme.resolveAttribute(R.attr.colorAccentTheme, typedValue, true)


    paint.color = typedValue.data
    paint.strokeWidth = 6f
    /**
     * Here's the problem, the shape is not being filled, only the stroke
     * is drawn, I have a few theories of what's happening.
     */
    paint.style = Paint.Style.FILL_AND_STROKE
    paint.isAntiAlias = true

    /**
     * I suggest the shape padding be no less that the stroke width to prevent
     * the shape borders/outskirts from going out of bounds
     */
    shapePadding = paint.strokeWidth.toInt()


}


override fun onDraw(canvas: Canvas?) {
    if (squircleBitmap == null) {
        return
    }
    /**
     * Draw a previously instantiated Bitmap instead of a path, this will increase performance drastically
     */
    canvas?.drawBitmap(squircleBitmap!!, 0f, 0f, null)
    super.onDraw(canvas)

}

}

一旦完成所有设置,便将自定义视图添加到布局中。

<?xml version="1.0" encoding="utf-8"?>
<com.devrj.helium.custom.SuperEllipseImageView
 android:scaleX="0"
 android:scaleY="0"
 android:id="@+id/superEllipseImageView"
 style="@style/settings_chip"
 xmlns:android="http://schemas.android.com/apk/res/android"/>

就我而言,我在具有复杂动画且一切正常的回收站视图中使用了此布局。我认为这非常有效,因为它使用了保存的位图实例。

我强烈建议您查看销售代表,这一点更加清楚。

这是我的特定输出。

Code output

答案 1 :(得分:0)

您可以创建Shape Drawable并设置转角半径。