Come utilizzare Android Canvas per disegnare un rettangolo con solo gli angoli in alto a sinistra e in alto a destra arrotondati?

StackOverflow https://stackoverflow.com/questions/5896234

  •  29-10-2019
  •  | 
  •  

Domanda

Ho trovato una funzione per i rettangoli con tutti e 4 gli angoli che sono rotondi, ma voglio avere solo i primi 2 angoli rotondi.Cosa posso fare?

canvas.drawRoundRect(new RectF(0, 100, 100, 300), 6, 6, paint);
.

È stato utile?

Soluzione

È possibile disegnare quel pezzo per pezzo utilizzando le funzioni drawLine() e drawArc() da Canvas.

Altri suggerimenti

Usa un percorso. Ha il vantaggio di funzionare per API inferiori a 21 (anche Arc è limitato in questo modo, motivo per cui ho quad). Il che è un problema perché non tutti hanno ancora Lollipop. È tuttavia possibile specificare un RectF e impostare i valori con quello e utilizzare arc per tornare all'API 1, ma in tal caso non si otterrebbe l'utilizzo di uno statico (senza dichiarare un nuovo oggetto per creare l'oggetto).

Disegnare un rettangolo arrotondato:

    path.moveTo(right, top + ry);
    path.rQuadTo(0, -ry, -rx, -ry);
    path.rLineTo(-(width - (2 * rx)), 0);
    path.rQuadTo(-rx, 0, -rx, ry);
    path.rLineTo(0, (height - (2 * ry)));
    path.rQuadTo(0, ry, rx, ry);
    path.rLineTo((width - (2 * rx)), 0);
    path.rQuadTo(rx, 0, rx, -ry);
    path.rLineTo(0, -(height - (2 * ry)));
    path.close();

Come funzione completa:

static public Path RoundedRect(float left, float top, float right, float bottom, float rx, float ry, boolean conformToOriginalPost) {
    Path path = new Path();
    if (rx < 0) rx = 0;
    if (ry < 0) ry = 0;
    float width = right - left;
    float height = bottom - top;
    if (rx > width/2) rx = width/2;
    if (ry > height/2) ry = height/2;
    float widthMinusCorners = (width - (2 * rx));
    float heightMinusCorners = (height - (2 * ry));

    path.moveTo(right, top + ry);
    path.rQuadTo(0, -ry, -rx, -ry);//top-right corner
    path.rLineTo(-widthMinusCorners, 0);
    path.rQuadTo(-rx, 0, -rx, ry); //top-left corner
    path.rLineTo(0, heightMinusCorners);

    if (conformToOriginalPost) {
        path.rLineTo(0, ry);
        path.rLineTo(width, 0);
        path.rLineTo(0, -ry);
    }
    else {
        path.rQuadTo(0, ry, rx, ry);//bottom-left corner
        path.rLineTo(widthMinusCorners, 0);
        path.rQuadTo(rx, 0, rx, -ry); //bottom-right corner
    }

    path.rLineTo(0, -heightMinusCorners);

    path.close();//Given close, last lineto can be removed.

    return path;
}

Ti consigliamo di allineare fino a quei bit d'angolo, piuttosto che quadruplicarli. Questo è ciò che fa l'impostazione true per conformToOriginalPost. Basta allineare al punto di controllo lì.

Se vuoi fare tutto questo ma non ti importa delle cose pre-Lollipop, e insisti urgentemente che se i tuoi rx e ry sono abbastanza alti, dovrebbe disegnare un cerchio.

@TargetApi(Build.VERSION_CODES.LOLLIPOP)
static public Path RoundedRect(float left, float top, float right, float bottom, float rx, float ry, boolean conformToOriginalPost) {
    Path path = new Path();
    if (rx < 0) rx = 0;
    if (ry < 0) ry = 0;
    float width = right - left;
    float height = bottom - top;
    if (rx > width/2) rx = width/2;
    if (ry > height/2) ry = height/2;
    float widthMinusCorners = (width - (2 * rx));
    float heightMinusCorners = (height - (2 * ry));

    path.moveTo(right, top + ry);
    path.arcTo(right - 2*rx, top, right, top + 2*ry, 0, -90, false); //top-right-corner
    path.rLineTo(-widthMinusCorners, 0);
    path.arcTo(left, top, left + 2*rx, top + 2*ry, 270, -90, false);//top-left corner.
    path.rLineTo(0, heightMinusCorners);
    if (conformToOriginalPost) {
        path.rLineTo(0, ry);
        path.rLineTo(width, 0);
        path.rLineTo(0, -ry);
    }
    else {
        path.arcTo(left, bottom - 2 * ry, left + 2 * rx, bottom, 180, -90, false); //bottom-left corner
        path.rLineTo(widthMinusCorners, 0);
        path.arcTo(right - 2 * rx, bottom - 2 * ry, right, bottom, 90, -90, false); //bottom-right corner
    }

    path.rLineTo(0, -heightMinusCorners);

    path.close();//Given close, last lineto can be removed.
    return path;
}

Quindi, conformToOriginalPost disegna effettivamente un rettangolo arrotondato senza i due bit inferiori arrotondati.

 arcquadimage

Disegnerei due rettangoli:

canvas.drawRect(new RectF(0, 110, 100, 290), paint);
canvas.drawRoundRect(new RectF(0, 100, 100, 200), 6, 6, paint);

O qualcosa del genere, basta sovrapporli in modo che gli angoli superiori siano arrotondati.Preferibilmente dovresti scrivere un metodo per questo

Ho cambiato questa risposta in modo che tu possa impostare quale angolo vuoi arrotondare e quale vuoi che sia nitido.funziona anche su pre-lolipop

Esempio di utilizzo :

solo gli angoli in alto a destra e in basso a destra sono arrotondati

 Path path = RoundedRect(0, 0, fwidth , fheight , 5,5,
                     false, true, true, false);
 canvas.drawPath(path,myPaint);

RoundRect:

    public static Path RoundedRect(
            float left, float top, float right, float bottom, float rx, float ry,
               boolean tl, boolean tr, boolean br, boolean bl
    ){
        Path path = new Path();
        if (rx < 0) rx = 0;
        if (ry < 0) ry = 0;
        float width = right - left;
        float height = bottom - top;
        if (rx > width / 2) rx = width / 2;
        if (ry > height / 2) ry = height / 2;
        float widthMinusCorners = (width - (2 * rx));
        float heightMinusCorners = (height - (2 * ry));

        path.moveTo(right, top + ry);
        if (tr)
            path.rQuadTo(0, -ry, -rx, -ry);//top-right corner
        else{
            path.rLineTo(0, -ry);
            path.rLineTo(-rx,0);
        }
        path.rLineTo(-widthMinusCorners, 0);
        if (tl)
            path.rQuadTo(-rx, 0, -rx, ry); //top-left corner
        else{
            path.rLineTo(-rx, 0);
            path.rLineTo(0,ry);
        }
        path.rLineTo(0, heightMinusCorners);

        if (bl)
            path.rQuadTo(0, ry, rx, ry);//bottom-left corner
        else{
            path.rLineTo(0, ry);
            path.rLineTo(rx,0);
        }

        path.rLineTo(widthMinusCorners, 0);
        if (br)
            path.rQuadTo(rx, 0, rx, -ry); //bottom-right corner
        else{
            path.rLineTo(rx,0);
            path.rLineTo(0, -ry);
        }

        path.rLineTo(0, -heightMinusCorners);

        path.close();//Given close, last lineto can be removed.

        return path;
    }

Semplice funzione di aiutante scritta in Kotlin.

private fun Canvas.drawTopRoundRect(rect: RectF, paint: Paint, radius: Float) {
    // Step 1. Draw rect with rounded corners.
    drawRoundRect(rect, radius, radius, paint)

    // Step 2. Draw simple rect with reduced height,
    // so it wont cover top rounded corners.
    drawRect(
            rect.left,
            rect.top + radius,
            rect.right,
            rect.bottom,
            paint
    )
}
.

Uso:

canvas.drawTopRoundRect(rect, paint, radius)
.

public static Path composeRoundedRectPath(RectF rect, float topLeftDiameter, float topRightDiameter,float bottomRightDiameter, float bottomLeftDiameter){
    Path path = new Path();
    topLeftDiameter = topLeftDiameter < 0 ? 0 : topLeftDiameter;
    topRightDiameter = topRightDiameter < 0 ? 0 : topRightDiameter;
    bottomLeftDiameter = bottomLeftDiameter < 0 ? 0 : bottomLeftDiameter;
    bottomRightDiameter = bottomRightDiameter < 0 ? 0 : bottomRightDiameter;

    path.moveTo(rect.left + topLeftDiameter/2 ,rect.top);
    path.lineTo(rect.right - topRightDiameter/2,rect.top);
    path.quadTo(rect.right, rect.top, rect.right, rect.top + topRightDiameter/2);
    path.lineTo(rect.right ,rect.bottom - bottomRightDiameter/2);
    path.quadTo(rect.right ,rect.bottom, rect.right - bottomRightDiameter/2, rect.bottom);
    path.lineTo(rect.left + bottomLeftDiameter/2,rect.bottom);
    path.quadTo(rect.left,rect.bottom,rect.left, rect.bottom - bottomLeftDiameter/2);
    path.lineTo(rect.left,rect.top + topLeftDiameter/2);
    path.quadTo(rect.left,rect.top, rect.left + topLeftDiameter/2, rect.top);
    path.close();

    return path;
}
.

Per API 21 e sopra la classe del percorso ha aggiunto un nuovo metodo addRoundRect() che è possibile utilizzarlo come questo.

corners = new float[]{
    80, 80,        // Top left radius in px
    80, 80,        // Top right radius in px
    0, 0,          // Bottom right radius in px
    0, 0           // Bottom left radius in px
};

final Path path = new Path();
path.addRoundRect(rect, corners, Path.Direction.CW);
canvas.drawPath(path, mPaint);
.

in kotlin

val corners = floatArrayOf(
    80f, 80f,   // Top left radius in px
    80f, 80f,   // Top right radius in px
    0f, 0f,     // Bottom right radius in px
    0f, 0f      // Bottom left radius in px
)

val path = Path()
path.addRoundRect(rect, corners, Path.Direction.CW)
canvas.drawPath(path, mPaint)
.

Ho ottenuto questo risultato seguendo i passaggi seguenti.

Questi sono i prerequisiti affinché il rettangolo arrotondato abbia un aspetto ordinato

  • Il raggio dei bordi deve essere uguale a (altezza del rettangolo / 2).Questo perché se ha un valore diverso, il punto in cui la curva incontra la linea retta del rettangolo non sarà

La prossima è la procedura per disegnare il rettangolo arrotondato.

  • Per prima cosa disegniamo 2 cerchi sul lato sinistro e destro, con il raggio= altezza del rettangolo / 2

  • Quindi tracciamo un rettangolo tra questi cerchi per ottenere il rettangolo arrotondato desiderato.

Sto pubblicando il codice di seguito

private void drawRoundedRect(Canvas canvas, float left, float top, float right, float bottom) {
    float radius = getHeight() / 2;
    canvas.drawCircle(radius, radius, radius, mainPaint);
    canvas.drawCircle(right - radius, radius, radius, mainPaint);
    canvas.drawRect(left + radius, top, right - radius, bottom, mainPaint);
}

Ora questo si traduce in un rettangolo arrotondato davvero carino come quello mostrato sotto  inserisci qui la descrizione dell'immagine

Un modo semplice ed efficiente per disegnare un lato solido è usare il clipping: rect clipping è essenzialmente gratuito e molto meno codice da scrivere rispetto a un percorso personalizzato.

Se voglio un rettangolo 300 x 300, con i lati in alto a sinistra e a destra arrotondati di 50 pixel, puoi fare:

canvas.save();
canvas.clipRect(0, 0, 300, 300);
canvas.drawRoundRect(new RectF(0, 0, 300, 350), 50, 50, paint);
canvas.restore();

Questo approccio funzionerà solo per l'arrotondamento su 2 o 3 angoli adiacenti, quindi è un po 'meno configurabile di un approccio basato su Path, ma l'uso di round rects è più efficiente, poiché drawRoundRect () è completamente accelerato dall'hardware (cioè, tassellato in triangoli) mentre drawPath () ricorre sempre al rendering software (software: disegna una bitmap di percorso e caricala per essere memorizzata nella cache sulla GPU).

Non è un grosso problema di prestazioni per disegni piccoli e poco frequenti, ma se stai animando i tracciati, il costo del disegno software può allungare i tempi dei fotogrammi e aumentare le tue possibilità di perdere fotogrammi. Anche la maschera del percorso costa memoria.

Se vuoi utilizzare un approccio basato sul percorso, ti consiglio di utilizzare GradientDrawable per semplificare le righe di codice (supponendo che non sia necessario impostare uno shader personalizzato, ad esempio per disegnare una bitmap).

mGradient.setBounds(0, 0, 300, 300);
mGradient.setCornerRadii(new int[] {50,50, 50,50, 0,0, 0,0});

Con GradientDrawable # setCornerRadii () , puoi impostare qualsiasi angolo in modo che sia arrotondato e ragionevolmente animato tra gli stati.

Disegna rotonda retta con angoli a sinistra arrotondata

  private void drawRoundRect(float left, float top, float right, float bottom, Paint paint, Canvas canvas) {
        Path path = new Path();
        path.moveTo(left, top);
        path.lineTo(right, top);
        path.lineTo(right, bottom);
        path.lineTo(left + radius, bottom);
        path.quadTo(left, bottom, left, bottom - radius);
        path.lineTo(left, top + radius);
        path.quadTo(left, top, left + radius, top);
        canvas.drawPath(path, onlinePaint);
    }
.

Utilizzare PaintDrawable potrebbe essere migliore:

    val topLeftRadius = 10
    val topRightRadius = 10
    val bottomLeftRadius = 0
    val bottomRightRadius = 0
    val rect = Rect(0, 0, 100, 100)
    val paintDrawable = PaintDrawable(Color.RED)
    val outter = floatArrayOf(topLeftRadius, topLeftRadius, topRightRadius, topRightRadius,
            bottomLeftRadius, bottomLeftRadius, bottomRightRadius, bottomRightRadius)
    paintDrawable.setCornerRadii(outter)
    paintDrawable.bounds = rect
    paintDrawable.draw(canvas)

Questa è una vecchia domanda, tuttavia volevo aggiungere la mia soluzione perché utilizza l'SDK nativo senza molto codice personalizzato o disegno hacky. Questa soluzione è supportata nell'API 1.

Il modo per farlo correttamente è creare un percorso (come menzionato in altre risposte) tuttavia le risposte precedenti sembrano trascurare la chiamata alla funzione addRoundedRect che prende i raggi per ogni angolo.

<”Variabili

private val path = Path()
private val paint = Paint()

Imposta Paint

paint.color = Color.RED
paint.style = Paint.Style.FILL

Aggiorna percorso con modifiche alle dimensioni

Chiamalo da qualche parte che non è onDraw, come onMeasure per una vista o onBoundChange per un drawable. Se non cambia (come questo esempio) potresti mettere questo codice dove hai impostato la tua pittura.

val radii = floatArrayOf(
    25f, 25f, //Top left corner
    25f, 25f, //Top right corner
    0f, 0f,   //Bottom right corner
    0f, 0f,   //Bottom left corner
)

path.reset() //Clears the previously set path
path.addRoundedRect(0f, 0f, 100f, 100f, radii, Path.Direction.CW)

Questo codice crea un rettangolo arrotondato 100x100 con gli angoli superiori arrotondati con un raggio di 25.

Disegna percorso

Chiamalo in onDraw per una vista o draw per un drawable.

canvas.drawPath(path, paint)
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top