In android I have create custom view.I have first drawn circle and now I want to draw a line angle wise within a circle. I want like this with animation.
https://drive.google.com/file/d/1Qx0MBu-77JIlQTByqGTyD-KtGKOB8naG/view?usp=sharing
I have used canvas to draw circle and lines.I have taken viewpager.If I swipe viewpager then pie graphics will rotate.
What I have done uptill now. When animating it's always start from zero:
https://drive.google.com/file/d/12mmAUOeY77jAlj_GmM3Ymcx5m34vli3X/view?usp=sharing
I have done below code:
public class PieView : View
{
int w, h, pl, pr, pt, pb, usableWidth, usableHeight, radius, cx, cy, lineLenght;
Paint paint;
public Canvas canvas;
public float firstLineangle = 0;
public float secondLineangle = 40;
public float thirdLineangle = 120;
float currentAngle,maxAngle;
public override void Draw(Canvas canvas)
{
base.Draw(canvas);
w = Width;
h = Height;
pl = PaddingLeft;
pr = PaddingRight;
pt = PaddingTop;
pb = PaddingBottom;
this.canvas = canvas;
usableWidth = w - (pl + pr);
usableHeight = h - (pt + pb);
radius = Math.Min(usableWidth, usableHeight) / 2;
cx = pl + (usableWidth / 2);
cy = pt + (usableHeight / 2);
lineLenght = radius - (pl * 2) - (pr * 2);
paint = new Paint();
paint.Color = Android.Graphics.Color.White;
paint.SetStyle(Paint.Style.Stroke);
paint.StrokeWidth = 5;
canvas.DrawCircle(cx, cy, radius - 5, paint);
Drawline(canvas, firstLineangle);
Drawline(canvas, secondLineangle);
Drawline(canvas, thirdLineangle);
PostInvalidateDelayed(500);
Invalidate();
}
public void Drawline(Canvas canvas, float angle)
{
float displacedAngle = angle - 90;
float x = cx + ((float)Math.Cos(degreesToRadians(displacedAngle)) * (radius - 5)); //convert angle to radians for x and y coordinates
float y = cy + ((float)Math.Sin(degreesToRadians(displacedAngle)) * (radius - 5));
canvas.DrawLine(cx, cy, x, y, paint); //draw a line from center point back to the point
}
public double degreesToRadians(double degrees)
{
return (degrees * Math.PI) / 180;
}
}
public class PieAnimation : Android.Views.Animations.Animation
{
private PieView pieView;
private float firstLineangle;
private float secondLineangle;
private float thirdLineangle;
public PieAnimation(PieView pieView, float firstLineangle,float secondLineangle,float thirdLineangle)
{
this.pieView = pieView;
this.firstLineangle = firstLineangle;
this.secondLineangle = secondLineangle;
this.thirdLineangle = thirdLineangle;
}
protected override void ApplyTransformation(float interpolatedTime, Transformation t)
{
pieView.firstLineangle = 0 + ((firstLineangle) * interpolatedTime);
pieView.secondLineangle = 0 + ((secondLineangle) * interpolatedTime);
pieView.thirdLineangle = 0 + ((thirdLineangle) * interpolatedTime);
pieView.RequestLayout();
}
}
public class TourPager : Java.Lang.Object, ViewPager.IOnPageChangeListener, ViewPager.IPageTransformer
{
private ViewPager mViewPager;
private float mLastOffset;
public TourView _context;
public TourPager(ViewPager viewpager, TourView context)
{
mViewPager = viewpager;
viewpager.AddOnPageChangeListener(this);
_context = context;
}
public void OnPageSelected(int position)
{
if (position == 0)
{
PieAnimation animation = new PieAnimation(_context._pieView, 0, 40, 120);
animation.Duration = (1000);
_context._pieView.StartAnimation(animation);
}
if (position==1)
{
PieAnimation animation = new PieAnimation(_context._pieView, 100, 140, 200);
animation.Duration=(1000);
_context._pieView.StartAnimation(animation);
}
if(position==2)
{
PieAnimation animation = new PieAnimation(_context._pieView, 180, 270, 10);
animation.Duration = (1000);
_context._pieView.StartAnimation(animation);
}
}
There is running GIF.
There is PieView.cs.
public class PieView:View
{
int w, h, pl, pr, pt, pb, usableWidth, usableHeight, radius, cx, cy, lineLenght;
int handTruncation, hourHandTruncation = 0;
Paint paint;
int[] numbers = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 };
Rect rect = new Rect();
public PieView(Context context) : base(context)
{
}
public PieView(Context context, IAttributeSet attrs) : base(context, attrs)
{
}
public PieView(Context context, IAttributeSet attrs, int defStyleAttr) : base(context, attrs, defStyleAttr)
{
}
public PieView(Context context, IAttributeSet attrs, int defStyleAttr, int defStyleRes) : base(context, attrs, defStyleAttr, defStyleRes)
{
}
public override void Draw(Canvas canvas)
{
base.Draw(canvas);
w = Width;
h = Height;
pl = PaddingLeft+10;
pr = PaddingRight+10;
pt = PaddingTop+10;
pb = PaddingBottom+10;
usableWidth = w - (pl + pr);
usableHeight = h - (pt + pb);
radius = Math.Min(usableWidth, usableHeight) / 2;
cx = pl + (usableWidth / 2);
cy = pt + (usableHeight / 2);
int min = Math.Min(usableWidth, usableHeight);
handTruncation = min / 20;
hourHandTruncation = min / 7;
lineLenght = radius - (pl * 2) - (pr * 2);
paint = new Paint();
paint.Color = Android.Graphics.Color.White;
paint.SetStyle(Paint.Style.Stroke);
paint.StrokeWidth = 5;
canvas.DrawCircle(cx, cy, radius , paint);
drawNumeral(canvas);
drawHands(canvas);
PostInvalidateDelayed(200);
Invalidate();
}
private void drawHands(Canvas canvas)
{
Calendar c = Calendar.Instance;
float hour = c.Get(CalendarField.HourOfDay);
hour = hour > 12 ? hour - 12 : hour;
drawHand1(canvas, (hour + c.Get(CalendarField.Minute) / 60) * 5f,true);
drawHand1(canvas, c.Get(CalendarField.Minute),false);
drawHand1(canvas, c.Get(CalendarField.Second),false);
}
private void drawNumeral(Canvas canvas)
{
paint.TextSize=50;
foreach (var number in numbers)
{
string tmp = number.ToString();
paint.GetTextBounds( tmp, 0, tmp.Length, rect); //getTextBounds(tmp, 0, tmp.length(), rect);
double angle = Math.PI / 6 * (number - 3);
int x = (int)(w / 2 + Math.Cos(angle) * radius - rect.Width() / 2);
int y = (int)(h / 2 + Math.Sin(angle) * radius + rect.Height() / 2);
canvas.DrawText(tmp, x, y, paint);
}
}
private void drawHand1(Canvas canvas, double loc, bool isHour)
{
double angle = Math.PI * loc / 30 - Math.PI / 2;
int handRadius = isHour ? radius - handTruncation - hourHandTruncation : radius - handTruncation;
canvas.DrawLine(Width / 2, Height / 2,
(float)(Width / 2 + Math.Cos(angle) * handRadius),
(float)(Height / 2 + Math.Sin(angle) * handRadius),
paint);
}
}
You can use it in MainActivity.cs
RelativeLayout relativeLayout1 = FindViewById<RelativeLayout>
(Resource.Id.relativeLayout1);
relativeLayout1.SetBackgroundColor(Color.Black);
AddContentView(new PieView(this),new ViewGroup.LayoutParams(-1,-1));
Related
I've created a custom view that draws a dial gauge. If I set a static value for the angle of the needle, the gauge draws as expected (see first image below). If I attempt to set the angle of the needle through runOnUiThread, then I have weird needle artifacts (see second image).
I am calling canvas.drawColor(color.BLACK) each time onDraw() is called, so I am not sure how the artifacts are being carried over. My best guess is that I am not handling threading correctly in the method that calls runOnUiThread().
DialGaugeView class:
package net.dynu.kubie.redneksldhlr;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.View;
public class DialGaugeView extends View {
private int height, width, min = 0;
private int numberFontSize, titleFontSize = 0;
private float dialEdgeStroke, dialEdgeRadius = 0;
private float dialFaceRadius = 0;
private float tickMajorStroke, tickSweep, tickAngleStart, tickAngleEnd, tickEdgeRadius, tickInnerMajorRadius, tickInnerMinorRadius = 0;
private int tickMajorCount = 0;
private float tickMinorStroke = 0;
private int tickMinorCount = 0;
private int tickTotalCount = 0;
private int numberMin, numberMax = 0;
private float numberRadius = 0;
private float arrowTipRadius, arrowRearRadius = 0;
private float arrowCenterRadius, arrowPinRadius = 0;
private float sensorInput = 0;
private float temp = 0;
private Paint paint;
private boolean isInit;
private float titleRadius = 0;
private Rect rect = new Rect();
private Path path = new Path();
private String titleStr = "";
//Variables common to drawing functions
private float center_x, center_y = 0;
private float x1, x2, x3, y1, y2, y3 = 0;
private float angle = 0;
private int number = 0;
private String str = "";
public DialGaugeView(Context context) {
super(context);
}
public DialGaugeView(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.getTheme().obtainStyledAttributes(
attrs,
R.styleable.DialGaugeView,
0, 0);
try {
tickMajorCount = a.getInteger(R.styleable.DialGaugeView_tickCountMajor, 5);
tickMinorCount = a.getInteger(R.styleable.DialGaugeView_tickCountMinor, 0);
tickSweep = a.getFloat(R.styleable.DialGaugeView_tickSweep, 270) / 2;
numberMin = a.getInteger(R.styleable.DialGaugeView_displayMin, 0);
numberMax = a.getInteger(R.styleable.DialGaugeView_displayMax, 1);
titleStr = a.getString(R.styleable.DialGaugeView_displayTitle);
} finally {
a.recycle();
}
}
public DialGaugeView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public int getTickMajorCount() {
return tickMajorCount;
}
public void setTickMajorCount(int i) {
tickMajorCount = i;
isInit = false;
invalidate();
requestLayout();
}
public int getTickMinorCount() {
return tickMinorCount;
}
public void setTickMinorCount(int i) {
tickMinorCount = i;
isInit = false;
invalidate();
requestLayout();
}
public float getTickSweep() {
return tickSweep;
}
public void setTickSweep(float i) {
tickSweep = i / 2;
isInit = false;
invalidate();
requestLayout();
}
public int getDisplayMin() {
return numberMin;
}
public void setDisplayMin(int i) {
numberMin = i;
isInit = false;
invalidate();
requestLayout();
}
public int getDisplayMax() {
return numberMax;
}
public void setDisplayMax(int i) {
numberMax = i;
isInit = false;
invalidate();
requestLayout();
}
public float getSensorInput() {
return sensorInput;
}
public void setSensorInput(float i) {
sensorInput = i;
isInit = false;
invalidate();
requestLayout();
}
public String getTitle() {
return titleStr;
}
public void setTitle(String i) {
titleStr = i;
invalidate();
requestLayout();
}
private void initGauge() {
//Common variables
height = getHeight();
width = getWidth();
min = Math.min(height, width);
center_x = width / 2;
center_y = height / 2;
paint = new Paint();
isInit = true;
//Dial face variables
dialEdgeStroke = min / 20;
dialEdgeRadius = min / 2 - dialEdgeStroke / 2;
dialFaceRadius = dialEdgeRadius - dialEdgeStroke / 2;
//Tick variables
//tickMajorCount = 7; //TODO - Class input needed
//tickMinorCount = 1; //TODO - Class input needed
//tickSweep = 270 / 2; //TODO - Class input needed //Degrees +/- from 90 degrees
tickEdgeRadius = dialFaceRadius - dialEdgeStroke / 2;
tickInnerMajorRadius = (float) (tickEdgeRadius - dialEdgeStroke * 1.5);
tickInnerMinorRadius = ((tickEdgeRadius + tickInnerMajorRadius) / 2);
tickMajorStroke = min / 75;
tickAngleStart = 90 + tickSweep;
tickAngleEnd = 90 - tickSweep;
tickMinorStroke = tickMajorStroke / 2;
tickTotalCount = tickMajorCount + (tickMajorCount - 1) * tickMinorCount;
//Numeral variables
//numberMin = 0; //TODO - Class input needed
//numberMax = 120; //TODO - Class input needed
numberRadius = (tickInnerMajorRadius); // - (tickEdgeRadius - tickInnerMajorRadius) * 1.25);
numberFontSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, min / 40,
getResources().getDisplayMetrics());
//Title variables
titleRadius = tickInnerMajorRadius;
titleFontSize = numberFontSize;
//Arrow variables
//sensorInput = (float) (numberMax * .4965); //TODO - Class input needed
arrowTipRadius = tickEdgeRadius;
arrowRearRadius = arrowTipRadius / 2;
arrowCenterRadius = (arrowTipRadius * 1 / 8);
arrowPinRadius = (arrowTipRadius * 1 / 24);
}
#Override
protected void onDraw(Canvas canvas) {
if (!isInit) {
initGauge();
}
canvas.drawColor(Color.BLACK);
drawDialFace(canvas);
drawTicks(canvas);
drawNumeral(canvas);
drawTitle(canvas);
drawArrow(canvas);
postInvalidateDelayed(500);
invalidate();
requestLayout();
}
private void drawDialFace(Canvas canvas) {
paint.reset();
paint.setColor(getResources().getColor(android.R.color.black));
paint.setStrokeWidth(dialEdgeStroke);
paint.setStyle(Paint.Style.STROKE);
paint.setAntiAlias(true);
canvas.drawCircle(center_x, center_y, dialEdgeRadius, paint);
paint.reset();
paint.setColor(getResources().getColor(android.R.color.white));
paint.setStyle(Paint.Style.FILL);
paint.setAntiAlias(true);
canvas.drawCircle(center_x, center_y, dialFaceRadius, paint);
}
private void drawTicks(Canvas canvas) {
paint.reset();
paint.setColor(getResources().getColor(android.R.color.black));
paint.setStyle(Paint.Style.STROKE);
paint.setAntiAlias(true);
for(int i = 0; i < tickTotalCount; i++) {
angle = (float) (((tickAngleEnd - tickAngleStart) * i / (tickTotalCount - 1) + tickAngleStart) / 180 * Math.PI);
x1 = (float) (center_x + Math.cos(angle) * tickEdgeRadius);
y1 = (float) (center_y - Math.sin(angle) * tickEdgeRadius);
if((i % (tickMinorCount + 1)) == 0) {
paint.setStrokeWidth(tickMajorStroke);
x2 = (float) (center_x + Math.cos(angle) * tickInnerMajorRadius);
y2 = (float) (center_y - Math.sin(angle) * tickInnerMajorRadius);
} else {
paint.setStrokeWidth(tickMinorStroke);
x2 = (float) (center_x + Math.cos(angle) * tickInnerMinorRadius);
y2 = (float) (center_y - Math.sin(angle) * tickInnerMinorRadius);
}
canvas.drawLine(x1, y1, x2, y2, paint);
}
}
private void drawNumeral(Canvas canvas) {
paint.reset();
paint.setTextSize(numberFontSize);
paint.setColor(getResources().getColor(android.R.color.black));
for(int i = 0; i < tickMajorCount; i++) {
angle = (float) (((tickAngleEnd - tickAngleStart) * i / (tickMajorCount - 1) + tickAngleStart) / 180 * Math.PI);
number = (numberMax - numberMin) * i / (tickMajorCount - 1) + numberMin;
str = String.valueOf(number);
paint.getTextBounds(str, 0, str.length(), rect);
double c = Math.cos(angle);
double s = Math.sin(angle);
if(rect.width() * Math.abs(s) < rect.height() * Math.abs(c)) {
x2 = (float) (Math.signum(c) * rect.width() / 2);
y2 = (float) (Math.tan(angle) * x2);
} else {
y2 = (float) (Math.signum(s) * rect.height() / 2);
x2 = (float) (1 / Math.tan(angle) * y2);//Math.cotg(angle) * y;
}
x1 = (float) (center_x + Math.cos(angle) * numberRadius - rect.width() / 2 - x2 * 1.25);
y1 = (float) (center_y - Math.sin(angle) * numberRadius + rect.height() / 2 + y2 * 1.25);
canvas.drawText(str, x1, y1, paint);
}
}
private void drawTitle(Canvas canvas) {
paint.reset();
paint.setTextSize(titleFontSize);
paint.setColor(getResources().getColor(android.R.color.black));
angle = (float) ((270.0 / 180) * Math.PI);
paint.getTextBounds(titleStr, 0, str.length(), rect);
x1 = (float) (center_x + Math.cos(angle) * titleRadius - rect.width() / 2);
y1 = (float) (center_y - Math.sin(angle) * titleRadius + rect.height() / 3);
canvas.drawText(titleStr, x1, y1, paint);
}
private void drawArrow(Canvas canvas) {
paint.reset();
paint.setColor(getResources().getColor(android.R.color.holo_red_dark));
paint.setStyle(Paint.Style.FILL);
paint.setAntiAlias(true);
//Calculate (x,y) coordinates for the arrow path.
temp = (tickAngleEnd - tickAngleStart) * sensorInput / numberMax + tickAngleStart;
angle = (float) (temp / 180 * Math.PI);
x1 = (float) (center_x + Math.cos(angle) * arrowTipRadius);
y1 = (float) (center_y - Math.sin(angle) * arrowTipRadius);
angle = (float) ((temp + 170) / 180 * Math.PI);
x2 = (float) (center_x + Math.cos(angle) * arrowRearRadius);
y2 = (float) (center_y - Math.sin(angle) * arrowRearRadius);
angle = (float) ((temp - 170) / 180 * Math.PI);
x3 = (float) (center_x + Math.cos(angle) * arrowRearRadius);
y3 = (float) (center_y - Math.sin(angle) * arrowRearRadius);
//Draw arrow path using calculated coordinates.
path.moveTo(x1, y1);
path.lineTo(x2, y2);
path.lineTo(x3, y3);
path.close();
canvas.drawPath(path, paint);
//Calculate (x,y) coordinates for dial center.
x1 = center_x;
y1 = center_y;
canvas.drawCircle(x1, y1, arrowCenterRadius, paint);
paint.setColor(getResources().getColor(android.R.color.darker_gray));
canvas.drawCircle(x1, y1, arrowPinRadius, paint);
}
}
My MainActivity:
package net.dynu.kubie.redneksldhlr;
import android.app.Activity;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;
import org.w3c.dom.Text;
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
hideSystemUI();
startGenerating();
}
#Override
protected void onResume() {
super.onResume();
hideSystemUI();
//demoData();
}
#Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
if (hasFocus) {
hideSystemUI();
//demoData();
}
}
private void hideSystemUI() {
// Enables regular immersive mode.
// For "lean back" mode, remove SYSTEM_UI_FLAG_IMMERSIVE.
// Or for "sticky immersive," replace it with SYSTEM_UI_FLAG_IMMERSIVE_STICKY
View decorView = getWindow().getDecorView();
decorView.setSystemUiVisibility(
View.SYSTEM_UI_FLAG_IMMERSIVE
// Set the content to appear under the system bars so that the
// content doesn't resize when the system bars hide and show.
| View.SYSTEM_UI_FLAG_LAYOUT_STABLE
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
// Hide the nav bar and status bar
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_FULLSCREEN);
}
private void startGenerating() {
DoSomethingThread randomWork = new DoSomethingThread();
randomWork.start();
}
public class DoSomethingThread extends Thread {
private static final String TAG = "DoSomethingThread";
private static final int DELAY = 15000; // 5 seconds
private static final int RANDOM_MULTIPLIER = 120;
#Override
public void run() {
//Log.v(TAG, "doing work in Random Number Thread");
while (true) {
float randNum = (float) (Math.random() * RANDOM_MULTIPLIER);
// need to publish the random number back on the UI at this point in the code through the publishProgress(randNum) call
publishProgress(randNum);
try {
Thread.sleep(DELAY);
} catch (InterruptedException e) {
// Log.v(TAG, "Interrupting and stopping the Random Number Thread");
return;
}
}
}
}
private void publishProgress(float randNum) {
//Log.v(TAG, "reporting back from the Random Number Thread");
//final String text = String.format(getString(R.string.service_msg), randNum);
//final String text = Integer.toString(randNum);
final float text = randNum;
runOnUiThread(new Runnable() {
#Override
public void run() {
updateResults(text);
}
});
}
public void updateResults(float results) {
//TextView myTextView = (TextView) findViewById(R.id.testTextView);
DialGaugeView myDial = findViewById(R.id.my_gauge);
myDial.setSensorInput(results);
}
}
I eventually realized that my issue wasn't artifacts at all. My arrow is being drawn as a series of paths. If I do not reset the path prior to adding coordinates for the new arrow, the previous arrow(s) just get dragged along. Adding path.reset(); prior to the addition of the first arrow coordinate solved my problem.
//Draw arrow path using calculated coordinates.
path.reset();
path.moveTo(x1, y1);
path.moveTo(x2, y2);
path.moveTo(x3, y3);
path.close();
canvas.drawPath(path, paint);
Hi i am creating indicator for doughnut chart but it is covering only three side. Here i added onDraw method.The selected chart index arc should be highlighted with indicator.The indicator should be cover all the four sides.
Code:
#Override
public void draw(Canvas canvas, int x, int y, int width, int height, Paint paint) {
paint.setAntiAlias(true);
paint.setStyle(Style.FILL);
int legendSize = getLegendSize(mRenderer, height / 5, 0);
int left = x;
int top = y;
int right = x + width;
int sLength = mDataset.getItemCount();
double total = 0;
String[] titles = new String[sLength];
for (int i = 0; i < sLength; i++) {
total += mDataset.getValue(i);
titles[i] = mDataset.getCategory(i);
}
if (mRenderer.isFitLegend()) {
legendSize = drawLegend(canvas, mRenderer, titles, left, right, y, width, height, legendSize, paint, true);
}
int bottom = y + height - legendSize;
drawBackground(mRenderer, canvas, x, y, width, height, paint, false, Renderer.NO_COLOR);
float currentAngle = mRenderer.getStartAngle();
float labelCurrentAngle = mRenderer.getStartAngle();
// int mRadius = Math.min(Math.abs(right - left), Math.abs(bottom - top));
// int radius = (int) (500 * 0.35) + 50;
//Log.i("radius++", "" + radius);
int mRadius = Math.min(Math.abs(right - left), Math.abs(bottom - top));
double rCoef = 0.35 * mRenderer.getScale();
double decCoef = 0.2 / sLength;
int radius = (int) (mRadius * rCoef);
if (mCenterX == 0) {
mCenterX = (left + right) / 2;
}
if (mCenterY == 0) {
mCenterY = (bottom + top) / 2;
}
// Hook in clip detection after center has been calculated
mPieMap.setDimensions(radius, mCenterX, mCenterY);
boolean loadPieCfg = !mPieMap.areAllSegmentPresent(sLength);
if (loadPieCfg) {
mPieMap.clearPieSegments();
}
float shortRadius = radius * 0.9f;
float longRadius = radius * 1.1f;
RectF oval = new RectF(mCenterX - radius, mCenterY - radius, mCenterX + radius, mCenterY + radius);
for (int i = 0; i < sLength; i++) {
float value = (float) mDataset.getValue(i);
float angle = (float) (value / total * 360);
int color = mRenderer.getRenderers().get(i).getColor();
if (mRenderer.getRenderers().get(i).isClicked()) {
ClickedArc.CANVAS = canvas;
ClickedArc.CURRENT_ANGLE = currentAngle;
ClickedArc.ANGLE = angle;
ClickedArc.COLOR = color;
ClickedArc.INDEX = mRenderer.getRenderers().get(i).getDataIndex();
} else {
Paint paint3 = new Paint(Paint.ANTI_ALIAS_FLAG);
paint3.setStrokeCap(Paint.Cap.ROUND);
paint3.setStyle(Style.FILL);
int[] colors = {color, color};
RadialGradient gradient3 = new RadialGradient(mCenterX, mCenterY, radius, colors, null, android.graphics.Shader.TileMode.CLAMP);
paint3.setShader(gradient3);
paint3.setAntiAlias(true);
canvas.drawArc(oval, currentAngle, angle, true, paint3);
}
try {
if (mRenderer.getRenderers().get(ClickedArc.INDEX).isClicked()) {
Paint paint3 = new Paint(Paint.ANTI_ALIAS_FLAG);
paint3.setStyle(Style.FILL);
paint3.setColor(Color.WHITE);
Paint shadow = new Paint(Paint.ANTI_ALIAS_FLAG);
int[] colors = {Color.BLACK, Color.BLACK};
RadialGradient gradient3 = new RadialGradient(mCenterX, mCenterY, radius, colors, null, android.graphics.Shader.TileMode.CLAMP);
shadow.setShader(gradient3);
shadow.setAntiAlias(true);
shadow.setColor(Color.BLACK);
shadow.setStrokeWidth(8);
shadow.setDither(true);
shadow.setStyle(Paint.Style.STROKE);
shadow.setStrokeCap(Paint.Cap.BUTT);
shadow.setAntiAlias(true);
int shadowRadius = radius + 2;
RectF shadowOval = new RectF(mCenterX - shadowRadius, mCenterY - shadowRadius, mCenterX + shadowRadius, mCenterY + shadowRadius);
canvas.drawArc(shadowOval, ClickedArc.CURRENT_ANGLE - 1, ClickedArc.ANGLE + 2, true, shadow);
canvas.drawArc(oval, ClickedArc.CURRENT_ANGLE, ClickedArc.ANGLE, true, paint3);
}
} catch (IndexOutOfBoundsException e) {
e.printStackTrace();
}
if (loadPieCfg) {
mRenderer.getRenderers().get(i).setDataIndex(i);
mPieMap.addPieSegment(i, value, currentAngle, angle + currentAngle);
}
currentAngle += angle;
}
radius -= (int) mRadius * decCoef;
shortRadius -= mRadius * decCoef - 2;
List<RectF> prevLabelsBounds = new ArrayList<RectF>();
for (int i = 0; i < sLength; i++) {
float value = (float) mDataset.getValue(i);
float angle = (float) (value / total * 360);
drawLabel(canvas, mDataset.getCategory(i), mRenderer, prevLabelsBounds, mCenterX, mCenterY, shortRadius / 2 + 50, longRadius / 2 + 50,
labelCurrentAngle, angle, left, right, mRenderer.getLabelsColor(), paint, true, mRenderer.getRenderers().get(i));
Point sPoint = mRenderer.getRenderers().get(i).getCenterPoint();
Point ePoint = new Point((int) mRenderer.getRenderers().get(i).getTextWidth(), (int) mRenderer.getRenderers().get(i).getTextHeight());
mPieMap.addLabelSegment(i, value, sPoint, ePoint);
labelCurrentAngle += angle;
}
prevLabelsBounds.clear();
}
I've managed to draw a staircase and also a smiley face (called Avatar in the code) using paths in my custom view. Id like to move the happy face up the stairs using my nextStep function. nextStep iterates the current step and then calls a function called moveToStep(int step) which offsets the Avatar to the location of the specified step. Lastly, nextStep calls this.invalidate so that onDraw gets called again in hopes that the Avatar is redrawn at the new offset. The problem is that after nextStep is called, the Avatar disappears even though the staircase still remains. I know its not offscreen because I checked the coordinates the smiley is offset to.
Custom View Code
public class StaircaseView extends View {
// setup initial color
private final int paint_color = Color.BLACK;
private int curr_step = 1;
int STEPS = 5;
private float avatar_radius;
// defines paint and canvas
private Paint DrawPaint;
private int view_width, view_height, view_size;
private Path StaircasePath, Avatar;
private float scale, side_length;
private char constrainer;
public StaircaseView(Context context, AttributeSet attrs) {
super(context, attrs);
setupPaint();
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Log.d("StaircaseView", "onDraw called");
canvas.drawPath(StaircasePath, DrawPaint);
canvas.drawPath(Avatar, DrawPaint);
}
#Override
protected void onSizeChanged(int xNew, int yNew, int xOld, int yOld){
super.onSizeChanged(xNew, yNew, xOld, yOld);
view_height = yNew;
view_width = xNew;
view_size = Math.min(view_height, view_width);
if (view_width == view_size) {
constrainer = 'w';
} else {
constrainer = 'h';
}
scale = (float)view_size/100;
Log.i("StaircaseView", "onSizeChanged --> view width: " + String.valueOf(xNew) + ", view height: " + String.valueOf(yNew) + ", scale: " + String.valueOf(scale) + ", view size: " + String.valueOf(view_size));
float padding = 5 * scale;
StaircasePath = createStaircase(padding);
avatar_radius = 7*scale;
Avatar = createAvatar(0, 0, avatar_radius);
StaircasePath.offset(0, view_height);
moveToStep(curr_step);
}
public void nextStep() {
if (curr_step < STEPS) {
curr_step++;
} else {
curr_step = 1;
}
moveToStep(curr_step);
this.invalidate();
}
private void moveToStep(int step) {
float x = step * side_length - avatar_radius;
float y = view_height - (step - 1) * side_length - avatar_radius;
Log.i("StaircaseView", String.valueOf(x) + ", " + String.valueOf(y));
Avatar.offset(x, y);
}
// Setup paint with color and stroke styles
private void setupPaint() {
DrawPaint = new Paint();
DrawPaint.setColor(paint_color);
DrawPaint.setAntiAlias(true);
DrawPaint.setStrokeWidth(5);
DrawPaint.setStyle(Paint.Style.STROKE);
DrawPaint.setStrokeJoin(Paint.Join.ROUND);
DrawPaint.setStrokeCap(Paint.Cap.ROUND);
}
private Path createStaircase(float padding) {
if (constrainer == 'w') {
side_length = (view_size-padding)/(STEPS+1);
} else {
side_length = (view_size-padding)/STEPS;
}
Path path = new Path();
float curr_x = 0;
float curr_y = 0;
path.moveTo(curr_x, curr_y);
curr_x += side_length;
path.lineTo(curr_x, curr_y);
for(int i=0; i<STEPS; i++) {
curr_y -= side_length;
path.lineTo(curr_x, curr_y);
curr_x += side_length;
path.lineTo(curr_x, curr_y);
}
path.lineTo(curr_x, 0);
path.lineTo(0, 0);
return path;
}
private Path createShape(ArrayList<PointF> points) {
Path path = new Path();
path.moveTo(points.get(0).x, points.get(0).y);
for(int i=1; i< points.size(); i++) {
path.lineTo(points.get(i).x, points.get(i).y);
}
return path;
}
private Path createAvatar(int x, int y, float radius){
Path avatar = new Path();
float width = radius*2;
avatar.addCircle(x, y, radius, Path.Direction.CW);
avatar.addCircle(x - (radius /2), y - (radius / 5), radius/5, Path.Direction.CW);
avatar.addCircle(x + (radius / 2), y - (radius / 5), radius / 5, Path.Direction.CW);
avatar.addRect((float) x - (radius / 5), (float) y - (radius / 5), (float) x + (radius / 5), (float) y - (radius/5), Path.Direction.CCW);
return avatar;
}
}
After looking at this question decided to figure out another way to do this. So what I did was I changed my moveToStep method to return the coordinates to move the Avatar to instead of offsetting him. I then passed these coordinates to the createAvatar method as start coordinates. It worked after that.
New Code
/**
* Created by yako on 11/5/15.
*/
public class StaircaseView extends View {
// setup initial color
private final int paint_color = Color.BLACK;
private int curr_step = 1;
int STEPS = 5;
private float avatar_radius;
// defines paint and canvas
private Paint DrawPaint;
private int view_width, view_height, view_size;
private Path StaircasePath, Avatar;
private float scale, side_length;
private char constrainer;
public StaircaseView(Context context, AttributeSet attrs) {
super(context, attrs);
setupPaint();
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Log.d("StaircaseView", "onDraw called");
canvas.drawPath(StaircasePath, DrawPaint);
canvas.drawPath(Avatar, DrawPaint);
}
#Override
protected void onSizeChanged(int xNew, int yNew, int xOld, int yOld){
super.onSizeChanged(xNew, yNew, xOld, yOld);
view_height = yNew;
view_width = xNew;
view_size = Math.min(view_height, view_width);
if (view_width == view_size) {
constrainer = 'w';
} else {
constrainer = 'h';
}
scale = (float)view_size/100;
Log.i("StaircaseView", "onSizeChanged --> view width: " + String.valueOf(xNew) + ", view height: " + String.valueOf(yNew) + ", scale: " + String.valueOf(scale) + ", view size: " + String.valueOf(view_size));
float padding = 5 * scale;
// Log.i("StaircaseView", String.valueOf(view_height/2 + padding/2));
StaircasePath = createStaircase(padding);
// Center the staircase in the view
// if (constrainer == 'w') {
// StaircasePath.offset(padding/2, view_height - (view_height - view_width - padding*2));
// } else {
// StaircasePath.offset(view_height/2 - padding/2, view_height-padding/2);
// }
avatar_radius = 7*scale;
Avatar = createAvatar(moveToStep(curr_step), avatar_radius);
StaircasePath.offset(0, view_height);
}
public void nextStep() {
if (curr_step <= STEPS) {
curr_step++;
} else {
curr_step = 1;
}
Avatar = createAvatar(moveToStep(curr_step), avatar_radius);
this.invalidate();
}
private float[] moveToStep(int step) {
float[] offset = new float[2];
offset[0] = step * side_length - avatar_radius;
offset[1] = view_height - (step - 1) * side_length - avatar_radius;
Log.i("StaircaseView", "Moving avatar to "+ String.valueOf(offset[0]) + ", " + String.valueOf(offset[1]));
return offset;
}
// Setup paint with color and stroke styles
private void setupPaint() {
DrawPaint = new Paint();
DrawPaint.setColor(paint_color);
DrawPaint.setAntiAlias(true);
DrawPaint.setStrokeWidth(5);
DrawPaint.setStyle(Paint.Style.STROKE);
DrawPaint.setStrokeJoin(Paint.Join.ROUND);
DrawPaint.setStrokeCap(Paint.Cap.ROUND);
}
private Path createStaircase(float padding) {
if (constrainer == 'w') {
side_length = (view_size-padding)/(STEPS+1);
} else {
side_length = (view_size-padding)/(STEPS+1);
}
Path path = new Path();
float curr_x = 0;
float curr_y = 0;
path.moveTo(curr_x, curr_y);
curr_x += side_length;
path.lineTo(curr_x, curr_y);
for(int i=0; i<STEPS; i++) {
curr_y -= side_length;
path.lineTo(curr_x, curr_y);
curr_x += side_length;
path.lineTo(curr_x, curr_y);
}
path.lineTo(curr_x, 0);
path.lineTo(0, 0);
return path;
}
private Path createShape(ArrayList<PointF> points) {
Path path = new Path();
path.moveTo(points.get(0).x, points.get(0).y);
for(int i=1; i< points.size(); i++) {
path.lineTo(points.get(i).x, points.get(i).y);
}
return path;
}
private Path createAvatar(float[] offset, float radius){
float x = offset[0];
float y = offset[1];
Path avatar = new Path();
float width = radius*2;
avatar.addCircle(x, y, radius, Path.Direction.CW);
avatar.addCircle(x - (radius /2), y - (radius / 5), radius/5, Path.Direction.CW);
avatar.addCircle(x + (radius / 2), y - (radius / 5), radius / 5, Path.Direction.CW);
avatar.addRect((float) x - (radius / 5), (float) y - (radius / 5), (float) x + (radius / 5), (float) y - (radius/5), Path.Direction.CCW);
return avatar;
}
}
Why this question is not a duplicate: The reference question explains how to add a single string to a curved path.. I'm trying to find out how to to add two strings with different fonts/styles to a curved path
I have two string that need to be drawn with different styles on the upper part of a circle.
I have tried a couple of things but all I manage to achieve is to draw each string on top of each other or on opposite ends (with a huge gap between them).
This is an example of what I'm trying to achieve:
Here is the code I have so far:
public class LabeledImageView extends ImageView
{
Path path;
Paint infoTextPaint;
Paint titleTextPaint;
String stringTitle = "Test name ";
String stringInfo = "0/20";
public LabeledImageView(Context context)
{
super(context);
}
public LabeledImageView(Context context, AttributeSet attrs)
{
super(context, attrs);
}
public LabeledImageView(Context context, AttributeSet attrs, int defStyleAttr)
{
super(context, attrs, defStyleAttr);
}
#SuppressLint("NewApi")
public LabeledImageView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)
{
super(context, attrs, defStyleAttr, defStyleRes);
}
private void buildArch()
{
int textColor = Color.WHITE;
float cx = (float) getWidth() / 2.0f;
float cy = (float) getHeight() / 2.0f;
Point textSizePoint;
float rInn;
float rOut;
float endX;
float varTextSize = 14.0f;
CFTextView temp = new CFTextView(getContext());//CFTextView is a TextView that uses a custom font
temp.setSingleLine(true);
temp.setTypeface(null, Typeface.BOLD);
temp.setText(stringTitle + stringInfo);
temp.setLayoutParams(
new ViewGroup.LayoutParams(
(int) ((cx / 2.0f) * Math.PI),
ViewGroup.LayoutParams.WRAP_CONTENT
)
);
temp.setPadding(0, 0, 0, 0);
do
{
temp.setTextSize(varTextSize);
textSizePoint = Utils.measureView(
getContext(),
(int) ((cx / 2.0f) * Math.PI),
temp,
true
);//This method forces the view to draw and returns a Point with the final size of the view
rInn = (float) getWidth() / 2.0f;
rOut = rInn + textSizePoint.y;
endX = (float) textSizePoint.x / 2.0f;
varTextSize--;
}while(endX >= rOut);
float textSize = TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_SP,
varTextSize,
Resources.getSystem().getDisplayMetrics()
);
float smallTextSize = TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_SP,
varTextSize - 2 > 2 ? varTextSize - 2 : varTextSize,
Resources.getSystem().getDisplayMetrics()
);
float angle = 90.0f - (float) Math.toDegrees(Math.acos(endX / rOut));
float startAngle = -(angle + 90.0f);
float sweepAngle = 2.0f * angle;
titleTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
titleTextPaint.setStyle(Paint.Style.FILL_AND_STROKE);
titleTextPaint.setColor(textColor);
titleTextPaint.setTextSize(textSize);
titleTextPaint.setTextAlign(Paint.Align.CENTER);
titleTextPaint.setTypeface(
FontManager.getFontUrl(
getContext(),
Typeface.BOLD
)//FontManager.getFontUrl returns a custom TypeFace
);
infoTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
infoTextPaint.setStyle(Paint.Style.FILL_AND_STROKE);
infoTextPaint.setColor(textColor);
infoTextPaint.setTextSize(smallTextSize);
infoTextPaint.setTextAlign(Paint.Align.CENTER);
infoTextPaint.setTypeface(
FontManager.getFontUrl(
getContext(),
Typeface.NORMAL
)//FontManager.getFontUrl returns a custom TypeFace
);
path = new Path();
float r = rInn;
r += textSize * 0.1f;
RectF segment = new RectF(cx - r, cy - r, cx + r, cy + r);
path.addArc(segment, startAngle, sweepAngle);
}
#Override
protected void onDraw(Canvas canvas)
{
super.onDraw(canvas);
if(path == null && getWidth() > 0)
{
buildArch();
}
if(path != null)
{
canvas.drawTextOnPath(
stringTitle,
path,
0,
0,
titleTextPaint
);
canvas.drawTextOnPath(
stringInfo,
path,
0,
0,
infoTextPaint
);
}
}
}
Is there a way to achieve this on Android?
As I was about to set a bounty on this question I got an idea.. and it worked :D
I calculated half the size of the title text and added it as an horizontal offset to the draw call for the stringInfo and subtracted half the size of the info text to the horizontal offset to the draw call for the stringTitle.
Here is the full code:
public class LabeledImageView extends ImageView
{
Path path;
Paint infoTextPaint;
Paint titleTextPaint;
String stringTitle = "Test name ";
String stringInfo = "0/20";
float titleHorizontalOffset = 0.0f;//Added this line
float infoHorizontalOffset = 0.0f;//Added this line
public LabeledImageView(Context context)
{
super(context);
}
public LabeledImageView(Context context, AttributeSet attrs)
{
super(context, attrs);
}
public LabeledImageView(Context context, AttributeSet attrs, int defStyleAttr)
{
super(context, attrs, defStyleAttr);
}
#SuppressLint("NewApi")
public LabeledImageView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)
{
super(context, attrs, defStyleAttr, defStyleRes);
}
private void buildArch()
{
int textColor = Color.WHITE;
float cx = (float) getWidth() / 2.0f;
float cy = (float) getHeight() / 2.0f;
Point textSizePoint;
float rInn;
float rOut;
float endX;
float varTextSize = 14.0f;
CFTextView temp = new CFTextView(getContext());//CFTextView is a TextView that uses a custom font
temp.setSingleLine(true);
temp.setTypeface(null, Typeface.BOLD);
temp.setText(stringTitle + stringInfo);
temp.setLayoutParams(
new ViewGroup.LayoutParams(
(int) ((cx / 2.0f) * Math.PI),
ViewGroup.LayoutParams.WRAP_CONTENT
)
);
temp.setPadding(0, 0, 0, 0);
do
{
temp.setTextSize(varTextSize);
textSizePoint = Utils.measureView(
getContext(),
(int) ((cx / 2.0f) * Math.PI),
temp,
true
);//This method forces the view to draw and returns a Point with the final size of the view
rInn = (float) getWidth() / 2.0f;
rOut = rInn + textSizePoint.y;
endX = (float) textSizePoint.x / 2.0f;
varTextSize--;
}while(endX >= rOut);
float textSize = TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_SP,
varTextSize,
Resources.getSystem().getDisplayMetrics()
);
float smallTextSize = TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_SP,
varTextSize - 2 > 2 ? varTextSize - 2 : varTextSize,
Resources.getSystem().getDisplayMetrics()
);
float angle = 90.0f - (float) Math.toDegrees(Math.acos(endX / rOut));
float startAngle = -(angle + 90.0f);
float sweepAngle = 2.0f * angle;
titleHorizontalOffset = endX;//Added this line
temp.setTypeface(null, Typeface.NORMAL);
temp.setText(stringInfo);
temp.setTextSize(smallTextSize);
infoHorizontalOffset = Utils.measureView(
getContext(),
(int) ((cx / 2.0f) * Math.PI),
temp,
true
).x;//Added this line
titleTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
titleTextPaint.setStyle(Paint.Style.FILL_AND_STROKE);
titleTextPaint.setColor(textColor);
titleTextPaint.setTextSize(textSize);
titleTextPaint.setTextAlign(Paint.Align.CENTER);
titleTextPaint.setTypeface(
FontManager.getFontUrl(
getContext(),
Typeface.BOLD
)//FontManager.getFontUrl returns a custom TypeFace
);
infoTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
infoTextPaint.setStyle(Paint.Style.FILL_AND_STROKE);
infoTextPaint.setColor(textColor);
infoTextPaint.setTextSize(smallTextSize);
infoTextPaint.setTextAlign(Paint.Align.CENTER);
infoTextPaint.setTypeface(
FontManager.getFontUrl(
getContext(),
Typeface.NORMAL
)//FontManager.getFontUrl returns a custom TypeFace
);
path = new Path();
float r = rInn;
r += textSize * 0.1f;
RectF segment = new RectF(cx - r, cy - r, cx + r, cy + r);
path.addArc(segment, startAngle, sweepAngle);
}
#Override
protected void onDraw(Canvas canvas)
{
super.onDraw(canvas);
if(path == null && getWidth() > 0)
{
buildArch();
}
if(path != null)
{
canvas.drawTextOnPath(
stringTitle,
path,
-titleHorizontalOffset,//Changed this line
0,
titleTextPaint
);
canvas.drawTextOnPath(
stringInfo,
path,
infoHorizontalOffset - titleHorizontalOffset,//Changed this line
0,
infoTextPaint
);
}
}
}
I have a dial that I display wind direction in and the arrow displays well in some positions, but others its edges are pixelated. Here is the code to render the image:
public class DialView extends View {
private Context mContext;
private Bitmap mArrow;
private WeatherDataModel mWdm;
private float iters = 10.0f;
private static float previousAngle = 0.0f;
private int mHourIndex = 0;
private boolean isHourly = false;
private final int XLARGE = 0x4;
public DialView(Context context, AttributeSet attrs) {
super(context, attrs);
mContext = context;
int screenLayout = mContext.getApplicationContext().getResources().getConfiguration().screenLayout;
mArrow = Utilities.applyFilter(context, BitmapFactory.decodeResource(context.getResources(), R.drawable.wind_arrow));
}
#Override
public void onDraw(Canvas canvas) {
float degrees = 0.0f;
degrees = (!isHourly) ? cardinalToDegrees(mWdm) : cardinalToDegrees(mWdm.hourly.get(mHourIndex));
Bitmap bit;
int originY = getHeight() / 2;
int originX = getWidth() / 2;
int r = originY > originX ? getWidth() * 8 / 27 : getHeight() * 8 / 27;
int x, y;
Matrix matrix = new Matrix();
degrees = (previousAngle * (iters / 10.0f) + degrees * (10.0f - iters) / 10.0f);
//Log.d(DEBUG_TAG, "Previous angle = " + previousAngle + " degrees" + degrees);
matrix.postRotate(degrees - 90.f);
bit = Bitmap.createBitmap(mArrow, 0, 0, mArrow.getWidth(), mArrow.getHeight(), matrix, false);
x = (int)(Math.cos(Math.PI * degrees / 180.0f) * r) + originX - (bit.getWidth() / 2);
y = (int)(Math.sin(Math.PI * degrees / 180.0f) * r) + originY - (bit.getHeight() / 2);
//Log.d(DEBUG_TAG, "x: " + x + " y: " + y);
canvas.drawBitmap(bit, x, y, null);
if (iters > 0) {
invalidate();
iters--;
}
previousAngle = degrees;
}
Here is the arrow good:
Here it is pixelated:
Any ideas how to handle this?
try to define a paint object and enable AntiAlias
like this:
mPaint.setAntiAlias(true);
canvas.drawBitmap(bit, x, y, mPaint);