I'm using Android's android.graphics.Canvas class to draw a ring. My onDraw method clips the canvas to make a hole for the inner circle, and then draws the full outer circle over the hole:
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();
The result is a ring with a pretty, anti-aliased outer edge and a jagged, ugly inner edge:
What can I do to antialias the inner edge?
I don't want to cheat by drawing a grey circle in the middle because the dialog is slightly transparent. (This transparency isn't as subtle on on other backgrounds.)
As far as I know, you can't antialias clip regions.
I'd suggest using bitmap masking instead. Render the the pink, white, and light gray foreground to one bitmap, render the outer/inner circle mask (the grayscale alpha channel) to another bitmap, and then use Paint.setXfermode to render the foreground bitmap with the mask as its alpha channel.
An example can be found in the ApiDemos source code here.
I know this is not a general answer, but in this particular case, you could draw arcs with a thick stroke width, instead of the circles + mask.
I too had this same problem. I tried using Bitmap masking (xFermode) to fix the Aliasing, but it was heavy.
So for API < 19, I used the Bitmap masking way and for API >= 19, I used Path.Op. Instead of clipping the path and then drawing the shape. I did a REVERSE_DIFFERENCE of the path and the shape(which is of type Path). You can perform operations on Path from API 19 and above.
Works perfectly for me!
you can try the following code:
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();
}
}
Use the Xfermode Api with canvas.clipPath() may resolve this problem...
Result
Related
I am trying to draw spiral view as in screenshot attached,
I have array of points to draw based on number on circles.
I am new to android and kotlin .
Can anyone please suggest how to do this?
Today I added spiral drawing to this custom scrolling shapes app I'm building. My implementation makes use of Android import android.graphics.Path;. The spiral drawing is based on the user scrolling, with more segments added as the user scrolls.
The gist of the solution is as follows: I start with an initial arc and draw that. That arc is the initial arc of the spiral. On that arc has been added to the path, then use the bounding Rect of the Path to determine the bounds for the next segment of the arc.
The below is a sample but for full context you'll need to view the project: https://github.com/jdgreene2008/android_custom_views
private void addTopSpiralSegment(Path path, RectF bounds, SpiralSegment spiralSegment) {
float centerX = bounds.centerX();
float centerY = bounds.centerY();
float segmentBoundsLeft;
float segmentBoundsRight;
float segmentBoundsTop;
float segmentBoundsBottom;
if (path.isEmpty()) {
segmentBoundsLeft = centerX - spiralSegment.getWidth() / 2;
segmentBoundsRight = centerX + spiralSegment.getWidth() / 2;
segmentBoundsTop = centerY - spiralSegment.getHeight() / 2;
segmentBoundsBottom = centerY + spiralSegment.getHeight() / 2;
} else {
RectF pathBounds = new RectF();
path.computeBounds(pathBounds, true);
segmentBoundsLeft = pathBounds.left;
segmentBoundsRight = segmentBoundsLeft + spiralSegment.getWidth();
segmentBoundsTop = centerY - spiralSegment.getHeight() / 2;
segmentBoundsBottom = centerY + spiralSegment.getHeight() / 2;
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
path.addArc(segmentBoundsLeft, segmentBoundsTop, segmentBoundsRight,
segmentBoundsBottom, 180, 180);
}
}
private void addBottomSpiralSegment(Path path, RectF bounds, SpiralSegment spiralSegment) {
float centerX = bounds.centerX();
float centerY = bounds.centerY();
float segmentBoundsLeft;
float segmentBoundsRight;
float segmentBoundsTop;
float segmentBoundsBottom;
if (path.isEmpty()) {
segmentBoundsLeft = centerX - spiralSegment.getWidth() / 2;
segmentBoundsRight = centerX + spiralSegment.getWidth() / 2;
segmentBoundsTop = centerY - spiralSegment.getHeight() / 2;
segmentBoundsBottom = centerY + spiralSegment.getHeight() / 2;
} else {
RectF pathBounds = new RectF();
path.computeBounds(pathBounds, true);
segmentBoundsLeft = pathBounds.right - spiralSegment.getWidth();
segmentBoundsRight = pathBounds.right;
segmentBoundsTop = centerY - spiralSegment.getHeight() / 2;
segmentBoundsBottom = centerY + spiralSegment.getHeight() / 2;
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
path.addArc(segmentBoundsLeft, segmentBoundsTop, segmentBoundsRight,
segmentBoundsBottom, 0, 180);
}
}
How can I draw Path with fading (opacity or thicknes) line? Something like this.
I know there is LinearGradient shader for Paint, but it won't bend along the Path.
One possible solution might be to get points along the Path and just draw it by myself through the segments`. But I coouldn't find any method for that either.
I came up with the following code. The mos important thing is PathMeasure's getPosTan() method.
if (getGesturePath() != null) {
final short steps = 150;
final byte stepDistance = 5;
final byte maxTrailRadius = 15;
pathMeasure.setPath(getGesturePath(), false);
final float pathLength = pathMeasure.getLength();
for (short i = 1; i <= steps; i++) {
final float distance = pathLength - i * stepDistance;
if (distance >= 0) {
final float trailRadius = maxTrailRadius * (1 - (float) i / steps);
pathMeasure.getPosTan(distance, pathPos, null);
final float x = pathPos[0] + RandomUtils.nextFloat(0, 2 * trailRadius) - trailRadius;
final float y = pathPos[1] + RandomUtils.nextFloat(0, 2 * trailRadius) - trailRadius;
paint.setShader(new RadialGradient(
x,
y,
trailRadius > 0 ? trailRadius : Float.MIN_VALUE,
ColorUtils.setAlphaComponent(Color.GREEN, random.nextInt(0xff)),
Color.TRANSPARENT,
Shader.TileMode.CLAMP
));
canvas.drawCircle(x, y, trailRadius, paint);
}
}
}
I'm trying to make an animation on my android application.
I have a list of View I want to draw every 10 ms to have a smooth animations.
class CircularAnimationSector extends View{
private double scale;
private double circularPosition;
private double circularLength;
private double sectionRadiusRatio;
private double circularSpeed;
private int color;
private Paint paint = new Paint();
private ICircular iCircular;
#Override
public void draw(Canvas canvas) {
super.draw(canvas);
Path path = new Path();
int width = this.getWidth();
// int height = this.getHeight();
Point center = new Point((int)(width / 2.0f), (int)(width / 2.0f));
int innerWidthRadius = (int) (this.iCircular.GetViewSize() / 2.0f);
float startRadius = (float)(innerWidthRadius * this.iCircular.GetStartRatio());
float endRadius = (float)(startRadius + this.scale * (this.sectionRadiusRatio - this.iCircular.GetStartRatio()) * innerWidthRadius);
float startAngle = (float)(this.circularPosition - this.circularLength / 2.0f);
float endAngle = (float)(this.circularPosition + this.circularLength / 2.0f);
Point p1 = new Point((int) (center.x + startRadius * Math.cos(startAngle)), (int) (center.y + startRadius * Math.sin(startAngle)));
Point p3 = new Point((int) (center.x + endRadius * Math.cos(endAngle)), (int) (center.y + endRadius * Math.sin(endAngle)));
// PATH DRAWING
path.moveTo((float) (p1.x), (float) (p1.y));
path.arcTo(new RectF(center.x - startRadius, center.y - startRadius, center.x + startRadius, center.y + startRadius), (float) (startAngle * 180 / Math.PI), (float) ((endAngle - startAngle) * 180 / Math.PI));
path.lineTo((float) (p3.x), (float) (p3.y));
path.arcTo(new RectF(center.x - endRadius, center.y - endRadius, center.x + endRadius, center.y + endRadius), (float) (endAngle * 180 / Math.PI), (float) ((startAngle - endAngle) * 180 / Math.PI));
path.lineTo((float) (p1.x), (float) (p1.y));
canvas.drawPath(path, this.paint);
this.circularPosition += this.circularSpeed;
}
}
But it seems that the animation is not as smooth as expected and the application lag a little.
Do you have any advice to optimize my animation ?
I just want to draw a view every 10 ms (for instance) and then upadte its position and draw it again around a circle.
I am able to draw custom back ground to xml file using Shape
But how to add arc or curv at specified place.
In my library Facebook Like Button I implemented custom Path in order to achieve this goal.
There is ability to specify location of marker wherever you want:
Code from source:
import android.graphics.Path;
import android.graphics.RectF;
public class CalloutPath extends Path {
public static final int MARKER_NONE = 0x0;
public static final int MARKER_LEFT = 0x1;
public static final int MARKER_TOP = 0x2;
public static final int MARKER_RIGHT = 0x4;
public static final int MARKER_BOTTOM = 0x8;
public static final int MARKER_ALL = 0xf;
private final RectF oval = new RectF();
/**
* #param m marker
* #param w width
* #param h height
* #param s stroke thickness
* #param r corners radius
*/
public void build(int m, float w, float h, float s, float r) {
int fl = factor(m, MARKER_LEFT);
int ft = factor(m, MARKER_TOP);
int fr = factor(m, MARKER_RIGHT);
int fb = factor(m, MARKER_BOTTOM);
float x0 = s + 0f;
float x1 = s + r * fl;
float x2 = s + r + r * fl;
float x3 = w / 2f - r;
float x4 = w / 2f;
float x5 = w / 2f + r;
float x6 = w - 1f - s - r - r * fr;
float x7 = w - 1f - s - r * fr;
float x8 = w - 1f - s;
float y0 = s + 0f;
float y1 = s + r * ft;
float y2 = s + r + r * ft;
float y3 = h / 2f - r;
float y4 = h / 2f;
float y5 = h / 2f + r;
float y6 = h - 1f - s - r - r * fb;
float y7 = h - 1f - s - r * fb;
float y8 = h - 1f - s;
reset();
moveTo(x1, y2);
oval.set(x2 - r, y2 - r, x2 + r, y2 + r);
arcTo(oval, 180f, 90f);
if (ft != 0) {
lineTo(x3, y1);
lineTo(x4, y0);
lineTo(x5, y1);
}
lineTo(x6, y1);
oval.set(x6 - r, y2 - r, x6 + r, y2 + r);
arcTo(oval, 270f, 90f);
if (fr != 0) {
lineTo(x7, y3);
lineTo(x8, y4);
lineTo(x7, y5);
}
lineTo(x7, y6);
oval.set(x6 - r, y6 - r, x6 + r, y6 + r);
arcTo(oval, 0f, 90f);
if (fb != 0) {
lineTo(x5, y7);
lineTo(x4, y8);
lineTo(x3, y7);
}
lineTo(x2, y7);
oval.set(x2 - r, y6 - r, x2 + r, y6 + r);
arcTo(oval, 90f, 90f);
if (fl != 0) {
lineTo(x1, y5);
lineTo(x0, y4);
lineTo(x1, y3);
}
close();
}
public static int factor(int marker, int mask) {
return (marker & mask) != 0 ? 1 : 0;
}
}
Because you have this arc or curv you should create a custom jpg.
But you can use with corners and have below :
You do not need to do it programmatically, you should just use a nine patch image resource that make a perfect image for you. If you want to know more about nine patch and how it works, have a look at this and this links.
As you can see this image makes a perfect chat bubble for you. I already decompiled Whatsapp , Viber and they all using a nine patched image to make a chat bubble. As you can see, the Telegram app also using a nine patched image to achieve this.
My sample chat bubble image:
I also should mention that you don't need any custom path. It is a typical patched image without any customization.
make 9patch image like this so that if in conversation lots of text then also it can be adjusted itself and also not stretch or compressed from curve .
Hope it will help you
I have a canvas!
1.On canvas i'm draw are graph (see below)!
2.Draw a points and lines between.
3.I'm was calculate are all points on a line, and draw little lines to bottom.
4.And draw gradient with are #e5fafd color!
private void drawGradient(Canvas canvas){
Paint gradientPainter = new Paint();
gradientPainter.setStrokeWidth(5);
gradientPainter.setShader(new LinearGradient(0, 0, 0, getHeight(), getResources().getColor(R.color.graph_gradient_start), Color.TRANSPARENT, Shader.TileMode.CLAMP));
for(int i = 0; i < ourCoords.size(); i++){
if(i != ourCoords.size() - 1){
final float x = ourCoords.get(i+1).getxCoord() - ourCoords.get(i).getxCoord();
final float y = ourCoords.get(i+1).getyCoord() - ourCoords.get(i).getyCoord();
float xT = ourCoords.get(i).getxCoord(), yT = ourCoords.get(i).getyCoord();
final float percentX = (float) (x / 100.0);
final float percentY = (float) (y / 100.0);
for(float j= (float) 0.1; j<100.0; j++){
float startX = (percentX * j) + xT;
float startY = (percentY * j) + yT;
float endX = startX;
float endY = startY + 400;
if(startX < (canvasWidth - marginLeft - 8)){
canvas.drawLine(startX, startY, endX, endY, gradientPainter);
}
}
}
drawPoints(canvas, ourCoords.get(i).getxCoord(), ourCoords.get(i).getyCoord(), 20);
}
}
I ended up getting a black gradient. Please help me and sorry for the mistakes.
See screen http://mepic.ru/view/?id=b1904b5d5b6d156e83206ab0c485d40a