Related
I'm trying to figure out how to fill an View clockwise.
I figured out how to rotate an image but not sure how to rotate and fill.
Used this class to create a circle:
public class Circle extends View {
private static final int START_ANGLE_POINT = 270;
private final Paint paint;
private RectF rect;
private float angle;
public Circle(Context context, AttributeSet attrs) {
super(context, attrs);
final int strokeWidth = 60;
Point[] points = new Point[3];
points[0] = new Point(7, 13);
points[1] = new Point(13, 19);
points[2] = new Point(21, 9);
paint = new Paint();
paint.setAntiAlias(true);
paint.setStyle(Paint.Style.FILL);
//Circle color
paint.setColor(Theme.darkRedColour());
//Initial Angle (optional, it can be zero)
angle = 0;
}
#Override
protected void onDraw(Canvas canvas) {
if (rect == null) {
DisplayMetrics metrics = App.getAppContext().getResources().getDisplayMetrics();
int densityDpi = (int)metrics.density;
densityDpi = 3;
int canvasW = getWidth();
int canvasH = getHeight();
Point centerOfCanvas = new Point(canvasW / 2, canvasH / 2);
int rectW = 100; // * (densityDpi - 1);
int rectH = 100; // * (densityDpi - 1);
int left = centerOfCanvas.x - (rectW / 2);
int top = centerOfCanvas.y - (rectH / 2);
int right = centerOfCanvas.x + (rectW / 2);
int bottom = centerOfCanvas.y + (rectH / 2);
rect = new RectF(left, top, right, bottom);
}
super.onDraw(canvas);
canvas.drawArc(rect, START_ANGLE_POINT, angle, true, paint);
}
public float getAngle() {
return angle;
}
public void setAngle(float angle) {
this.angle = angle;
}
}
Then used an AnimiationListener and updated the angle.
I have to create ViewPager with Fragments, and the main problem is that, each Fragment's background needs to have a cut in on edge in background. The cut in should expand while dragging. Do you have any ideas how to do it?
It should look like that:
U can refer the Flowing Drawer in the git
Link :https://github.com/mxn21/FlowingDrawer
In this sample they are using a "LeftDrawerLayout" a custom class to handle this effect and its property is set by FlowingView.
Use this to create a custom class.
This is prototype of my solution:
I created the custom FrameLayout and override onDraw method. This is how it's look like:
I'll add comments and description, how it's work, soon!
public class CurvedFrameLayout extends FrameLayout {
private Paint paint;
private Path path;
private int width;
private int height;
private float leftCurvePosition = 0.5f;
private float topCurvePosition = 0.5f;
private float rightCurvePosition = 0.5f;
private float bottomCurvePosition = 0.5f;
private int minimumCurve = 50;
private int maximumCurve = 100;
private int minimumLeftCurve = minimumCurve;
private int minimumTopCurve = minimumCurve;
private int minimumRightCurve = minimumCurve;
private int minimumBottomCurve = minimumCurve;
private int maximumLeftCurve = maximumCurve;
private int maximumTopCurve = maximumCurve;
private int maximumRightCurve = maximumCurve;
private int maximumBottomCurve = maximumCurve;
private float leftCurveOffset = 0f;
private float topCurveOffset = 0f;
private float rightCurveOffset = 0f;
private float bottomCurveOffset = 0f;
private int curveRadius = 150;
private float elevation = 4f;
private float cornerRadius = 50f;
private float margin = elevation;
private int color = Color.LTGRAY;
public CurvedFrameLayout(Context context) {
super(context);
init(context, null);
}
public CurvedFrameLayout(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
public CurvedFrameLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs);
}
private void init(Context context, AttributeSet attrs) {
if (attrs != null) {
TypedArray a = context.getTheme().obtainStyledAttributes(
attrs,
R.styleable.CurvedFrameLayout,
0, 0);
try {
leftCurvePosition = a.getFloat(R.styleable.CurvedFrameLayout_leftCurvePosition, 0.5f);
topCurvePosition = a.getFloat(R.styleable.CurvedFrameLayout_topCurvePosition, 0.5f);
rightCurvePosition = a.getFloat(R.styleable.CurvedFrameLayout_rightCurvePosition, 0.5f);
bottomCurvePosition = a.getFloat(R.styleable.CurvedFrameLayout_bottomCurvePosition, 0.5f);
minimumCurve = a.getDimensionPixelSize(R.styleable.CurvedFrameLayout_minimumCurve, 50);
maximumCurve = a.getDimensionPixelSize(R.styleable.CurvedFrameLayout_maximumCurve, 100);
minimumLeftCurve = a.getDimensionPixelSize(R.styleable.CurvedFrameLayout_minimumLeftCurve, 50);
minimumTopCurve = a.getDimensionPixelSize(R.styleable.CurvedFrameLayout_minimumTopCurve, 50);
minimumRightCurve = a.getDimensionPixelSize(R.styleable.CurvedFrameLayout_minimumRightCurve, 50);
minimumBottomCurve = a.getDimensionPixelSize(R.styleable.CurvedFrameLayout_minimumBottomCurve, 50);
maximumLeftCurve = a.getDimensionPixelSize(R.styleable.CurvedFrameLayout_maximumLeftCurve, 100);
maximumTopCurve = a.getDimensionPixelSize(R.styleable.CurvedFrameLayout_maximumTopCurve, 100);
maximumRightCurve = a.getDimensionPixelSize(R.styleable.CurvedFrameLayout_maximumRightCurve, 100);
maximumBottomCurve = a.getDimensionPixelSize(R.styleable.CurvedFrameLayout_maximumBottomCurve, 100);
leftCurveOffset = a.getFloat(R.styleable.CurvedFrameLayout_leftCurveOffset, 0f);
topCurveOffset = a.getFloat(R.styleable.CurvedFrameLayout_topCurveOffset, 0f);
rightCurveOffset = a.getFloat(R.styleable.CurvedFrameLayout_rightCurveOffset, 0f);
bottomCurveOffset = a.getFloat(R.styleable.CurvedFrameLayout_bottomCurveOffset, 0f);
curveRadius = a.getDimensionPixelSize(R.styleable.CurvedFrameLayout_curveRadius, 150);
cornerRadius = a.getDimensionPixelSize(R.styleable.CurvedFrameLayout_cornerRadius, 50);
elevation = a.getDimensionPixelSize(R.styleable.CurvedFrameLayout_elevation, 0);
margin = elevation;
color = a.getColor(R.styleable.CurvedFrameLayout_color, Color.LTGRAY);
} finally {
a.recycle();
}
}
setWillNotDraw(false);
paint = new Paint();
//setLayerType(LAYER_TYPE_SOFTWARE, paint);
paint.setColor(color);
paint.setShadowLayer(elevation, 0f, 0f, Color.LTGRAY);
path = new Path();
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
drawLayout(canvas);
}
private void drawLayout(Canvas canvas) {
float cornerRadiusMargin = cornerRadius + margin;
float widthMinusMargin = width - margin;
float widthMinusMarginMinusCornerR = widthMinusMargin - cornerRadius;
float heightMinusMargin = height - margin;
float heightMinusMarginMinusCornerR = heightMinusMargin - cornerRadius;
//Top-left corner
path.reset();
path.moveTo(margin, cornerRadiusMargin);
path.quadTo(margin, margin, cornerRadiusMargin, margin);
//Top line
drawTopEdge(cornerRadiusMargin, margin, widthMinusMarginMinusCornerR, margin);
//Top-right corner
path.quadTo(widthMinusMargin, margin, widthMinusMargin, cornerRadiusMargin);
//Right line
drawRightEdge(widthMinusMargin, cornerRadiusMargin, widthMinusMargin, heightMinusMarginMinusCornerR);
//Bottom-right corner
path.quadTo(widthMinusMargin, heightMinusMargin, widthMinusMarginMinusCornerR, heightMinusMargin);
//Bottom line
path.lineTo(cornerRadiusMargin, heightMinusMargin);
//Bottom-left corner
path.quadTo(margin, heightMinusMargin, margin, heightMinusMarginMinusCornerR);
//Left line
drawLeftEdge(margin, heightMinusMarginMinusCornerR, margin, cornerRadiusMargin);
canvas.drawPath(path, paint);
canvas.clipPath(path, Region.Op.REPLACE);
}
private void drawTopEdge(float x1, float y1, float x2, float y2) {
float curveCenterX = (x1 + x2) * topCurvePosition;
float curveDeltaY = positionForOffset(minimumTopCurve, maximumTopCurve, topCurveOffset);
int curveX = curveRadius / 2;
path.lineTo(curveCenterX - curveRadius, y1);
path.rCubicTo(curveX, 0, curveX, curveDeltaY, curveRadius, curveDeltaY);
path.rCubicTo(curveX, 0, curveX, -curveDeltaY, curveRadius, -curveDeltaY);
path.lineTo(x2, y2);
}
private void drawRightEdge(float x1, float y1, float x2, float y2) {
float curveCenterY = (y1 + y2) * rightCurvePosition;
float curveDeltaX = positionForOffset(minimumRightCurve, maximumRightCurve, rightCurveOffset);
path.lineTo(x1, curveCenterY - curveRadius);
int curveY = curveRadius / 2;
path.rCubicTo(0, curveY, -curveDeltaX, curveY, -curveDeltaX, curveRadius);
path.rCubicTo(0, curveY, curveDeltaX, curveY, curveDeltaX, curveRadius);
path.lineTo(x2, y2);
}
private void drawLeftEdge(float x1, float y1, float x2, float y2) {
float curveCenterY = (y1 + y2) * leftCurvePosition;
float curveDeltaX = positionForOffset(minimumLeftCurve, maximumLeftCurve, leftCurveOffset);
path.lineTo(x1, curveCenterY + curveRadius);
int curveY = -curveRadius / 2;
path.rCubicTo(0, curveY, curveDeltaX, curveY, curveDeltaX, -curveRadius);
path.rCubicTo(0, curveY, -curveDeltaX, curveY, -curveDeltaX, -curveRadius);
path.lineTo(x2, y2);
}
private float positionForOffset(float start, float end, float offset) {
return start + (end - start) * offset;
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
width = w;
height = h;
}
public void setLeftCurveOffset(float leftCurveOffset) {
this.leftCurveOffset = leftCurveOffset;
invalidate();
}
public void setRightCurveOffset(float rightCurveOffset) {
this.rightCurveOffset = rightCurveOffset;
invalidate();
}
}
At this time, code is not perfect, but as soon as I improve this code I'll update the answer.
I am trying to implement Oval path animation, I want to show path animation using image, I tried https://github.com/matthewrkula/AnimatedPathView but it's not work for oval. I also tried below code for oval path but it is shows circle, Anyone have an idea? Thanks in advance!!!
MyAnimation.java
public class MyAnimation extends Animation {
private View view;
private float cx, cy; // center x,y position of circular path
private float prevX, prevY; // previous x,y position of image during animation
private float r; // radius of circle
private float prevDx, prevDy;
/**
* #param view - View that will be animated
* #param r - radius of circular path
*/
public MyAnimation(View view, float r){
this.view = view;
this.r = r;
}
#Override
public boolean willChangeBounds() {
return true;
}
#Override
public void initialize(int width, int height, int parentWidth, int parentHeight) {
// calculate position of image center
int cxImage = width / 2;
int cyImage = height / 1;
cx = view.getLeft() + cxImage;
cy = view.getTop() + cyImage;
// set previous position to center
prevX = cx;
prevY = cy;
}
#Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
if(interpolatedTime == 0){
t.getMatrix().setTranslate(prevDx, prevDy);
return;
}
float angleDeg = (interpolatedTime * 360f + 90) % 360;
float angleRad = (float) Math.toRadians(angleDeg);
// r = radius, cx and cy = center point, a = angle (radians)
float x = (float) (cx + r * Math.cos(angleRad));
float y = (float) (cy + r * Math.sin(angleRad));
float dx = prevX - x;
float dy = prevY - y;
prevX = x;
prevY = y;
prevDx = dx;
prevDy = dy;
t.getMatrix().setTranslate(dx, dy);
}
}
PathAnimation.java
image = (ImageView) findViewById(R.id.image);
image.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Animation anim = new MyAnimation(image, 300);
anim.setDuration(1000);
image.startAnimation(anim);
}
});
I have found the solution after many tried using this custom class
AnimationView.java
public class AnimationView extends View {
Paint paint;
long animationDuration = 10000;
int framesPerSecond = 60;
Bitmap bm;
int bm_offsetX, bm_offsetY;
Path animPath;
PathMeasure pathMeasure;
float pathLength;
float step; //distance each step
float distance; //distance moved
float[] pos;
float[] tan;
Matrix matrix;
public AnimationView(Context context) {
super(context);
initMyView();
}
public AnimationView(Context context, AttributeSet attrs) {
super(context, attrs);
initMyView();
}
public AnimationView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initMyView();
}
#TargetApi(Build.VERSION_CODES.LOLLIPOP)
public void initMyView(){
paint = new Paint();
paint.setColor(Color.RED);
paint.setStrokeWidth(5);
paint.setStyle(Paint.Style.STROKE);
bm = BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher);
bm_offsetX = bm.getWidth()/2;
bm_offsetY = bm.getHeight()/2;
animPath = new Path();
animPath.moveTo(100, 100);
animPath.addArc(new RectF(1, 100, 300, 600), 1, 800);
animPath.close();
pathMeasure = new PathMeasure(animPath, false);
pathLength = pathMeasure.getLength();
Toast.makeText(getContext(), "pathLength: " + pathLength, Toast.LENGTH_LONG).show();
step = 1;
distance = 0;
pos = new float[2];
tan = new float[2];
matrix = new Matrix();
}
#Override
protected void onDraw(Canvas canvas) {
canvas.drawPath(animPath, paint);
if(distance < pathLength){
pathMeasure.getPosTan(distance, pos, tan);
matrix.reset();
float degrees = (float)(Math.atan2(tan[1], tan[0])*180.0/Math.PI);
matrix.postRotate(degrees, bm_offsetX, bm_offsetY);
matrix.postTranslate(pos[0]-bm_offsetX, pos[1]-bm_offsetY);
canvas.drawBitmap(bm, matrix, null);
distance += step;
}else{
distance = 0;
}
invalidate();
}
}
and put into xml
<com.example.android.mydemo.animation.pathanimation.AnimationView
android:layout_width="match_parent"
android:layout_height="450dp" />
I tried to replicate a donut chart code that I found in the net. The code is as follows
public class DonutChart extends View{
private float radius;
SharedPreferences prefs;
Paint paint;
Paint shadowPaint;
int a,b,c;
Path myPath;
Path shadowPath;
RectF outterCircle;
RectF innerCircle;
RectF shadowRectF;
public DonutChart(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.getTheme().obtainStyledAttributes(
attrs,
R.styleable.DonutChart,
0, 0
);
try {
radius = a.getDimension(R.styleable.DonutChart_radius, 20.0f);
} finally {
a.recycle();
}
paint = new Paint();
paint.setDither(true);
paint.setStyle(Paint.Style.FILL);
paint.setStrokeJoin(Paint.Join.ROUND);
paint.setStrokeCap(Paint.Cap.ROUND);
paint.setAntiAlias(true);
paint.setStrokeWidth(radius / 14.0f);
shadowPaint = new Paint();
shadowPaint.setColor(0xf0000000);
shadowPaint.setStyle(Paint.Style.STROKE);
shadowPaint.setAntiAlias(true);
shadowPaint.setStrokeWidth(6.0f);
shadowPaint.setMaskFilter(new BlurMaskFilter(4, BlurMaskFilter.Blur.SOLID));
myPath = new Path();
shadowPath = new Path();
outterCircle = new RectF();
innerCircle = new RectF();
shadowRectF = new RectF();
float adjust = (.019f*radius);
shadowRectF.set(adjust, adjust, radius*2-adjust, radius*2-adjust);
adjust = .038f * radius;
outterCircle.set(adjust, adjust, radius*2-adjust, radius*2-adjust);
adjust = .276f * radius;
innerCircle.set(adjust, adjust, radius * 2 - adjust, radius * 2 - adjust);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// draw shadow
paint.setShader(null);
float adjust = (.0095f*radius);
paint.setShadowLayer(8, adjust, -adjust, 0xaa000000);
drawDonut(canvas, paint, 0, 359.9f);
//Orange
setGradient(0xffEF6632,0xffEF6632);
drawDonut(canvas,paint, 0,b);
//Blue
setGradient(0xff00CCDA,0xff00CCDA);
drawDonut(canvas, paint, 60,a);
// blue
// setGradient(0xff4AB6C1,0xff2182AD);
// drawDonut(canvas, paint, 120, 60);
// Grey
setGradient(0xff557687,0xff557687);
drawDonut(canvas, paint, 180,c);
}
public void drawDonut(Canvas canvas, Paint paint, float start,float sweep){
myPath.reset();
myPath.arcTo(outterCircle, start, sweep, false);
myPath.arcTo(innerCircle, start+sweep, -sweep, false);
myPath.close();
canvas.drawPath(myPath, paint);
}
public void setGradient(int sColor, int eColor){
paint.setShader(new RadialGradient(radius, radius, radius - 5,
new int[]{sColor, eColor},
new float[]{.6f, .95f}, TileMode.CLAMP));
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int desiredWidth = (int) radius*2;
int desiredHeight = (int) radius*2;
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int width;
int height;
//70dp exact
if (widthMode == MeasureSpec.EXACTLY) {
width = widthSize;
}else if (widthMode == MeasureSpec.AT_MOST) {
//wrap content
width = Math.min(desiredWidth, widthSize);
} else {
width = desiredWidth;
}
//Measure Height
if (heightMode == MeasureSpec.EXACTLY) {
height = heightSize;
} else if (heightMode == MeasureSpec.AT_MOST) {
height = Math.min(desiredHeight, heightSize);
} else {
height = desiredHeight;
}
//MUST CALL THIS
setMeasuredDimension(width, height);
}
public void getData(int x,int y){
invalidate();
a=((x*360)/10);
b=(y*360)/10;
c=((10-(x+y))*360)/10;
String s1,s2,s3;
s1=String.valueOf(a);
s2=String.valueOf(b);
s3=String.valueOf(c);
Toast.makeText(getContext(),"Inside Chart "+s1+" "+s2+" "+s3 +" "+String.valueOf(x),Toast.LENGTH_SHORT).show();
}
}
The problem is when I render the graph on my device it gives me a weird shadow like this:
or like this:
What is causing this and how to rectify it?
This is actually caused because only the value of the line length is changed whereas the starting point is the same so sometimes they tend to overlap.
This can be solved by changing the code as follows
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// draw shadow
paint.setShader(null);
float adjust = (.0095f*radius);
paint.setShadowLayer(8, adjust, -adjust, 0xaa000000);
drawDonut(canvas, paint, 0, 359.9f);
//Orange
setGradient(0xffEF6632,0xffEF6632);
drawDonut(canvas,paint, 0,b);
//Blue
setGradient(0xff00CCDA,0xff00CCDA);
drawDonut(canvas, paint, b,a);
// Grey
setGradient(0xff557687,0xff557687);
drawDonut(canvas, paint, a+b,c);
}
How to customize a ProgressBar to look like a Thermometer ? with the possibility to change color.
My suggestion was to rotate the progressBar 90° to become vertical then have it overlay an image of an empty Thermometer but it's bad and messy solution.
I Think the best will be to either to extends View or ProgressBar class and customize the draw method but I have no idea how to draw Thermometer, any Help would be appreciated.
I created something like this for a project
package com.janslab.thermometer.widgets;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.view.View;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.widget.Scroller;
import com.janslab.thermometer.R;
public class DummyThermometer extends View {
private Paint mInnerCirclePaint;
private Paint mOuterCirclePaint;
private Paint mFirstOuterCirclePaint;
//thermometer arc paint
private Paint mFirstOuterArcPaint;
//thermometer lines paints
private Paint mInnerLinePaint;
private Paint mOuterLinePaint;
private Paint mFirstOuterLinePaint;
//thermometer radii
private int mOuterRadius;
private int mInnerRadius;
private int mFirstOuterRadius;
//thermometer colors
private int mThermometerColor = Color.rgb(200, 115, 205);
//circles and lines variables
private float mLastCellWidth;
private int mStageHeight;
private float mCellWidth;
private float mStartCenterY; //center of first cell
private float mEndCenterY; //center of last cell
private float mStageCenterX;
private float mXOffset;
private float mYOffset;
// I 1st Cell I 2nd Cell I 3rd Cell I
private static final int NUMBER_OF_CELLS = 3; //three cells in all ie.stageHeight divided into 3 equal cells
//animation variables
private float mIncrementalTempValue;
private boolean mIsAnimating;
private Animator mAnimator;
public DummyThermometer(Context context) {
this(context, null);
}
public DummyThermometer(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public DummyThermometer(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
if (attrs != null) {
final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.Thermometer, defStyle, 0);
mThermometerColor = a.getColor(R.styleable.Thermometer_therm_color, mThermometerColor);
a.recycle();
}
init();
}
private void init() {
mInnerCirclePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mInnerCirclePaint.setColor(mThermometerColor);
mInnerCirclePaint.setStyle(Paint.Style.FILL);
mInnerCirclePaint.setStrokeWidth(17f);
mOuterCirclePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mOuterCirclePaint.setColor(Color.WHITE);
mOuterCirclePaint.setStyle(Paint.Style.FILL);
mOuterCirclePaint.setStrokeWidth(32f);
mFirstOuterCirclePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mFirstOuterCirclePaint.setColor(mThermometerColor);
mFirstOuterCirclePaint.setStyle(Paint.Style.FILL);
mFirstOuterCirclePaint.setStrokeWidth(60f);
mFirstOuterArcPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mFirstOuterArcPaint.setColor(mThermometerColor);
mFirstOuterArcPaint.setStyle(Paint.Style.STROKE);
mFirstOuterArcPaint.setStrokeWidth(30f);
mInnerLinePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mInnerLinePaint.setColor(mThermometerColor);
mInnerLinePaint.setStyle(Paint.Style.FILL);
mInnerLinePaint.setStrokeWidth(17f);
mOuterLinePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mOuterLinePaint.setColor(Color.WHITE);
mOuterLinePaint.setStyle(Paint.Style.FILL);
mFirstOuterLinePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mFirstOuterLinePaint.setColor(mThermometerColor);
mFirstOuterLinePaint.setStyle(Paint.Style.FILL);
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mStageCenterX = getWidth() / 2;
mStageHeight = getHeight();
mCellWidth = mStageHeight / NUMBER_OF_CELLS;
//center of first cell
mStartCenterY = mCellWidth / 2;
//move to 3rd cell
mLastCellWidth = (NUMBER_OF_CELLS * mCellWidth);
//center of last(3rd) cell
mEndCenterY = mLastCellWidth - (mCellWidth / 2);
// mOuterRadius is 1/4 of mCellWidth
mOuterRadius = (int) (0.25 * mCellWidth);
mInnerRadius = (int) (0.656 * mOuterRadius);
mFirstOuterRadius = (int) (1.344 * mOuterRadius);
mFirstOuterLinePaint.setStrokeWidth(mFirstOuterRadius);
mOuterLinePaint.setStrokeWidth(mFirstOuterRadius / 2);
mFirstOuterArcPaint.setStrokeWidth(mFirstOuterRadius / 4);
mXOffset = mFirstOuterRadius / 4;
mXOffset = mXOffset / 2;
//get the d/f btn firstOuterLine and innerAnimatedline
mYOffset = (mStartCenterY + (float) 0.875 * mOuterRadius) - (mStartCenterY + mInnerRadius);
mYOffset = mYOffset / 2;
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
drawFirstOuterCircle(canvas);
drawOuterCircle(canvas);
drawInnerCircle(canvas);
drawFirstOuterLine(canvas);
drawOuterLine(canvas);
animateInnerLine(canvas);
drawFirstOuterCornerArc(canvas);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//take care of paddingTop and paddingBottom
int paddingY = getPaddingBottom() + getPaddingTop();
//get height and width
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
height += paddingY;
setMeasuredDimension(width, height);
}
private void drawInnerCircle(Canvas canvas) {
drawCircle(canvas, mInnerRadius, mInnerCirclePaint);
}
private void drawOuterCircle(Canvas canvas) {
drawCircle(canvas, mOuterRadius, mOuterCirclePaint);
}
private void drawFirstOuterCircle(Canvas canvas) {
drawCircle(canvas, mFirstOuterRadius, mFirstOuterCirclePaint);
}
private void drawCircle(Canvas canvas, float radius, Paint paint) {
canvas.drawCircle(mStageCenterX, mEndCenterY, radius, paint);
}
private void drawOuterLine(Canvas canvas) {
float startY = mEndCenterY - (float) (0.875 * mOuterRadius);
float stopY = mStartCenterY + (float) (0.875 * mOuterRadius);
drawLine(canvas, startY, stopY, mOuterLinePaint);
}
private void drawFirstOuterLine(Canvas canvas) {
float startY = mEndCenterY - (float) (0.875 * mFirstOuterRadius);
float stopY = mStartCenterY + (float) (0.875 * mOuterRadius);
drawLine(canvas, startY, stopY, mFirstOuterLinePaint);
}
private void drawLine(Canvas canvas, float startY, float stopY, Paint paint) {
canvas.drawLine(mStageCenterX, startY, mStageCenterX, stopY, paint);
}
//simulate temperature measurement for now
private void animateInnerLine(Canvas canvas) {
if (mAnimator == null)
measureTemperature();
if (!mIsAnimating) {
mIncrementalTempValue = mEndCenterY + (float) (0.875 * mInnerRadius);
mIsAnimating = true;
} else {
mIncrementalTempValue = mEndCenterY + (float) (0.875 * mInnerRadius) - mIncrementalTempValue;
}
if (mIncrementalTempValue > mStartCenterY + mInnerRadius) {
float startY = mEndCenterY + (float) (0.875 * mInnerRadius);
drawLine(canvas, startY, mIncrementalTempValue, mInnerCirclePaint);
} else {
float startY = mEndCenterY + (float) (0.875 * mInnerRadius);
float stopY = mStartCenterY + mInnerRadius;
drawLine(canvas, startY, stopY, mInnerCirclePaint);
mIsAnimating = false;
stopMeasurement();
}
}
private void drawFirstOuterCornerArc(Canvas canvas) {
float y = mStartCenterY - (float) (0.875 * mFirstOuterRadius);
RectF rectF = new RectF(mStageCenterX - mFirstOuterRadius / 2 + mXOffset, y + mFirstOuterRadius, mStageCenterX + mFirstOuterRadius / 2 - mXOffset, y + (2 * mFirstOuterRadius) + mYOffset);
canvas.drawArc(rectF, -180, 180, false, mFirstOuterArcPaint);
}
public void setThermometerColor(int thermometerColor) {
this.mThermometerColor = thermometerColor;
mInnerCirclePaint.setColor(mThermometerColor);
mFirstOuterCirclePaint.setColor(mThermometerColor);
mFirstOuterArcPaint.setColor(mThermometerColor);
mInnerLinePaint.setColor(mThermometerColor);
mFirstOuterLinePaint.setColor(mThermometerColor);
invalidate();
}
//simulate temperature measurement for now
private void measureTemperature() {
mAnimator = new Animator();
mAnimator.start();
}
private class Animator implements Runnable {
private Scroller mScroller;
private final static int ANIM_START_DELAY = 1000;
private final static int ANIM_DURATION = 4000;
private boolean mRestartAnimation = false;
public Animator() {
mScroller = new Scroller(getContext(), new AccelerateDecelerateInterpolator());
}
public void run() {
if (mAnimator != this)
return;
if (mRestartAnimation) {
int startY = (int) (mStartCenterY - (float) (0.875 * mInnerRadius));
int dy = (int) (mEndCenterY + mInnerRadius);
mScroller.startScroll(0, startY, 0, dy, ANIM_DURATION);
mRestartAnimation = false;
}
boolean isScrolling = mScroller.computeScrollOffset();
mIncrementalTempValue = mScroller.getCurrY();
if (isScrolling) {
invalidate();
post(this);
} else {
stop();
}
}
public void start() {
mRestartAnimation = true;
postDelayed(this, ANIM_START_DELAY);
}
public void stop() {
removeCallbacks(this);
mAnimator = null;
}
}
private void stopMeasurement() {
if (mAnimator != null)
mAnimator.stop();
}
#Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
measureTemperature();
}
#Override
protected void onDetachedFromWindow() {
stopMeasurement();
super.onDetachedFromWindow();
}
#Override
public void setVisibility(int visibility) {
super.setVisibility(visibility);
switch (visibility) {
case View.VISIBLE:
measureTemperature();
break;
default:
stopMeasurement();
break;
}
}
}
attrs.xml file
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="Thermometer">
<attr name="therm_color" format="color" />
</declare-styleable>
</resources>
First I would provide 2 setters, one for color and one for the temperature value, normalized from 0 ... 1, where 0 means no visible bar, and 1 means a fully visible bar.
public void setColor(int color) {
mColor = color;
invalidate(); // important, this triggers onDraw
}
public void setValue(float value) {
mValue = -(value - 1);
invalidate(); // important, this triggers onDraw
}
Notice for value, I reverse the value, since we draw the bar from bottom up, instead from top down. It makes sense in the canvas.drawRect method.
If your CustomView may have custom sizes, set your size of the progressBar (I refer to the inner bar as progressBar) in onSizeChanged, as this gets called when the View has changed it's size.
If it is a fixed size, you can just provide those values statically in an init function or the constructor.
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mProgressRect = new Rect(
/*your bar left offset relative to base bitmap*/,
/*your bar top offset relative to base bitmap*/,
/*your bar total width*/,
/*your max bar height*/
);
}
Then in ondraw, take these values into account and draw accordingly.
First draw the Bitmap, depending on your selected color (I would provide the thermometer base as a Bitmap, as long as it does not have to be completely dynamically drawn (special requirements)
Then draw the progress bar, with an height based on mValue * totalHeight of the bar, using the color provided in the setter.
For example:
#Override
protected void onDraw(Canvas canvas) {
// draw your thermometer base, bitmap based on color value
canvas.drawBitmap( /*your base thermometer bitmap here*/ );
// draw the "progress"
canvas.drawRect(mProgressRect.left, mProgressRect.top + (mValue * mProgressRect.bottom - mProgressRect.top), mProgressRect.right, mProgressRect.bottom, mPaint);
}
Hope that helps.
P.S.:
If you want to have the thermometer base image also dynamically drawn, it's a slightly different story, it would involve creating a path first and draw it with a Paint object, instead of drawing the bitmap.
EDIT:
Even better, if you want a simple solution for the "roundness" of the bar, draw a line instead a rect.
Define a line paint object like this:
mPaint = new Paint();
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeCap(Paint.Cap.ROUND);
mPaint.setStrokeWidth(20); // thickness of your bar
then in onDraw, instead drawRect:
// draw the "progress"
canvas.drawLine(mProgressRect.left, mProgressRect.top + (mValue * mProgressRect.bottom - mProgressRect.top), mProgressRect.left, mProgressRect.bottom, mPaint);
Be sure to adjust your mProgressRectaccordingly.