studio programacion herramientas fundamentos con avanzado aplicaciones android android-widget

programacion - ¿Cómo antialias el límite del clip en el lienzo de Android?



manual de android en pdf (4)

Estoy usando la clase android.graphics.Canvas de Android para dibujar un anillo . Mi método onDraw recorta el lienzo para hacer un agujero para el círculo interior y luego dibuja el círculo exterior completo sobre el agujero:

clip = new Path(); clip.addRect(outerCircle, Path.Direction.CW); clip.addOval(innerCircle, Path.Direction.CCW); canvas.save(); canvas.clipPath(clip); canvas.drawOval(outerCircle, lightGrey); canvas.restore();

El resultado es un anillo con un borde exterior bonito y suavizado y un borde interior irregular y feo:

¿Qué puedo hacer para suavizar el borde interior?

No quiero hacer trampa dibujando un círculo gris en el medio porque el diálogo es ligeramente transparente. (Esta transparencia no es tan sutil en otros fondos).


AFAIK, no puedes antialias clip de regiones.

Yo sugeriría usar enmascaramiento de mapa de bits en su lugar. Represente el primer plano rosa, blanco y gris claro en un mapa de bits, la máscara del círculo exterior / interior (el canal alfa en escala de grises) en otro mapa de bits, y luego use Paint.setXfermode para representar el mapa de bits del primer plano con la máscara como canal alfa .

Un ejemplo se puede encontrar en el código fuente de ApiDemos aquí:

EDITAR : para una buena medida, aquí hay una captura de pantalla de esa actividad en las Demos de API, que ilustra el enmascaramiento:

Captura de pantalla de Xfermodes http://nikonizer.yfrog.com/Himg46/scaled.php?tn=0&server=46&filename=xfermodes.png&xsize=640&ysize=640


Puedes probar el siguiente código:

