Indeterminate ProgressBar with round edges - android

I am simply trying to customize the default indeterminate progressbar. Please how do I go from this
<ProgressBar
android:layout_width="100dp"
android:layout_height="100dp"
android:indeterminate="true"
android:indeterminateTintMode="src_atop"
android:indeterminateTint="#FFFFFF"/>
asper:
to this (easily):
Update: seems the question isn't a bit straight forward so I have added .gifs below to further explain, I want this (with sharp edges): to simply become this (with round edges):
sorry about the color change, that was the best google turned up

You can use https://github.com/korre/android-circular-progress-bar
There you have a method useRoundedCorners you need to pass false to make it not round by-default it is round at the edge
There is a custom class you can actually take from that library(It is more than enough),
public class CircularProgressBar extends View {
private int mViewWidth;
private int mViewHeight;
private final float mStartAngle = -90; // Always start from top (default is: "3 o'clock on a watch.")
private float mSweepAngle = 0; // How long to sweep from mStartAngle
private float mMaxSweepAngle = 360; // Max degrees to sweep = full circle
private int mStrokeWidth = 20; // Width of outline
private int mAnimationDuration = 400; // Animation duration for progress change
private int mMaxProgress = 100; // Max progress to use
private boolean mDrawText = true; // Set to true if progress text should be drawn
private boolean mRoundedCorners = true; // Set to true if rounded corners should be applied to outline ends
private int mProgressColor = Color.BLACK; // Outline color
private int mTextColor = Color.BLACK; // Progress text color
private final Paint mPaint; // Allocate paint outside onDraw to avoid unnecessary object creation
public CircularProgressBar(Context context) {
this(context, null);
}
public CircularProgressBar(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public CircularProgressBar(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
initMeasurments();
drawOutlineArc(canvas);
if (mDrawText) {
drawText(canvas);
}
}
private void initMeasurments() {
mViewWidth = getWidth();
mViewHeight = getHeight();
}
private void drawOutlineArc(Canvas canvas) {
final int diameter = Math.min(mViewWidth, mViewHeight) - (mStrokeWidth * 2);
final RectF outerOval = new RectF(mStrokeWidth, mStrokeWidth, diameter, diameter);
mPaint.setColor(mProgressColor);
mPaint.setStrokeWidth(mStrokeWidth);
mPaint.setAntiAlias(true);
mPaint.setStrokeCap(mRoundedCorners ? Paint.Cap.ROUND : Paint.Cap.BUTT);
mPaint.setStyle(Paint.Style.STROKE);
canvas.drawArc(outerOval, mStartAngle, mSweepAngle, false, mPaint);
}
private void drawText(Canvas canvas) {
mPaint.setTextSize(Math.min(mViewWidth, mViewHeight) / 5f);
mPaint.setTextAlign(Paint.Align.CENTER);
mPaint.setStrokeWidth(0);
mPaint.setColor(mTextColor);
// Center text
int xPos = (canvas.getWidth() / 2);
int yPos = (int) ((canvas.getHeight() / 2) - ((mPaint.descent() + mPaint.ascent()) / 2)) ;
canvas.drawText(calcProgressFromSweepAngle(mSweepAngle) + "%", xPos, yPos, mPaint);
}
private float calcSweepAngleFromProgress(int progress) {
return (mMaxSweepAngle / mMaxProgress) * progress;
}
private int calcProgressFromSweepAngle(float sweepAngle) {
return (int) ((sweepAngle * mMaxProgress) / mMaxSweepAngle);
}
/**
* Set progress of the circular progress bar.
* #param progress progress between 0 and 100.
*/
public void setProgress(int progress) {
ValueAnimator animator = ValueAnimator.ofFloat(mSweepAngle, calcSweepAngleFromProgress(progress));
animator.setInterpolator(new DecelerateInterpolator());
animator.setDuration(mAnimationDuration);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
mSweepAngle = (float) valueAnimator.getAnimatedValue();
invalidate();
}
});
animator.start();
}
public void setProgressColor(int color) {
mProgressColor = color;
invalidate();
}
public void setProgressWidth(int width) {
mStrokeWidth = width;
invalidate();
}
public void setTextColor(int color) {
mTextColor = color;
invalidate();
}
public void showProgressText(boolean show) {
mDrawText = show;
invalidate();
}
/**
* Toggle this if you don't want rounded corners on progress bar.
* Default is true.
* #param roundedCorners true if you want rounded corners of false otherwise.
*/
public void useRoundedCorners(boolean roundedCorners) {
mRoundedCorners = roundedCorners;
invalidate();
}}
Then you can set the view in your xml like
<yourPackageName.CircularProgressBar
android:id="#+id/circularProgress"
android:layout_width="180dp"
android:layout_height="180dp"/>
And in your class you can call it like this,
CircularProgressBar circularProgressBar = (CircularProgressBar)
findViewById(R.id.circularProgress);
circularProgressBar.setProgress(50);
circularProgressBar.setProgressColor(Color.BLUE);

