I need a custom Image View that first draws some shapes behind the image before it draws the image itself using the same "Scaling" applied to the actual bitmap itself. Everything is working great except I have an AlphaAnimation to fade the custom ImageView in. It fades the BitmapDrawable part but not my shape. The Shape is drawn in full opacity at all times. I have tried setting the alpha of the paint i'm using to the getAlpha of the view but with no luck. Here is my onDraw function inside my custom ImageView class:
#Override
protected void onDraw(Canvas canvas) {
float[] f = new float[9];
getImageMatrix().getValues(f);
final float scaleX = f[Matrix.MSCALE_X];
final float scaleY = f[Matrix.MSCALE_Y];
final Drawable d = getDrawable();
final int origW = d.getIntrinsicWidth();
final int origH = d.getIntrinsicHeight();
final int actW = Math.round(origW * scaleX);
final int actH = Math.round(origH * scaleY);
canvas.drawRect(Math.max((getWidth() - actW) / 2, 0) + 1, Math.max((getHeight() - actH) / 2, 0) + 1, Math.max((getWidth() - actW) / 2, 0) + actW - 1, Math.max((getHeight() - actH) / 2, 0) + actH - 1, paint);
super.onDraw(canvas);
}
A quick fix for your problem will be to add the custom view to some sort of view container and then just animate the container instead.
This is the source code, version: 5.0.0_r1, of onDraw method of imageView
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (mDrawable == null) {
return; // couldn't resolve the URI
}
if (mDrawableWidth == 0 || mDrawableHeight == 0) {
return; // nothing to draw (empty bounds)
}
if (mDrawMatrix == null && mPaddingTop == 0 && mPaddingLeft == 0) {
mDrawable.draw(canvas);
} else {
int saveCount = canvas.getSaveCount();
canvas.save();
if (mCropToPadding) {
final int scrollX = mScrollX;
final int scrollY = mScrollY;
canvas.clipRect(scrollX + mPaddingLeft, scrollY + mPaddingTop,
scrollX + mRight - mLeft - mPaddingRight,
scrollY + mBottom - mTop - mPaddingBottom);
}
canvas.translate(mPaddingLeft, mPaddingTop);
if (mDrawMatrix != null) {
canvas.concat(mDrawMatrix);
}
mDrawable.draw(canvas);
canvas.restoreToCount(saveCount);
}
}
As you can see, there is no use of Paint in here, so I bet this is your problem. Try creating new Paint and set its alpha to Drawable.getAlpha()
Since Drawable.getAlpha() require API 19, you can wrap it in your own Drawable like this:
class MyDrawable extends Drawable {
final Drawable originalDrawable;
private int mAlpha = 1;
#Override
public int getAlpha() {
return mAlpha;
}
public MyDrawable(Drawable drawable){
originalDrawable = drawable;
}
#Override
public void draw(Canvas canvas) {
originalDrawable.draw(canvas);
}
#Override
public void setAlpha(int alpha) {
mAlpha = alpha;
originalDrawable.setAlpha(alpha);
}
#Override
public void setColorFilter(ColorFilter cf) {
originalDrawable.setColorFilter(cf);
}
#Override
public int getOpacity() {
return originalDrawable.getOpacity();
}
}
Related
Problem Solved:
TextPaint's setTextSize() should be called before TextPaint's descent() & ascend() as those two methods seem to depend on the text size for calculations.
#Override
public void draw(Canvas canvas) {
Rect r = getBounds();
float boundSize = Math.min(r.width(), r.height());
float fontSize = Math.min(mFontSize, boundSize);
mTextPaint.setTextSize(fontSize); // Should be before descent() & ascent()
int originX = r.width() / 2;
int originY = (int) ((r.height() / 2)
- ((mTextPaint.descent() + mTextPaint.ascent()) / 2)) ;
canvas.drawText(mText, originX, originY, mTextPaint);
}
Context: I created a TextDrawable to turn strings into Drawables so I can set them as icons. I want them to be centered both horizontally and vertically when I assign them to an ImageView.
Problem: When I bind them to a RecyclerView, sometimes they center vertically but sometimes they don't (as you can see above). This can alternate as the items are rebinded each time I scroll through the RecyclerView.
I suspect the cause might be inside TextDrawable's draw() but I'm not sure how to fix it.
TextDrawable:
public class TextDrawable extends Drawable {
private String mText;
private Paint mTextPaint = new Paint();
private float mFontSize = 48;
public TextDrawable(String text) {
mText = text;
mTextPaint.setColor(Color.WHITE);
mTextPaint.setAntiAlias(true);
mTextPaint.setFakeBoldText(false);
mTextPaint.setStyle(Paint.Style.FILL);
mTextPaint.setTypeface(Typeface.create("sans-serif-light", Typeface.NORMAL));
mTextPaint.setTextAlign(Paint.Align.CENTER);
mTextPaint.setStrokeWidth(0);
}
public void setFontSize(float fontSize) {
mFontSize = fontSize;
}
#Override
public void draw(Canvas canvas) {
Rect r = getBounds();
int originX = r.width() / 2;
int originY = (int) ((r.height() / 2)
- ((mTextPaint.descent() + mTextPaint.ascent()) / 2)) ;
float boundSize = Math.min(r.width(), r.height());
float fontSize = Math.min(mFontSize, boundSize);
mTextPaint.setTextSize(fontSize);
canvas.drawText(mText, originX, originY, mTextPaint);
}
#Override
public void setAlpha(int alpha) {
mTextPaint.setAlpha(alpha);
}
#Override
public void setColorFilter(ColorFilter colorFilter) {
mTextPaint.setColorFilter(colorFilter);
}
#Override
public int getOpacity() {
return PixelFormat.TRANSLUCENT;
}
}
Task's getIcon() Method:
public Drawable getIcon() {
String letter = mTitle.substring(0, 1);
return new TextDrawable(letter);
}
RecyclerView ViewHolder's bind() Method:
void bind(Task task) {
mCircleView.setImageDrawable(task.getIcon());
}
I have custom animated play/pause button which is getting highlighted when I press it. How can I remove the highlighted gray color around the button.
Setting background to transparent isn't working.
public class PlayPauseMiniView extends FrameLayout {
private static final long PLAY_PAUSE_ANIMATION_DURATION = 200;
private final PlayPauseDrawable mDrawable;
private final Paint mPaint = new Paint();
private AnimatorSet mAnimatorSet;
private int mWidth;
private int mHeight;
public PlayPauseMiniView(Context context, AttributeSet attrs) {
super(context, attrs);
setWillNotDraw(false);
mPaint.setAntiAlias(true);
mPaint.setStyle(Paint.Style.FILL);
mDrawable = new PlayPauseDrawable(context);
mDrawable.setCallback(this);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
final int size = Math.min(getMeasuredWidth(), getMeasuredHeight());
setMeasuredDimension(120, 120);
}
#Override
protected void onSizeChanged(final int w, final int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mDrawable.setBounds(0, 0, w, h);
mWidth = w;
mHeight = h;
}
#Override
protected boolean verifyDrawable(Drawable who) {
return who == mDrawable || super.verifyDrawable(who);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
mDrawable.draw(canvas);
}
public void toggle() {
if (mAnimatorSet != null) {
mAnimatorSet.cancel();
}
mAnimatorSet = new AnimatorSet();
final Animator pausePlayAnim = mDrawable.getPausePlayAnimator();
mAnimatorSet.setInterpolator(new DecelerateInterpolator());
mAnimatorSet.setDuration(PLAY_PAUSE_ANIMATION_DURATION);
mAnimatorSet.playTogether(pausePlayAnim);
mAnimatorSet.start();
}
}
public class PlayPauseDrawable extends Drawable {
private static final Property<PlayPauseDrawable, Float> PROGRESS =
new Property<PlayPauseDrawable, Float>(Float.class, "progress") {
#Override
public Float get(PlayPauseDrawable d) {
return d.getProgress();
}
#Override
public void set(PlayPauseDrawable d, Float value) {
d.setProgress(value);
}
};
private final Path mLeftPauseBar = new Path();
private final Path mRightPauseBar = new Path();
private final Paint mPaint = new Paint();
private final RectF mBounds = new RectF(0,0,10,10);
private final float mPauseBarWidth;
private final float mPauseBarHeight;
private final float mPauseBarDistance;
private float mWidth;
private float mHeight;
private float mProgress;
private boolean mIsPlay;
public PlayPauseDrawable(Context context) {
final Resources res = context.getResources();
mPaint.setAntiAlias(true);
mPaint.setStyle(Paint.Style.FILL);
mPaint.setColor(Color.DKGRAY);
mPauseBarWidth = res.getDimensionPixelSize(R.dimen.pause_bar_width);
mPauseBarHeight = res.getDimensionPixelSize(R.dimen.pause_bar_height);
mPauseBarDistance = res.getDimensionPixelSize(R.dimen.pause_bar_distance);
}
#Override
protected void onBoundsChange(Rect bounds) {
super.onBoundsChange(bounds);
mBounds.set(bounds);
mWidth = mBounds.width();
mHeight = mBounds.height();
}
#Override
public void draw(Canvas canvas) {
mLeftPauseBar.rewind();
mRightPauseBar.rewind();
// The current distance between the two pause bars.
final float barDist = lerp(mPauseBarDistance, 0, mProgress);
// The current width of each pause bar.
final float barWidth = lerp(mPauseBarWidth, mPauseBarHeight / 2f, mProgress);
// The current position of the left pause bar's top left coordinate.
final float firstBarTopLeft = lerp(0, barWidth, mProgress);
// The current position of the right pause bar's top right coordinate.
final float secondBarTopRight = lerp(2 * barWidth + barDist, barWidth + barDist, mProgress);
// Draw the left pause bar. The left pause bar transforms into the
// top half of the play button triangle by animating the position of the
// rectangle's top left coordinate and expanding its bottom width.
mLeftPauseBar.moveTo(0, 0);
mLeftPauseBar.lineTo(firstBarTopLeft, -mPauseBarHeight);
mLeftPauseBar.lineTo(barWidth, -mPauseBarHeight);
mLeftPauseBar.lineTo(barWidth, 0);
mLeftPauseBar.close();
// Draw the right pause bar. The right pause bar transforms into the
// bottom half of the play button triangle by animating the position of the
// rectangle's top right coordinate and expanding its bottom width.
mRightPauseBar.moveTo(barWidth + barDist, 0);
mRightPauseBar.lineTo(barWidth + barDist, -mPauseBarHeight);
mRightPauseBar.lineTo(secondBarTopRight, -mPauseBarHeight);
mRightPauseBar.lineTo(2 * barWidth + barDist, 0);
mRightPauseBar.close();
canvas.save();
// Translate the play button a tiny bit to the right so it looks more centered.
canvas.translate(lerp(0, mPauseBarHeight / 8f, mProgress), 0);
// (1) Pause --> Play: rotate 0 to 90 degrees clockwise.
// (2) Play --> Pause: rotate 90 to 180 degrees clockwise.
final float rotationProgress = mIsPlay ? 1 - mProgress : mProgress;
final float startingRotation = mIsPlay ? 90 : 0;
canvas.rotate(lerp(startingRotation, startingRotation + 90, rotationProgress), mWidth / 2f, mHeight / 2f);
// Position the pause/play button in the center of the drawable's bounds.
canvas.translate(mWidth / 2f - ((2 * barWidth + barDist) / 2f), mHeight / 2f + (mPauseBarHeight / 2f));
// Draw the two bars that form the animated pause/play button.
canvas.drawPath(mLeftPauseBar, mPaint);
canvas.drawPath(mRightPauseBar, mPaint);
canvas.restore();
}
public Animator getPausePlayAnimator() {
final Animator anim = ObjectAnimator.ofFloat(this, PROGRESS, mIsPlay ? 1 : 0, mIsPlay ? 0 : 1);
anim.addListener(new AnimatorListenerAdapter() {
#Override
public void onAnimationEnd(Animator animation) {
mIsPlay = !mIsPlay;
}
});
return anim;
}
public boolean isPlay() {
return mIsPlay;
}
private void setProgress(float progress) {
mProgress = progress;
invalidateSelf();
}
private float getProgress() {
return mProgress;
}
#Override
public void setAlpha(int alpha) {
mPaint.setAlpha(alpha);
invalidateSelf();
}
#Override
public void setColorFilter(ColorFilter cf) {
mPaint.setColorFilter(cf);
invalidateSelf();
}
#Override
public int getOpacity() {
return PixelFormat.TRANSLUCENT;
}
/**
* Linear interpolate between a and b with parameter t.
*/
private static float lerp(float a, float b, float t) {
return a + (b - a) * t;
}
}
You can use ImageView instead of ImageButton
Have You tried android:background="#null" ?
I have tried everything possible available online, but this method is not called consistently. The onDraw() method is sometimes called and sometimes just neglected:
Please, please help me, any help is appreciated;
BadgeDrawable class: (want to draw a badge on to my notification icon in action bar)
public class BadgeDrawable extends Drawable {
private float mTextSize;
private Paint mBadgePaint;
private Paint mBadgePaint1;
private Paint mTextPaint;
private Rect mTxtRect = new Rect();
private String mCount = "";
private boolean mWillDraw = false;
public BadgeDrawable(Context context) {
mTextSize = /*context.getResources().getDimension(R.dimen.badge_text_size)*/ 20;
mBadgePaint = new Paint();
mBadgePaint.setColor(ResourceReader.getInstance(context).getColorFromResource(R.color.colorPrimary));
mBadgePaint.setAntiAlias(true);
mBadgePaint.setStyle(Paint.Style.FILL);
mBadgePaint1 = new Paint();
mBadgePaint1.setColor(Color.parseColor("#FFFFFF"));//white
mBadgePaint1.setAntiAlias(true);
mBadgePaint1.setStyle(Paint.Style.FILL);
mTextPaint = new Paint();
mTextPaint.setColor(Color.WHITE);
mTextPaint.setTypeface(Typeface.DEFAULT);
mTextPaint.setTextSize(mTextSize);
//mTextPaint.setFlags(Paint.FAKE_BOLD_TEXT_FLAG);
mTextPaint.setAntiAlias(true);
mTextPaint.setTextAlign(Paint.Align.CENTER);
this.setCallback(callback);
}
#Override
public void draw(Canvas canvas) {
Log.d("dj", "onDraw BadgeDrawable");
if (!mWillDraw) {
return;
}
Rect bounds = getBounds();
float width = bounds.right - bounds.left;
float height = bounds.bottom - bounds.top;
float radius = ((Math.max(width, height) / 2)) / 2;
float centerX = (width - radius - 1) +10;
float centerY = radius -5;
if(mCount.length() <= 2){
// Draw badge circle.
/*canvas.drawCircle(centerX, centerY, radius+9, mBadgePaint1);
canvas.drawCircle(centerX, centerY, radius+7, mBadgePaint);*/
canvas.drawCircle(centerX, centerY, radius+9, mBadgePaint1);
canvas.drawCircle(centerX, centerY, radius+7, mBadgePaint);
}
else{
/*canvas.drawCircle(centerX, centerY, radius+10, mBadgePaint1);
canvas.drawCircle(centerX, centerY, radius+8, mBadgePaint);*/
canvas.drawCircle(centerX, centerY, radius+10, mBadgePaint1);
canvas.drawCircle(centerX, centerY, radius+8, mBadgePaint);
}
// Draw badge count text inside the circle.
mTextPaint.getTextBounds(mCount, 0, mCount.length(), mTxtRect);
float textHeight = mTxtRect.bottom - mTxtRect.top;
float textY = centerY + (textHeight / 2f);
/*if(mCount.length() > 2)
canvas.drawText("99+", centerX, textY, mTextPaint);
else*/
canvas.drawText(mCount, centerX, textY, mTextPaint);
}
/*
Sets the count (i.e notifications) to display.
*/
public void setCount(String count) {
Log.d(Constants.TAG, "setCount count val- BadgeDrawable: "+count);
mCount = count;
// Only draw a badge if there are notifications.
mWillDraw = !count.equalsIgnoreCase("0");
invalidateSelf();
}
private Drawable.Callback callback = new Callback() {
#Override
public void invalidateDrawable(Drawable who) {
Log.d(Constants.TAG, "invalidateDrawable - BadgeDrawable: ");
}
#Override
public void scheduleDrawable(Drawable who, Runnable what, long when) {
}
#Override
public void unscheduleDrawable(Drawable who, Runnable what) {
}
};
#Override
public void setAlpha(int alpha) {
// do nothing
}
#Override
public void setColorFilter(ColorFilter cf) {
// do nothing
}
#Override
public int getOpacity() {
return PixelFormat.UNKNOWN;
}
}
So when I get the count of unread notification from server I set like this:
public static void setBadgeCount(Context context, LayerDrawable icon, String count) {
BadgeDrawable badge; // Reuse drawable if possible
Drawable reuse = icon.findDrawableByLayerId(R.id.ic_badge); //getting the layer 2
if (reuse != null && reuse instanceof BadgeDrawable) {
badge = (BadgeDrawable) reuse;
} else {
badge = new BadgeDrawable(context);
}
badge.setCount(count);
icon.mutate();
icon.setDrawableByLayerId(R.id.ic_badge, badge);
}
I know it's too late. Answer is
public static void setBadgeCount(Context context, LayerDrawable icon, String count) {
BadgeDrawable badge; // Reuse drawable if possible
Drawable reuse = icon.findDrawableByLayerId(R.id.ic_badge); //getting the layer 2
if (reuse != null && reuse instanceof BadgeDrawable) {
badge = (BadgeDrawable) reuse;
} else {
badge = new BadgeDrawable(context);
icon.mutate();
icon.setDrawableByLayerId(R.id.ic_badge, badge);
}
badge.setCount(count);
}
Ensure that the bounds of the drawable produce a width and height which is greater than 0. When a drawable's bound's width or height is 0, the draw method will not be called.
You can set the bounds of a drawable by calling setBounds() on the drawable itself.
EDIT
Another way to get a drawable to redraw itself is by using the level mechanism. This is good for drawables that animate, but can be used regardless.
You can trigger a refresh of the drawable by calling:
setLevel(getLevel() + 1)
In addition, if you want your drawable to redraw on a level change, you need to override the following method and return true:
#Override
protected boolean onLevelChange(int level) {
return true;
}
Perhaps you could even consider using the level instead of the "count" variable, as a redraw won't be triggered if the level did not change.
Hope this helps.
Can someone please explain to me how to implement a progress bar with a divider just like its shown on the image below?
For the progress bar I am using https://github.com/akexorcist/Android-RoundCornerProgressBar but this does not seem to have a divider option.
replace ProgressDrawable from my answer with the modified one:
class ProgressDrawable extends Drawable {
private static final int NUM_SEGMENTS = 4;
private final int mForeground;
private final int mBackground;
private final Paint mPaint = new Paint();
private final RectF mSegment = new RectF();
public ProgressDrawable(int fgColor, int bgColor) {
mForeground = fgColor;
mBackground = bgColor;
}
#Override
protected boolean onLevelChange(int level) {
invalidateSelf();
return true;
}
#Override
public void draw(Canvas canvas) {
float level = getLevel() / 10000f;
Rect b = getBounds();
float gapWidth = b.height() / 2f;
float segmentWidth = (b.width() - (NUM_SEGMENTS - 1) * gapWidth) / NUM_SEGMENTS;
mSegment.set(0, 0, segmentWidth, b.height());
mPaint.setColor(mForeground);
for (int i = 0; i < NUM_SEGMENTS; i++) {
float loLevel = i / (float) NUM_SEGMENTS;
float hiLevel = (i + 1) / (float) NUM_SEGMENTS;
if (loLevel <= level && level <= hiLevel) {
float middle = mSegment.left + NUM_SEGMENTS * segmentWidth * (level - loLevel);
canvas.drawRect(mSegment.left, mSegment.top, middle, mSegment.bottom, mPaint);
mPaint.setColor(mBackground);
canvas.drawRect(middle, mSegment.top, mSegment.right, mSegment.bottom, mPaint);
} else {
canvas.drawRect(mSegment, mPaint);
}
mSegment.offset(mSegment.width() + gapWidth, 0);
}
}
#Override
public void setAlpha(int alpha) {
}
#Override
public void setColorFilter(ColorFilter cf) {
}
#Override
public int getOpacity() {
return PixelFormat.TRANSLUCENT;
}
}
and create it like this:
Drawable d = new ProgressDrawable(0xdd00ff00, 0x4400ff00);
/**
* Created by nagendra on 16/06/15.
*/
public class ProgressBarDrawable extends Drawable {
private int parts = 10;
private Paint paint = null;
private int fillColor = Color.parseColor("#2D6EB9");
private int emptyColor = Color.parseColor("#233952");
private int separatorColor = Color.parseColor("#FFFFFF");
private RectF rectFill = null;
private RectF rectEmpty = null;
private List<RectF> separators = null;
public ProgressBarDrawable(int parts)
{
this.parts = parts;
this.paint = new Paint(Paint.ANTI_ALIAS_FLAG);
this.separators = new ArrayList<RectF>();
}
#Override
protected boolean onLevelChange(int level)
{
invalidateSelf();
return true;
}
#Override
public void draw(Canvas canvas)
{
// Calculate values
Rect b = getBounds();
float width = b.width();
float height = b.height();
int spaceFilled = (int)(getLevel() * width / 10000);
this.rectFill = new RectF(0, 0, spaceFilled, height);
this.rectEmpty = new RectF(spaceFilled, 0, width, height);
int spaceBetween = (int)(width / 100);
int widthPart = (int)(width / this.parts - (int)(0.9 * spaceBetween));
int startX = widthPart;
for (int i=0; i<this.parts - 1; i++)
{
this.separators.add( new RectF(startX, 0, startX + spaceBetween, height) );
startX += spaceBetween + widthPart;
}
// Foreground
this.paint.setColor(this.fillColor);
canvas.drawRect(this.rectFill, this.paint);
// Background
this.paint.setColor(this.emptyColor);
canvas.drawRect(this.rectEmpty, this.paint);
// Separator
this.paint.setColor(this.separatorColor);
for (RectF separator : this.separators)
{
canvas.drawRect(separator, this.paint);
}
}
#Override
public void setAlpha(int alpha)
{
}
#Override
public void setColorFilter(ColorFilter cf)
{
}
#Override
public int getOpacity()
{
return PixelFormat.TRANSLUCENT;
}
}
in XM Layout
<ProgressBar
android:id="#+id/progress_bar_test"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="?android:attr/progressBarStyleHorizontal"
android:max="100"
android:progress="10"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
/>
ProgressBar progressBar= (ProgressBar)findViewById(R.id.progress_bar_test);
ProgressBarDrawable bgProgress= new ProgressBarDrawable(5);
progressBar.setProgressDrawable(bgProgress);
With the help of this and this answers, I could create my customized version of the segmented horizontal progress bar.
First, Create a class as follows.
public class SegmentedProgressDrawable extends Drawable {
private int parts;
private Paint paint;
private int fillColor;
private int emptyColor;
private int cutOffWidth;
private int separatorColor;
public SegmentedProgressDrawable(int parts, int fillColor, int emptyColor, int separatorColor) {
this.parts = parts;
this.fillColor = fillColor;
this.emptyColor = emptyColor;
this.separatorColor = separatorColor;
this.paint = new Paint(Paint.ANTI_ALIAS_FLAG);
}
#Override
protected boolean onLevelChange(int level) {
invalidateSelf();
return true;
}
#Override
public void draw(#NonNull Canvas canvas) {
// Calculate values
Rect bounds = getBounds();
float actualWidth = bounds.width();
float actualHeight = bounds.height();
//width with dividers + segment width
int fullBlockWidth = (int) (actualWidth / this.parts);
//ToDo: to change the width of segment change this line
int segmentWidth = (int) (fullBlockWidth * 0.2f);
// int dividerWidth =fullBlockWidth-segmentWidth;
cutOffWidth = (int) (getLevel() * actualWidth / 10000);
//Draw separator as background
RectF fullBox = new RectF(0, 0, actualWidth, actualHeight);
this.paint.setColor(this.separatorColor);
canvas.drawRect(fullBox, this.paint);
//start drawing lines as segmented bars
int startX = 0;
for (int i = 0; i < this.parts; i++) {
int endX = startX + segmentWidth;
//in ideal condition this would be the rectangle
RectF part = new RectF(startX, 0, endX, actualHeight);
//if the segment is below level the paint color should be fill color
if ((startX + segmentWidth) <= cutOffWidth) {
this.paint.setColor(this.fillColor);
canvas.drawRect(part, this.paint);
}
//if the segment is started below the level but ends above the level than we need to create 2 different rectangle
else if (startX < cutOffWidth) {
RectF part1 = new RectF(startX, 0, cutOffWidth, actualHeight);
this.paint.setColor(this.fillColor);
canvas.drawRect(part1, this.paint);
RectF part2 = new RectF(cutOffWidth, 0, startX + segmentWidth, actualHeight);
this.paint.setColor(this.emptyColor);
canvas.drawRect(part2, this.paint);
}
//if the segment is above level the paint color should be empty color
else {
this.paint.setColor(this.emptyColor);
canvas.drawRect(part, this.paint);
}
//update the startX to start the new segment with the gap of divider and segment width
startX += fullBlockWidth;
}
}
#Override
public void setAlpha(int alpha) {
}
#Override
public void setColorFilter(ColorFilter cf) {
}
#Override
public int getOpacity() {
return PixelFormat.TRANSLUCENT;
}
}
And I, used it as follows:
horizontalProgressBar = findViewById(R.id.horizontal_progress_bar);
int fillColor = ContextCompat.getColor(getActivity(), R.color.primary);
int emptyColor = ContextCompat.getColor(getActivity(), R.color.color_redeem_badge_bg);
int separatorColor = ContextCompat.getColor(getActivity(), R.color.transparent);
SegmentedProgressDrawable progressDrawable = new SegmentedProgressDrawable(20, fillColor, emptyColor, separatorColor);
horizontalProgressBar.setProgressDrawable(progressDrawable);
horizontalProgressBar.setProgress(60);
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.