public class GrowthView extends View { private static final String TAG = "GrowthView"; private int bgColor = Color.parseColor("#33485d"); private int valColor = Color.parseColor("#ecb732"); private int[] scores = new int[]{0, 10, 80, 180, 800, 5000, 20000, 50000, 100000}; private Context mContext; private float w; private float h; private Paint bgPaint; private Paint growthPaint; private Paint textPaint; private Paint clipPaint; private Path bgPath; private Path bgClipPath; private Path growthPath; private int growthValue = 0; private float bgFullAngle = 240.0f; private float gapAngle = bgFullAngle / (scores.length - 1); private float gapRadius = 21.5f;//实际为21px 略大半个像素避免path无法缝合error private float outerRadius = 240.0f; private float innerRadius = outerRadius - gapRadius * 2; private RectF outerRecF; private RectF innerRecF; private RectF leftBoundRecF; private RectF rightBoundRecF; public GrowthView(Context context) { this(context, null); } public GrowthView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public GrowthView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); this.mContext = context; init(); } private void init() { Xfermode xFermode = new PorterDuffXfermode(PorterDuff.Mode.DARKEN); bgPaint = new Paint(); bgPaint.setStyle(Paint.Style.FILL); bgPaint.setColor(bgColor); bgPaint.setStrokeWidth(0.1f); bgPaint.setAntiAlias(true); growthPaint = new Paint(); growthPaint.setStyle(Paint.Style.FILL_AND_STROKE); growthPaint.setColor(valColor); growthPaint.setStrokeWidth(1f); growthPaint.setAntiAlias(true); clipPaint = new Paint(); clipPaint.setStyle(Paint.Style.FILL); clipPaint.setColor(Color.WHITE); clipPaint.setStrokeWidth(.1f); clipPaint.setAntiAlias(true); clipPaint.setXfermode(xFermode); textPaint = new Paint(); textPaint.setTextSize(96);//todo comfirm the textSize textPaint.setStrokeWidth(1f); textPaint.setAntiAlias(true); textPaint.setTextAlign(Paint.Align.CENTER); textPaint.setColor(valColor); bgPath = new Path(); growthPath = new Path(); //todo 暂定中心点为屏幕中心 DisplayMetrics metrics = new DisplayMetrics(); WindowManager wm = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); wm.getDefaultDisplay().getMetrics(metrics); w = metrics.widthPixels; h = metrics.heightPixels; outerRecF = new RectF(w / 2 - outerRadius, h / 2 - outerRadius, w / 2 + outerRadius, h / 2 + outerRadius); innerRecF = new RectF(w / 2 - innerRadius, h / 2 - innerRadius, w / 2 + innerRadius, h / 2 + innerRadius); rightBoundRecF = new RectF(w / 2 + (float) Math.pow(3, 0.5) * (innerRadius + gapRadius) / 2 - gapRadius, h / 2 + (innerRadius + gapRadius) / 2 - gapRadius, w / 2 + (float) Math.pow(3, 0.5) * (innerRadius + gapRadius) / 2 + gapRadius, h / 2 + (innerRadius + gapRadius) / 2 + gapRadius); leftBoundRecF = new RectF(w / 2 - (float) Math.pow(3, 0.5) * (innerRadius + gapRadius) / 2 - gapRadius, h / 2 + (innerRadius + gapRadius) / 2 - gapRadius, w / 2 - (float) Math.pow(3, 0.5) * (innerRadius + gapRadius) / 2 + gapRadius, h / 2 + (innerRadius + gapRadius) / 2 + gapRadius); bgClipPath = new Path(); bgClipPath.arcTo(innerRecF, 150.0f, 359.9f, true); bgClipPath.close(); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); //bg float startAngle = 150.0f; float endRecfFullAngle = 180.0f; bgPath.arcTo(outerRecF, startAngle, bgFullAngle, true); bgPath.arcTo(rightBoundRecF, 30.0f, endRecfFullAngle, true); bgPath.arcTo(innerRecF, startAngle, bgFullAngle); bgPath.arcTo(leftBoundRecF, -30.0f, endRecfFullAngle); bgPath.rMoveTo(w / 2 - outerRadius * (float) Math.pow(3, 0.5) / 2, h / 2 + outerRadius / 2); bgPath.setFillType(Path.FillType.WINDING); bgPath.close(); //growth if (getGrowthVal() != 0) { float temp = getGrowthAngle(getGrowthVal()); growthPath.arcTo(outerRecF, startAngle, temp, true); growthPath.arcTo(getDynamicRecF(getGrowthVal()), getDynamicOriginAngle(getGrowthVal()), endRecfFullAngle, true); growthPath.arcTo(innerRecF, startAngle, temp); growthPath.arcTo(leftBoundRecF, -30.0f, endRecfFullAngle); growthPath.rMoveTo(w / 2 - outerRadius * (float) Math.pow(3, 0.5) / 2, h / 2 + outerRadius / 2); growthPath.close(); } canvas.drawText(formatVal(getGrowthVal()), w / 2, h / 2, textPaint); canvas.clipPath(bgClipPath, Region.Op.DIFFERENCE); canvas.drawPath(bgPath, bgPaint); canvas.drawPath(growthPath, growthPaint); canvas.drawPath(bgClipPath, clipPaint); } private float getDynamicOriginAngle(int growthVal) { return growthVal <= 30 ? getGrowthAngle(growthVal) + 150 : getGrowthAngle(growthVal) - 210; } private RectF getDynamicRecF(int growthVal) { float dynamicAngle = getGrowthAngle(growthVal); //动态圆心 float _w = w / 2 + (float) Math.sin(Math.toRadians(dynamicAngle - 120)) * (outerRadius - gapRadius); float _y = h / 2 - (float) Math.sin(Math.toRadians(dynamicAngle - 30)) * (outerRadius - gapRadius); return new RectF(_w - gapRadius, _y - gapRadius, _w + gapRadius, _y + gapRadius); } private int getGrowthVal() { return this.growthValue; } public void setGrowthValue(int value) { if (value < 0 || value > 100000) { try { throw new Exception("成长值不在范围内"); } catch (Exception e) { Log.e(TAG, e.getMessage()); e.printStackTrace(); } } this.growthValue = value; invalidate(); } private float getGrowthAngle(int growthVal) { return gapAngle * (getLevel(growthVal) - 1) + gapAngle * (growthVal - scores[getLevel(growthVal) - 1]) / (scores[getLevel(growthVal)] - scores[getLevel(growthVal) - 1]); } private int getLevel(int score) { return score < 0 ? -1 : score <= 10 ? 1 : score <= 80 ? 2 : score <= 180 ? 3 : score <= 800 ? 4 : score <= 5000 ? 5 : score <= 20000 ? 6 : score <= 50000 ? 7 : 8; } private String formatVal(int value) { StringBuilder builder = new StringBuilder(String.valueOf(value)); return value < 1000 ? builder.toString() : builder.insert(builder.length() - 3, '','').toString(); }

}

Utilice Xfermode Api con canvas.clipPath () puede resolver este problema ... Result


Sé que esta no es una respuesta general, pero en este caso particular, podría dibujar arcos con un ancho de trazo grueso, en lugar de los círculos + máscara.


Yo también tuve este mismo problema. Intenté usar el enmascaramiento de mapa de bits (xFermode) para corregir el Aliasing, pero fue pesado.

Entonces para API <19, usé la forma de enmascaramiento de mapa de bits y para API> = 19, usé Path.Op En lugar de recortar el camino y luego dibujar la forma. Hice una REVERSE_DIFFERENCE de la path y la shape (que es de tipo Ruta). Puede realizar operaciones en la ruta desde la API 19 y superiores.

Funciona perfectamente para mi!