Related

Android custom rounded progress view

I would like to build a custom progress audio player. like facebook do.
I use a csutom Linearview for this and GradientDrawables. I fill progress rect on Ondraw function.
public class wavPlayerView extends LinearLayout {
private float mCornerRadius = 0;
private float mProgressMargin = 0;
private int progressColor;
private boolean mFinish;
private int mProgress=0;
private int mMaxProgress = 100;
private int mMinProgress = 0;
private GradientDrawable mDrawableLayout;
private GradientDrawable mDrawableProgressBackground;
private GradientDrawable mDrawableProgress;
private Context mContext;
private wavPlayer mWavPlayer;
private ImageView wavPlayerButton;
private TextView wavPlayerDuration;
public wavPlayerView(Context context) {
super(context);
initialize(context,null);
}
public wavPlayerView(Context context, #Nullable AttributeSet attrs) {
super(context, attrs);
initialize(context,attrs);
}
public wavPlayerView(Context context, #Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initialize(context,attrs);
}
/**
*
* #param context
* #param attrs
*/
private void initialize(Context context, AttributeSet attrs) {
mContext=context;
LayoutInflater.from(context).inflate(R.layout.wav_player_layout, this);
setOrientation(VERTICAL);
//get elements
wavPlayerButton = (ImageView) findViewById(R.id.wavPlayerBtn);
wavPlayerDuration=(TextView) findViewById(R.id.wavDuration);
//Progress background drawable
mDrawableProgressBackground = new GradientDrawable();
//Progress drawable
mDrawableProgress = new GradientDrawable();
//Normal drawable
mDrawableLayout = new GradientDrawable();
//Get default normal color
int defaultButtonColor = context.getResources().getColor(R.color.wite);
//Get default progress color
int defaultProgressColor = context.getResources().getColor(R.color.blue);
//Get default progress background color
int defaultBackColor = context.getResources().getColor(R.color.blue);
TypedArray attr = context.obtainStyledAttributes(attrs, R.styleable.ProgressLayout);
try {
mProgressMargin = attr.getDimension(R.styleable.ProgressLayout_progressMargin, mProgressMargin);
mCornerRadius = attr.getDimension(R.styleable.ProgressLayout_cornerRadius, mCornerRadius);
//Get custom normal color
int layoutColor = attr.getColor(R.styleable.ProgressLayout_layoutColor, defaultButtonColor);
//Set normal color
mDrawableLayout.setColor(layoutColor);
//Get custom progress background color
int progressBackColor = attr.getColor(R.styleable.ProgressLayout_progressBackColor, defaultBackColor);
//Set progress background drawable color
mDrawableProgressBackground.setColor(progressBackColor);
//Get custom progress color
progressColor = attr.getColor(R.styleable.ProgressLayout_progressColor, defaultProgressColor);
//Set progress drawable color
mDrawableProgress.setColor(progressColor);
//Get default progress
mProgress = attr.getInteger(R.styleable.ProgressLayout_progress, mProgress);
//Get minimum progress
mMinProgress = attr.getInteger(R.styleable.ProgressLayout_minProgress, mMinProgress);
//Get maximize progress
mMaxProgress = attr.getInteger(R.styleable.ProgressLayout_maxProgress, mMaxProgress);
} finally {
attr.recycle();
}
//Set corner radius
mDrawableLayout.setCornerRadius(mCornerRadius);
mDrawableProgressBackground.setCornerRadius(mCornerRadius);
setBackgroundDrawable(mDrawableLayout);
mFinish = false;
}
#Override
protected void onDraw(Canvas canvas) {
if (mProgress > mMinProgress && mProgress <= mMaxProgress && !mFinish) {
//Calculate the width of progress
float progressWidth =
(float) getMeasuredWidth() * ((float) (mProgress - mMinProgress) / mMaxProgress - mMinProgress);
if(progressWidth >= (getMeasuredWidth()-mCornerRadius)){
mDrawableProgress.setCornerRadii(new float[]{mCornerRadius, mCornerRadius, mCornerRadius, mCornerRadius,mCornerRadius, mCornerRadius, mCornerRadius, mCornerRadius});
} else{
mDrawableProgress.setCornerRadii(new float[]{mCornerRadius, mCornerRadius, 0, 0, 0, 0, mCornerRadius, mCornerRadius});
}
//Set rect of progress
mDrawableProgress.setBounds(0,0,
(int) (progressWidth),(int)getMeasuredHeight());
//Draw progress
mDrawableProgress.draw(canvas);
if (mProgress == mMaxProgress) {
setBackgroundDrawable(mDrawableLayout);
mFinish = true;
}
}
super.onDraw(canvas);
}
/**
* Set current progress
*/
public void setProgress(int progress) {
if (!mFinish) {
mProgress = progress;
setBackgroundDrawable(mDrawableProgressBackground);
invalidate();
}
}
public void setMaxProgress(int maxProgress) {
mMaxProgress = maxProgress;
}
public void setMinProgress(int minProgress) {
mMinProgress = minProgress;
}
public void reset() {
mFinish = false;
mProgress = mMinProgress;
}
public wavPlayer getmWavPlayer() {
return mWavPlayer;
}
}
problem
The problem is with the rounded corners of linearLayout. when i draw progress rect is out of the layout and became inside when it leave corners .
I resolved problem using this mathematic formula :
[
[
this is the new code :
#Override
protected void onDraw(Canvas canvas) {
if (mProgress > mMinProgress && mProgress <= mMaxProgress && !mFinish) {
//Calculate the width of progress
float progressWidth =
(float) getMeasuredWidth() * ((float) (mProgress - mMinProgress) / mMaxProgress - mMinProgress);
if(progressWidth >= (getMeasuredWidth()-mCornerRadius)){
float diff=progressWidth-((getMeasuredWidth()-mCornerRadius));
mDrawableProgress.setBounds(0,0,
(int) (progressWidth),(int)getMeasuredHeight());
mDrawableProgress.setCornerRadii(new float[]{mCornerRadius, mCornerRadius,diff,diff,diff, diff, mCornerRadius, mCornerRadius});
//Draw progress
mDrawableProgress.draw(canvas);
} else{
if(progressWidth<mCornerRadius){
Paint paint = new Paint();
paint.setStyle(Paint.Style.FILL);
paint.setColor(progressColor);
double a=2* Math.sqrt(progressWidth*((2*mCornerRadius)-progressWidth));
float rectTop=(float)(getMeasuredHeight()-a)/2;
RectF rect=new RectF(0,rectTop,2*progressWidth,(float)(rectTop+a));
canvas.drawArc(rect,90,180,true,paint);
}else{
//Set rect of progress
mDrawableProgress.setBounds(0,0,
(int) (progressWidth),(int)getMeasuredHeight());
mDrawableProgress.setCornerRadii(new float[]{mCornerRadius, mCornerRadius, 0, 0, 0, 0, mCornerRadius, mCornerRadius});
//Draw progress
mDrawableProgress.draw(canvas);
}
}
if (mProgress == mMaxProgress) {
// setBackgroundDrawable(mDrawableLayout);
mFinish = true;
}
}
super.onDraw(canvas);
}
here the result :

How to apply horizontal fading edge on the last line of a maxLine TextView

I'd like to implement a fading edge behavior to TextView like the Google Play Movie does:
As you can see the last letters of the third line have a fading edge effect.
Is there a way to achieve this for a specific line defined via android:maxLines? (For example android:maxLines="3")
I've tried the following but it works with the attribute android:singleLine only which is not my goal:
<TextView
...
android:requiresFadingEdge="horizontal"
android:fadingEdgeLength="30dp"
android:ellipsize="none"
android:singleLine="true" />
Setting android:maxLines here instead results in no fading at all.
Edit/Additional:
Previously I also tried a Shader with LinearGradient while extending TextView like here, but the described solution applies a background/foreground (and there were also some other issues with it ...).
I'd like to apply the Gradient to the last 3-4 characters of the maxLine line. Could this be possible?
Edit:
With Mike M.'s help (take a look into the comments) I could modify his answer to reach my wanted behavior. The final implementation with additions (or here as java file):
public class FadingTextView extends AppCompatTextView {
// Length
private static final float PERCENTAGE = .9f;
private static final int CHARACTERS = 6;
// Attribute for ObjectAnimator
private static final String MAX_HEIGHT_ATTR = "maxHeight";
private final Shader shader;
private final Matrix matrix;
private final Paint paint;
private final Rect bounds;
private int mMaxLines;
private boolean mExpanded = false;
public FadingTextView(Context context) {
this(context, null);
}
public FadingTextView(Context context, AttributeSet attrs) {
this(context, attrs, android.R.attr.textViewStyle);
}
public FadingTextView(Context context, AttributeSet attrs, int defStyleAttribute) {
super(context, attrs, defStyleAttribute);
matrix = new Matrix();
paint = new Paint();
bounds = new Rect();
shader = new LinearGradient(0f, 0f, PERCENTAGE, 0f, Color.TRANSPARENT, Color.BLACK, Shader.TileMode.CLAMP);
paint.setShader(shader);
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
mMaxLines = getMaxLines();
}
#Override
protected void onDraw(Canvas canvas) {
if (getLineCount() > getMaxLines() && !mExpanded
&& getRootView() != null && getText() != null
) {
final Matrix m = matrix;
final Rect b = bounds;
final Layout l = getLayout();
int fadeLength = (int) (getPaint().measureText(getText(), getText().length() - CHARACTERS, getText().length()));
final int line = mMaxLines - 1;
getLineBounds(line, b);
final int lineStart = l.getLineStart(line);
final int lineEnd = l.getLineEnd(line);
final CharSequence text = getText().subSequence(lineStart, lineEnd);
final int measure = (int) (getPaint().measureText(text, 0, text.length()));
b.right = b.left + measure;
b.left = b.right - fadeLength;
final int saveCount = canvas.saveLayer(0, 0, getWidth(), getHeight(), null);
super.onDraw(canvas);
m.reset();
m.setScale(fadeLength, 1f);
m.postTranslate(b.left, 0f);
shader.setLocalMatrix(matrix);
canvas.drawRect(b, paint);
canvas.restoreToCount(saveCount);
} else {
super.onDraw(canvas);
}
}
/**
* Makes the TextView expanding without any animation.
*/
public void expandCollapse() {
setMaxLines(mExpanded ? mMaxLines : getLineCount());
mExpanded = !mExpanded;
}
/**
* Makes the TextView expanding/collapsing with sliding animation (vertically)
*
* #param duration Duration in milliseconds from beginning to end of the animation
*/
public void expandCollapseAnimated(final int duration) {
// Height before the animation (either maxLine or lineCount, depending on current state)
final int startHeight = getMeasuredHeight();
// Set new maxLine value depending on current state
setMaxLines(mExpanded ? mMaxLines : getLineCount());
mExpanded = !mExpanded;
// Measuring new height
measure(View.MeasureSpec.makeMeasureSpec(
getWidth(), View.MeasureSpec.EXACTLY),
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
);
final int endHeight = getMeasuredHeight();
ObjectAnimator animation = ObjectAnimator.ofInt(
this, // TextView
MAX_HEIGHT_ATTR, // maxHeight
startHeight, // height before animation
endHeight // height after animation
);
animation.setDuration(duration).start();
}
/**
* Sets maxLine value programmatically
*
* #param newValue new value for maxLines
*/
public void setNewMaxLine(int newValue) {
mMaxLines = newValue;
}
}
Try this out .. Hope this will work
public class ShadowSpan : Android.Text.Style.CharacterStyle
{
public float Dx;
public float Dy;
public float Radius;
public Android.Graphics.Color Color;
public ShadowSpan(float radius, float dx, float dy, Android.Graphics.Color color)
{
Radius = radius; Dx = dx; Dy = dy; Color = color;
}
public override void UpdateDrawState (TextPaint tp)
{
tp.SetShadowLayer(Radius, Dx, Dy, Color);
}
}

Round edge progress bar in Android

I'm new to android and I'm trying to make a circular progress bar with nice style.
Therefore I found a very simple solution how to make a circular progress bar, using shape="ring"
My code is
mainpage_layout.xml :
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<ProgressBar
android:id="#+id/progressBar"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="250dp"
android:layout_height="250dp"
android:max="100"
android:progress="80"
android:rotation="-90"
android:layout_centerInParent="true"
android:progressDrawable="#drawable/circular_progress" />
</RelativeLayout>
circular_progress.xml:
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:useLevel="true"
android:innerRadiusRatio="2.3"
android:shape="ring"
android:thickness="7dp" >
<solid android:color="#color/colorPrimary" />
</shape>
and its looks like this
I want to style it like this awesome progress bar :
Edit
This is the padded background for the progress bar that i want to make
to add some background to the progress bar and to make the edges of it be rounded instead of squared.
Is there is an easy solution to do that or should I must give up on using this shape="ring" thing.
Thank you very much,
Asaf.
You can use https://github.com/korre/android-circular-progress-bar
There you have a method useRoundedCorners you need to pass false to make it not round by-default it is round at the edge
There is a custom class you can actually take from that library(It is more than enough),
import android.animation.ValueAnimator;
import android.content.Context;
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.DecelerateInterpolator;
public class CircularProgressBar extends View {
private int mViewWidth;
private int mViewHeight;
private final float mStartAngle = -90; // Always start from top (default is: "3 o'clock on a watch.")
private float mSweepAngle = 0; // How long to sweep from mStartAngle
private float mMaxSweepAngle = 360; // Max degrees to sweep = full circle
private int mStrokeWidth = 20; // Width of outline
private int mAnimationDuration = 400; // Animation duration for progress change
private int mMaxProgress = 100; // Max progress to use
private boolean mDrawText = true; // Set to true if progress text should be drawn
private boolean mRoundedCorners = true; // Set to true if rounded corners should be applied to outline ends
private int mProgressColor = Color.BLACK; // Outline color
private int mTextColor = Color.BLACK; // Progress text color
private final Paint mPaint; // Allocate paint outside onDraw to avoid unnecessary object creation
public CircularProgressBar(Context context) {
this(context, null);
}
public CircularProgressBar(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public CircularProgressBar(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
initMeasurments();
drawOutlineArc(canvas);
if (mDrawText) {
drawText(canvas);
}
}
private void initMeasurments() {
mViewWidth = getWidth();
mViewHeight = getHeight();
}
private void drawOutlineArc(Canvas canvas) {
final int diameter = Math.min(mViewWidth, mViewHeight) - (mStrokeWidth * 2);
final RectF outerOval = new RectF(mStrokeWidth, mStrokeWidth, diameter, diameter);
mPaint.setColor(mProgressColor);
mPaint.setStrokeWidth(mStrokeWidth);
mPaint.setAntiAlias(true);
mPaint.setStrokeCap(mRoundedCorners ? Paint.Cap.ROUND : Paint.Cap.BUTT);
mPaint.setStyle(Paint.Style.STROKE);
canvas.drawArc(outerOval, mStartAngle, mSweepAngle, false, mPaint);
}
private void drawText(Canvas canvas) {
mPaint.setTextSize(Math.min(mViewWidth, mViewHeight) / 5f);
mPaint.setTextAlign(Paint.Align.CENTER);
mPaint.setStrokeWidth(0);
mPaint.setColor(mTextColor);
// Center text
int xPos = (canvas.getWidth() / 2);
int yPos = (int) ((canvas.getHeight() / 2) - ((mPaint.descent() + mPaint.ascent()) / 2)) ;
canvas.drawText(calcProgressFromSweepAngle(mSweepAngle) + "%", xPos, yPos, mPaint);
}
private float calcSweepAngleFromProgress(int progress) {
return (mMaxSweepAngle / mMaxProgress) * progress;
}
private int calcProgressFromSweepAngle(float sweepAngle) {
return (int) ((sweepAngle * mMaxProgress) / mMaxSweepAngle);
}
/**
* Set progress of the circular progress bar.
* #param progress progress between 0 and 100.
*/
public void setProgress(int progress) {
ValueAnimator animator = ValueAnimator.ofFloat(mSweepAngle, calcSweepAngleFromProgress(progress));
animator.setInterpolator(new DecelerateInterpolator());
animator.setDuration(mAnimationDuration);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
mSweepAngle = (float) valueAnimator.getAnimatedValue();
invalidate();
}
});
animator.start();
}
public void setProgressColor(int color) {
mProgressColor = color;
invalidate();
}
public void setProgressWidth(int width) {
mStrokeWidth = width;
invalidate();
}
public void setTextColor(int color) {
mTextColor = color;
invalidate();
}
public void showProgressText(boolean show) {
mDrawText = show;
invalidate();
}
/**
* Toggle this if you don't want rounded corners on progress bar.
* Default is true.
* #param roundedCorners true if you want rounded corners of false otherwise.
*/
public void useRoundedCorners(boolean roundedCorners) {
mRoundedCorners = roundedCorners;
invalidate();
}
}
Then you can set the view in your xml like
<yourPackageName.CircularProgressBar
android:id="#+id/circularProgress"
android:layout_width="180dp"
android:layout_height="180dp"/>
And in your class you can call it like this,
CircularProgressBar circularProgressBar = (CircularProgressBar) findViewById(R.id.circularProgress);
circularProgressBar.setProgress(50);
circularProgressBar.setProgressColor(Color.BLUE);

Custom VIew Onclick Style change

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" ?

Customize a ProgressBar to become a Thermometer

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.

Categories

Resources