I a using MpAndroidChart library. I need to implement a design where I need to color the area between two limit lines. I have attached an image for reference. I have tried multiple ways but I have failed to achieve it. I am using this library for the first time. Can anyone help me about how this could be achieved.
As you can see the green shade behind the line graph. Which is the limit. I need to get that green shade
Thanks in advance,
Anudeep Reddy.
I don't think that there is a direct way to achieve this, but this workaround should help you:
LimitLine ll = new LimitLine(lowerLimit, "Systolic range");
ll.setLineColor(Color.GREEN);
ll.setLineWidth(upperLimit - lowerLimit);
ll.setTextColor(Color.WHITE);
ll.setTextSize(12f);
chart.getAxisLeft().setDrawLimitLinesBehindData(true);
The important thing here is the method setDrawLimitLinesBehindData(true).
As always, all the information is available in the documentation.
I had the same problem but reached a different workaround without having to subclass the LineChart. Using canvas to draw the rectangle works, but you have to translate your charts coordinates to the canvas coordinates. You cannot use a single limit line as there is a limit to the width of the line. The workaround I used was to simply loop through limit lines to create a rectangle within my range.
float increment = (rangeHigh - rangeLow) / 20;
float metricLine = rangeLow;
for(int i = 0; i < 20; i++) {
LimitLine llRange = new LimitLine(metricLine, "");
llRange.setLineColor(Color.parseColor("#b5eb45"));
llRange.setLineWidth(10f);
leftAxis.addLimitLine(llRange);
metricLine = metricLine + increment;
}
As this is still an issue I throw in my two cents.
I tried the solution of #HouseOfHufflepuff but I got the error message that I use too much limit lines in the plot. It seems to work anyway but I guess the performance is not optimal.
So I implemented a subclass for drawing zones in the background. Maybe it's helpful for someone:
public class TargetZoneCombinedChart extends CombinedChart {
protected Paint mYAxisSafeZonePaint;
private List<TargetZone> mTargetZones;
public TargetZoneCombinedChart(Context context) {
super(context);
}
public TargetZoneCombinedChart(Context context, AttributeSet attrs) {
super(context, attrs);
}
public TargetZoneCombinedChart(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
#Override
protected void init() {
super.init();
mYAxisSafeZonePaint = new Paint();
mYAxisSafeZonePaint.setStyle(Paint.Style.FILL);
// mGridBackgroundPaint.setColor(Color.rgb(240, 240, 240));
mTargetZones = new ArrayList<>();
}
#Override
protected void onDraw(Canvas canvas) {
for (TargetZone targetZone : mTargetZones) {
// prepare coordinates
float[] pts = new float[4];
pts[1] = targetZone.lowerLimit;
pts[3] = targetZone.upperLimit;
mLeftAxisTransformer.pointValuesToPixel(pts);
// draw
mYAxisSafeZonePaint.setColor(targetZone.color);
canvas.drawRect(mViewPortHandler.contentLeft(), pts[1], mViewPortHandler.contentRight(),
pts[3], mYAxisSafeZonePaint);
}
super.onDraw(canvas);
}
public void addTargetZone(TargetZone targetZone){
mTargetZones.add(targetZone);
}
public List<TargetZone> getTargetZones(){
return mTargetZones;
}
public void clearTargetZones(){
mTargetZones = new ArrayList<>();
}
public static class TargetZone {
public final int color;
public final float lowerLimit;
public final float upperLimit;
public TargetZone(int color, float lowerLimit, float upperLimit) {
this.color = color;
this.lowerLimit = lowerLimit;
this.upperLimit = upperLimit;
}
}
}
To add a zone you just need to add a target zone object:
float rangeHigh = 180f;
float rangeLow = 80f;
chart.addTargetZone(new TargetZoneCombinedChart.TargetZone( Color.parseColor("#33b5eb45"),rangeLow,rangeHigh));
whereby the ranges are y values of the left axis.
This can be done by sub-classing the chart class (e.g. LineChart) and then overriding the onDraw() method. In the overridden onDraw() you can draw the rectangle(s) you need directly onto the canvas and then call super.onDraw() to complete the rendering of the chart.
There is an example of how to do this on the MP Android Github (see below). I followed the code in the example and it worked well for me.
https://github.com/PhilJay/MPAndroidChart/issues/485
Related
I'm doing a school project. In this project I have to do a program that have one or more ball bouncing in the screen. I did some research on google to help me in this, and I found this code :
public class BouncingBallInside extends View {
private List<Ball> balls = new ArrayList<>();
public BouncingBallInside(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public BouncingBallInside(Context context) {
super(context);
init();
}
private void init(){
//Add a new ball to the view
balls.add(new Ball(50,50,100, Color.RED));
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//Draw the balls
for(Ball ball : balls){
//Move first
ball.move(canvas);
//Draw them
canvas.drawOval(ball.oval,ball.paint);
}
invalidate(); // See note
}
}
The ball class :
public class Ball{
public int x,y,size;
public int velX = 10;
public int velY=7;
public Paint paint;
public RectF oval;
public Ball(int x, int y, int size, int color){
this.x = x;
this.y = y;
this.size = size;
this.paint = new Paint();
this.paint.setColor(color);
}
public void move(Canvas canvas) {
this.x += velX;
this.y += velY;
this.oval = new RectF(x-size/2,y-size/2,x+size/2,y+size/2);
//Do we need to bounce next time?
Rect bounds = new Rect();
this.oval.roundOut(bounds); ///store our int bounds
//This is what you're looking for ▼
if(!canvas.getClipBounds().contains(bounds)){
if(this.x-size<0 || this.x+size > canvas.getWidth()){
velX=-velX;
}
if(this.y-size<0 || this.y+size > canvas.getHeight()){
velY=-velY;
}
}
}
}
The program works perfecly.
I studied it deeply as good as I could. But after it and after watching the documentation I couldn't understand two thing:
Where and when the method onDraw(Canvas canvas) is called the first time.
Why at the end of onDraw there is invalidate()?
I mean the documentation said :
Invalidate the whole view. If the view is visible, onDraw(android.graphics.Canvas) will be called at some point in the future.
so... if this method is used to call onDraw,why don't call it direcly? what's the difference?
1)The onDraw method will be called by the framework, whenever the view is invalid. A view is invalid when it first comes on screen, so when you set your content view for an activity they layout and all views in it will be measured, laid out, then drawn (via onDraw).
After that the UI thread will call onDraw if needed every 16ms or so (so it draws at 60 FPS).
2)Its marking the view as needing to be redrawn, so the next time the the screen is drawn onDraw will be called. Otherwise it would be skipped, as we could assume it isn't needed.
Why you don't call onDraw directly- efficiency. In a very simple drawing system you would- but drawing is time consuming, you don't want to do it more than you have to. So instead of drawing immediately (which wouldn't work anyway, you wouldn't have the right Canvas to pass to onDraw), you call invalidate and the system will call onDraw if needed at a regular interval.
Note that this isn't particularly good code. In particular, having the onDraw trigger the move which updates the balls location instead of using a timer is icky. Having onDraw call invalidate as a result is also kind of icky. A better solution would be to separate the view, model, and timer into more of an MVC or MVP system.
I am trying to draw a single line in Android using canvas
My class :
public class LineDrawer extends View {
public LineDrawer(Context context) {
super(context);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Paint paint = new Paint();
paint.setColor(Color.BLACK);
paint.setStyle(Paint.Style.FILL_AND_STROKE);
paint.setStrokeWidth(10);
float left = 20;
float top = 20;
float right = 50;
float bottom = 100;
canvas.drawLine(left, top, right, bottom, paint);
}
}
My Main Activity :
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
LineDrawer lineDrawer = new LineDrawer(this);
setContentView(R.layout.activity_Main);
}
}
I cannot find where is the problem , I try all the solutions in the internet but nothing happen , still a blank activity..
Should I import some code ?
lineDrawer is created but not added anywhere. Just creating a view is not enough, you need to add it to the current displayed views to be taken into account and drawn. You have two options:
Add it to your XML layout. You will have to add the following constructor to your custom view.
public LineDrawer(Context context, AttributeSet attrs) {
super(context, attrs);
}
Use addView(). Anyway, given how simple is your example, I'll use first (common) method.
As an additional comment, the Paint paint object should be created on view initialization, as is a costly operation. See in the original documentation for more information about this.
I'm looking to replicate the following within my application:
As you can see, its basically a button which increases/decreases the value of the text view contained within it. This button will have three visual states -> unpressed, decrease and increase (as seen in the image above, the user taps the increase arrows and the button appears pressed in on that side)
Here are my 3 button states currently:
As you can see, the problem I have is being able to correctly skew/rotate the text view so it looks visually correct and appears slanted along with the button when its being increased or decreased.
I have tried two different approaches so far:
Create a custom text view class which overrides the onDraw() method to skew the canvas:
#Override
public void onDraw(Canvas canvas) {
canvas.save();
canvas.skew(0.2f, 0f);
super.onDraw(canvas);
canvas.restore();
}
Integrate the Rotate3dAnimation class (source here) and used many different variations to get the desired result such as:
Rotate3dAnimation skew = new Rotate3dAnimation(
30, 0, centerX, centerY, 0, false);
txtAmount.startAnimation(skew);
Unfortunately, I'm not quite getting the exact result that mirrors the first image above. I'm getting confused with setting values with the Z-axis, skew, rotate etc.
I'd greatly appreciate any help from anyone who has experience with this stuff. Thanks in advance
Well I even tried and I came up with something like this:
public class DemoActivity extends TextView {
Context context;
String firstText = "$120.00";
public DemoActivity(Context context)
{
super(context);
this.context = context;
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
setText(firstText);
setTextSize(30);
canvas.skew(1.0f, 0.3f); //you need to change values over here
Rotate3dAnimation skew = new Rotate3dAnimation(
-20, 30,200, 200, 0, false); //here too
startAnimation(skew);
}
}
I got an output as:
I guess changing the values by trial and error can solve your problem.
Hope it helps.
Thanks to Parth Doshi answer. His answer need a little tweaking to run which I'm sharing here to save someone else time.
First create a class in src folder and write all of three constructors.
public class TextViewDemo extends TextView {
Context context;
String text = "TESTING 3DX TOOLS";
public TextViewDemo(Context context) {
super(context);
this.context = context;
}
public TextViewDemo(Context context, AttributeSet attrs) {
super(context, attrs);
this.context = context;
}
public TextViewDemo(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
this.context = context;
}
#Override
protected void onDraw(Canvas canvas) {
// TODO Auto-generated method stub
super.onDraw(canvas);
setText(text);
setTextSize(30);
canvas.skew(0.5f, 1.0f); // you need to change values over here
Rotate3dAnimation skew = new Rotate3dAnimation(-50, 30, 0, 0, 0,
false); // here too
startAnimation(skew);
}
}
In you res/layout/my_layout.xml file you can add a tag of your custom made TextView.
<com.yourpackage.name.TextViewDemo
android:id="#+id/name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:text="Hello World"
<!-- All parameters and value shall remain same -->
/>
Like any other view, you can create an instance of TextViewDemo in your onCreate() method
TextViewDemo txtDemo = (TextViewDemo) findViewById(R.id.name);
Regards
OnDraw function for my custom View is being called infinitely and is looping !! What could be possible reason??
Here is my custom view:-
public class Balls extends View{
private static final String TAG = "BallsView";
private int mMode = READY;
public static final int PAUSE = 0;
public static final int READY = 1;
public static final int RUNNING = 2;
public static final int LOSE = 3;
private final Paint mPaint = new Paint();
private double mUx = 0.1;
private double mUy = 2;
private double mVy;
private double mVx;
private double mSx;
private double mSy;
private double mRange;
private float mX1;
private float mY1;
private int mX2;
private int mY2;
private int mDx;
private int mDy;
Time t;
float mAngle;
private final double mGravity = -9.8;
private long mLastTime;
private double mT;
private Canvas mCanvas = null;
public Balls(Context context, AttributeSet attrs, int defStyle){
super(context, attrs, defStyle);
setFocusable(true);
setWillNotDraw(false);
mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
mPaint.setStrokeWidth(10);
mPaint.setAntiAlias(true);
mPaint.setStrokeCap(Cap.ROUND);
//mPaint.setColor(0xff00ffff);
mPaint.setARGB(255, 0, 255, 0);
mLastTime = System.currentTimeMillis();
}
public Balls(Context context, AttributeSet attrs) {
super(context, attrs);
setFocusable(true);
setWillNotDraw(false);
mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
mPaint.setStrokeWidth(10);
mPaint.setAntiAlias(true);
mPaint.setStrokeCap(Cap.ROUND);
mPaint.setColor(0xff00ffff);
}
#Override
public void onDraw(Canvas canvas) {
Log.w(this.getClass().getName(),"onDraw of Balls called");
super.onDraw(canvas);
mCanvas = canvas;
if(mCanvas!= null)
Log.w(this.getClass().getName(),"Canvas is not null");
}
This view is inflated as follows in another activity:-
mBalls = (Balls) findViewById(R.id.balls);
This view is placed in xml file inside a relative view and the relative view is the child of horizontal scroll view.
onDraw(Canvas) called too often like in an infinite loop, is not normal.
normally it should be called 1 to 3 times, if there is no following invalidate or layout changes.
reasons for infinite loop maybe:
1, you called invalidate or postInvalidate some where.
2, parent or sibling layout is changing all the time.
3, View.LAYER_TYPE_SOFTWARE is used, es. setLayerType(View.LAYER_TYPE_SOFTWARE, null).
it is notable that LAYER_TYPE_SOFTWARE will cause onDraw() to be called, like in a loop.
There isn't an infinite loop here. What is going on is the OS is redrawing your activity as fast as possible. When your activity gets redrawn it redraws all of its children views. As your code has very little computation, from what I can see here, it is running very fast and is probably redrawing at a rate >30 FPS. Your log message makes it appear as if there is an infinite loop when there isn't. In fact there isn't even a loop inside your onDraw method.
To illustrate what is going on try this. Add a protected member drawCount to your Balls class and set it to 0:
protect int drawCount = 0;
Then append drawCount to the end of your onDraw log message
public void onDraw(Canvas canvas){
drawCount++;
Log.w(this.getClass().getName(),"onDraw of Balls called. Total draws:" + Integer.toString(drawCount));
...
}
What you should see is each log message will display a different drawCount.
If you want to get fancy and calculate the framerate of your app you could measure the time since the first draw and then divide the drawCount by how much time has passed which would give you a estimate of your activities framerate.
onDraw() get's call at invalidate. Invalidate() get's called when the view or it's parent feel the need to change and have to change it's state.
For me that was happening because i had some code that was modifying UI inside overridden DispatchDraw..
well, im a begginer in android and i need to use maps on the device more specifically (polylines) i need to do something like this.
this is a web app i did to track down bus routes and bus-stops on my city , and i've been asked to do the same thing in android! ive been checking the maps api for android and did not found anything similar to polyline in JS api , is there a way to achieve this?
i have no problem adding simple overlays i've been checking the basic tutorials in android developer site, but i dont know how to draw the polyline.
There no such API in Android Google Maps API. You can only first list the actual GeoPoints of the route that you want to draw and then draw the points and lines on a Overlay object. There's just no easy way to do that.
A more easy way to do that is get your points and extend the ImageView that will display your image to draw the points, than you just need to pass the points that you want to draw .
In my project I did this:
public class ImageDraw extends ImageView{
private Paint mPaint = new Paint();
List<Point> pts = new ArrayList<Point>() ;
public ImageDraw(Context context) {
super(context);
}
//used to send the location of the points to draw on the screen
//must be called before every redraw to update the points on the screen
public void SetPointsToDraw(List<Point> pts)
{
this.pts = pts;
}
public ImageDraw(Context context, AttributeSet attrs)
{
super(context,attrs);
}
public ImageDraw(Context context, AttributeSet attrs, int defStyle)
{
super(context, attrs, defStyle);
}
#Override
public void onDraw(Canvas canvas)
{
super.onDraw(canvas);
Paint paintColor = mPaint;
paintColor.setColor(Color.YELLOW);
paintColor.setStrokeWidth(3);
if(pts.size() > 0)
{
canvas.drawCircle(pts.get(0).x, pts.get(0).y, 7, paintColor);
}
if (pts.size() > 1)
{
for (int i = 1 ; i < pts.size(); i++) {
paintColor.setColor(Color.YELLOW);
canvas.drawCircle(pts.get(i).x, pts.get(i).y, 7, paintColor);
paintColor.setColor(Color.RED);
canvas.drawLine(pts.get(i-1).x, pts.get(i-1).y, pts.get(i).x, pts.get(i).y, paintColor);
}
}
}
}
When you extends the Imageview and create the layout with xml don`t forget to put the entire package of you new widget like:
com.Myapp.MyImageView
FvZ's answer works but is not the native way, there are polylines on the map, a simple example
https://stackoverflow.com/a/21877742/618419
Also take a look at the Android Documentation, they have many simple and well put-together examples:
http://developer.android.com/reference/com/google/android/gms/maps/model/Polyline.html