Last active
April 29, 2019 21:28
-
-
Save alphamu/3c893704274e14f83903 to your computer and use it in GitHub Desktop.
A View that displayers a horizontal bar like the kind that is used in a bar chart.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import android.content.Context; | |
import android.content.res.TypedArray; | |
import android.graphics.Canvas; | |
import android.graphics.Color; | |
import android.graphics.Paint; | |
import android.graphics.Rect; | |
import android.graphics.Typeface; | |
import android.util.AttributeSet; | |
import android.view.View; | |
public class HorizontalBarView extends View { | |
String mText = ""; | |
float mValue = 30.0f; | |
Paint mBarPaintEmpty = new Paint(); | |
Paint mBarPaintFill = new Paint(); | |
Paint mFillTextPaint = new Paint(); | |
Paint mEmptyTextPaint = new Paint(); | |
Rect mBarRect = new Rect(); | |
Rect mTextRect = new Rect(); | |
int mHalfStrokeWidth; | |
int mEmptyColor; | |
int mFillColor; | |
int mEmptyTextColor; | |
int mFillTextColor; | |
int mTextSize = 15; | |
int mTextWidth = 0; | |
int mTextPadding = mTextSize; | |
public HorizontalBarView(Context context) { | |
super(context); | |
init(); | |
} | |
public HorizontalBarView(Context context, AttributeSet attrs) { | |
super(context, attrs); | |
init(context, attrs); | |
} | |
public HorizontalBarView(Context context, AttributeSet attrs, int defStyleAttr) { | |
super(context, attrs, defStyleAttr); | |
init(context, attrs); | |
} | |
private void init(Context context, AttributeSet attrs) { | |
// attrs contains the raw values for the XML attributes | |
// that were specified in the layout, which don't include | |
// attributes set by styles or themes, and which may have | |
// unresolved references. Call obtainStyledAttributes() | |
// to get the final values for each attribute. | |
// | |
// This call uses R.styleable.PieChart, which is an array of | |
// the custom attributes that were declared in attrs.xml. | |
TypedArray a = context.getTheme().obtainStyledAttributes( | |
attrs, | |
R.styleable.HorizontalBarView, | |
0, 0 | |
); | |
String fontPath = null; | |
try { | |
// Retrieve the values from the TypedArray and store into | |
// fields of this class. | |
// | |
// The R.styleable.PieChart_* constants represent the index for | |
// each custom attribute in the R.styleable.PieChart array. | |
mEmptyColor = a.getColor(R.styleable.HorizontalBarView_bar_emptyColor, Color.GRAY); | |
mFillColor = a.getColor(R.styleable.HorizontalBarView_bar_fillColor, Color.BLUE); | |
mEmptyTextColor = a.getColor(R.styleable.HorizontalBarView_bar_emptyTextColor, Color.BLACK); | |
mFillTextColor = a.getColor(R.styleable.HorizontalBarView_bar_fillTextColor, Color.WHITE); | |
mTextSize = a.getDimensionPixelSize(R.styleable.HorizontalBarView_bar_textSize, mTextSize); | |
mText = a.getString(R.styleable.HorizontalBarView_bar_text); | |
mTextPadding = a.getDimensionPixelSize(R.styleable.HorizontalBarView_bar_textPadding, mTextPadding); | |
mValue = a.getFloat(R.styleable.HorizontalBarView_bar_fillPercentage, mValue); | |
fontPath = a.getString(R.styleable.HorizontalBarView_bar_fontPath); | |
if (mText == null) { | |
mText = ""; | |
} | |
} finally { | |
// release the TypedArray so that it can be reused. | |
a.recycle(); | |
} | |
mBarPaintEmpty.setColor(mEmptyColor); | |
mBarPaintEmpty.setStyle(Paint.Style.FILL); | |
mBarPaintFill.setColor(mFillColor); | |
mBarPaintFill.setStyle(Paint.Style.FILL); | |
mEmptyTextPaint.setColor(mEmptyTextColor); | |
mEmptyTextPaint.setStyle(Paint.Style.FILL); | |
mEmptyTextPaint.setTextSize(mTextSize); | |
mEmptyTextPaint.setAntiAlias(true); | |
mFillTextPaint.setColor(mFillTextColor); | |
mFillTextPaint.setStyle(Paint.Style.FILL); | |
mFillTextPaint.setTextSize(mTextSize); | |
mFillTextPaint.setAntiAlias(true); | |
if (fontPath != null && fontPath.length() > 0) { | |
Typeface t = Typeface.createFromAsset(context.getAssets(), fontPath); | |
mFillTextPaint.setTypeface(t); | |
mEmptyTextPaint.setTypeface(t); | |
} | |
mTextWidth = (int) mEmptyTextPaint.measureText(mText); | |
} | |
private void init() { | |
mBarPaintEmpty.setColor(Color.GRAY); | |
mBarPaintEmpty.setStyle(Paint.Style.FILL); | |
mBarPaintFill.setColor(Color.BLUE); | |
mBarPaintFill.setStyle(Paint.Style.FILL); | |
mEmptyTextPaint.setColor(Color.BLACK); | |
mEmptyTextPaint.setAntiAlias(true); | |
mFillTextPaint.setColor(Color.WHITE); | |
mFillTextPaint.setAntiAlias(true); | |
} | |
public void setText(String text) { | |
this.mText = text; | |
mTextWidth = (int) mEmptyTextPaint.measureText(text); | |
invalidate(); | |
} | |
public void setPercentage(float value) { | |
if (value < 0 || value > 100) { | |
throw new IllegalArgumentException("value is a percentage and should be between 0 and 100 found -> "+value); | |
} | |
mValue = value; | |
invalidate(); | |
} | |
@Override | |
protected int getSuggestedMinimumHeight() { | |
int minHeight = super.getSuggestedMinimumHeight(); | |
if (minHeight <= 0) { | |
return mTextSize + (mTextPadding * 2); | |
} else { | |
return minHeight; | |
} | |
} | |
@Override | |
protected int getSuggestedMinimumWidth() { | |
int minWidth = super.getSuggestedMinimumHeight(); | |
if (minWidth <= 0) { | |
return mTextSize + (mTextPadding * 4); | |
} else { | |
return minWidth; | |
} | |
} | |
@Override | |
protected void onSizeChanged(int w, int h, int oldw, int oldh) { | |
super.onSizeChanged(w, h, oldw, oldh); | |
//stroke is always centered, we want to bring it within the bounds | |
//of the drawable area. | |
mHalfStrokeWidth = mBarPaintEmpty.getStrokeWidth() < 2 ? (int) mBarPaintEmpty.getStrokeWidth() : (int) mBarPaintEmpty.getStrokeWidth() / 2; | |
int ypad = getPaddingTop() + getPaddingBottom(); | |
int xpad = getPaddingLeft() + getPaddingRight(); | |
mBarRect.left = getPaddingLeft() + mHalfStrokeWidth; | |
mBarRect.top = getPaddingTop() + mHalfStrokeWidth; | |
mBarRect.right = getWidth() - getPaddingRight() - mHalfStrokeWidth; | |
mBarRect.bottom = getHeight() - getPaddingBottom() - mHalfStrokeWidth; | |
mEmptyTextPaint.getTextBounds(mText, 0, mText.length(), mTextRect); | |
mTextRect.left = getPaddingLeft() + mHalfStrokeWidth; //recalculate this on draw, so it's close to the fill | |
if (isInEditMode()) { | |
mTextRect.top = mBarRect.centerY() + mTextRect.bottom + mHalfStrokeWidth; | |
} else { | |
mTextRect.top = mBarRect.centerY() + mTextRect.height() / 2 + mHalfStrokeWidth; | |
} | |
} | |
@Override | |
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { | |
// Try for a width based on our minimum | |
int minw = getPaddingLeft() + getPaddingRight() + getSuggestedMinimumWidth(); | |
int w = resolveSizeAndState(minw, widthMeasureSpec, 1); | |
// Whatever the width ends up being, ask for a height that would let the pie | |
// get as big as it can | |
int minh = getPaddingBottom() + getPaddingTop() + getSuggestedMinimumHeight(); | |
int h = resolveSizeAndState(minh, heightMeasureSpec, 0); | |
setMeasuredDimension(w, h); | |
} | |
@Override | |
protected void onDraw(Canvas canvas) { | |
super.onDraw(canvas); | |
//Build the filled part first | |
mBarRect.right = (int) (mBarRect.right * (mValue / 100f)); | |
int fillRight = mBarRect.right; | |
canvas.drawRect(mBarRect, mBarPaintFill); | |
//Draw the empty part starting from the end of the filled part | |
//this is to avoid overdraw. | |
int originalLeft = mBarRect.left; | |
mBarRect.left = mBarRect.right; | |
mBarRect.right = getWidth() - getPaddingRight() - mHalfStrokeWidth; | |
int emptyRight = mBarRect.right; | |
canvas.drawRect(mBarRect, mBarPaintEmpty); | |
mBarRect.left = originalLeft; | |
//double the padding to account for the left and right sides. | |
if (emptyRight - fillRight > mTextWidth + (mTextPadding * 2)) { | |
//recalculate left for text to keep it close to the fill line. | |
mTextRect.left = fillRight + mTextPadding; | |
canvas.drawText(mText, mTextRect.left, mTextRect.top, mEmptyTextPaint); | |
} else { | |
//recalculate left for text to keep it close to the fill line. | |
mTextRect.left = fillRight - mTextWidth - mTextPadding; | |
canvas.drawText(mText,mTextRect.left, mTextRect.top, mFillTextPaint); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?xml version="1.0" encoding="utf-8"?> | |
<resources> | |
<declare-styleable name="HorizontalBarView"> | |
<attr name="bar_emptyColor" format="color"/> | |
<attr name="bar_fillColor" format="color"/> | |
<attr name="bar_emptyTextColor" format="color"/> | |
<attr name="bar_fillTextColor" format="color"/> | |
<attr name="bar_textSize" format="dimension"/> | |
<attr name="bar_text" format="string"/> | |
<attr name="bar_textPadding" format="dimension"/> | |
<attr name="bar_fillPercentage" format="float"/> | |
<attr name="bar_fontPath" format="string"/> | |
</declare-styleable> | |
</resources> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Dear sir, I have two questions:
resolveSizeAndState
method, I see View.java's javadoc, "Size information bit mask for the view's children.", but it's still confusing, could you please give more information about what 0 and 1 mean?getSuggestedMinimumHeight
andgetSuggestedMinimumWidth
, should you return the max value of min value returned by super and calculate from text size and padding?