Come utilizzare Android Canvas per disegnare un rettangolo con solo gli angoli in alto a sinistra e in alto a destra arrotondati?
-
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);
. 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.
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
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)