When implementing charts in Android applications, developers often rely on third-party libraries. However, for simple charting requirements, using a full-featured library might be overkill. This guide demonstrates how to create a custom bar chart view from scratch.
For complex charting needs, consider established libraries like:
- MPAndroidChart
- hellocharts-android
- XCL-Charts
- Android-Charts
Let's explore how to build a custom bar chart view in Android.
Basic Bar Chart Implementation
The following example shows a simple bar chart implementation:
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 java.util.List;
public class CustomBarChart extends View {
private float chartWidth;
private float chartHeight;
private float barWidth;
private float spacing;
private float maxDataValue;
private List<BarData> dataItems;
private Paint barPaint;
private Paint textPaint;
private RectF barRect;
public CustomBarChart(Context context) {
this(context, null);
}
public CustomBarChart(Context context, AttributeSet attrs) {
super(context, attrs);
initialize();
}
private void initialize() {
barPaint = new Paint();
barPaint.setAntiAlias(true);
barRect = new RectF();
textPaint = new Paint();
textPaint.setAntiAlias(true);
textPaint.setTextAlign(Paint.Align.CENTER);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
chartWidth = widthSize - getPaddingLeft() - getPaddingRight();
chartHeight = heightSize - getPaddingTop() - getPaddingBottom();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (dataItems == null || dataItems.isEmpty()) return;
float availableWidth = chartWidth - spacing;
barWidth = (availableWidth / dataItems.size()) * 0.7f;
float maxBarHeight = chartHeight * 0.8f;
float bottomY = chartHeight - 50;
for (int i = 0; i < dataItems.size(); i++) {
BarData item = dataItems.get(i);
float barHeight = (item.getValue() / maxDataValue) * maxBarHeight;
float leftX = getPaddingLeft() + spacing + (i * (barWidth + spacing));
float topY = bottomY - barHeight;
// Draw bar
barPaint.setColor(item.getColor());
barRect.set(leftX, topY, leftX + barWidth, bottomY);
canvas.drawRect(barRect, barPaint);
// Draw label
textPaint.setColor(Color.BLACK);
textPaint.setTextSize(12);
canvas.drawText(item.getLabel(), leftX + barWidth/2, bottomY + 20, textPaint);
// Draw value
canvas.drawText(String.valueOf(item.getValue()),
leftX + barWidth/2, topY - 10, textPaint);
}
}
public void setData(List<BarData> data) {
this.dataItems = data;
// Calculate max value for scaling
maxDataValue = 0;
for (BarData item : data) {
if (item.getValue() > maxDataValue) {
maxDataValue = item.getValue();
}
}
invalidate();
}
public static class BarData {
private int color;
private String label;
private float value;
public BarData(int color, String label, float value) {
this.color = color;
this.label = label;
this.value = value;
}
public int getColor() { return color; }
public String getLabel() { return label; }
public float getValue() { return value; }
}
}Multi-Layer Bar Chart
For more complex visualization, you might need a multi-layer bar chart. Here's an implementation that supports stacked bars:
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 java.util.List;
public class StackedBarChart extends View {
private float chartWidth;
private float chartHeight;
private float barWidth;
private float spacing;
private float maxDataValue;
private Paint barPaint;
private Paint textPaint;
private RectF barRect;
private List<StackedBarData> dataItems;
public StackedBarChart(Context context) {
this(context, null);
}
public StackedBarChart(Context context, AttributeSet attrs) {
super(context, attrs);
initialize();
}
private void initialize() {
barPaint = new Paint();
barPaint.setAntiAlias(true);
barRect = new RectF();
textPaint = new Paint();
textPaint.setAntiAlias(true);
textPaint.setTextAlign(Paint.Align.CENTER);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
chartWidth = widthSize - getPaddingLeft() - getPaddingRight();
chartHeight = heightSize - getPaddingTop() - getPaddingBottom();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (dataItems == null || dataItems.isEmpty()) return;
float availableWidth = chartWidth - spacing;
barWidth = (availableWidth / dataItems.size()) * 0.7f;
float maxBarHeight = chartHeight * 0.8f;
float bottomY = chartHeight - 50;
for (int i = 0; i < dataItems.size(); i++) {
StackedBarData stackedBar = dataItems.get(i);
List<BarSegment> segments = stackedBar.getSegments();
float currentHeight = 0;
for (BarSegment segment : segments) {
float segmentHeight = (segment.getValue() / maxDataValue) * maxBarHeight;
float topY = bottomY - currentHeight - segmentHeight;
// Draw segment
barPaint.setColor(segment.getColor());
barRect.set(getPaddingLeft() + spacing + (i * (barWidth + spacing)),
topY,
getPaddingLeft() + spacing + (i * (barWidth + spacing)) + barWidth,
bottomY - currentHeight);
canvas.drawRect(barRect, barPaint);
// Draw value
textPaint.setColor(Color.WHITE);
textPaint.setTextSize(10);
canvas.drawText(String.valueOf(segment.getValue()),
getPaddingLeft() + spacing + (i * (barWidth + spacing)) + barWidth/2,
topY + segmentHeight/2, textPaint);
currentHeight += segmentHeight;
}
// Draw label
textPaint.setColor(Color.BLACK);
textPaint.setTextSize(12);
canvas.drawText(stackedBar.getLabel(),
getPaddingLeft() + spacing + (i * (barWidth + spacing)) + barWidth/2,
bottomY + 20, textPaint);
}
}
public void setData(List<StackedBarData> data) {
this.dataItems = data;
// Calculate max value for scaling
maxDataValue = 0;
for (StackedBarData stackedBar : data) {
float totalValue = 0;
for (BarSegment segment : stackedBar.getSegments()) {
totalValue += segment.getValue();
}
if (totalValue > maxDataValue) {
maxDataValue = totalValue;
}
}
invalidate();
}
public static class StackedBarData {
private String label;
private List<BarSegment> segments;
public StackedBarData(String label, List<BarSegment> segments) {
this.label = label;
this.segments = segments;
}
public String getLabel() { return label; }
public List<BarSegment> getSegments() { return segments; }
}
public static class BarSegment {
private int color;
private float value;
public BarSegment(int color, float value) {
this.color = color;
this.value = value;
}
public int getColor() { return color; }
public float getValue() { return value; }
}
}Implementation Tips
- Use anti-aliased painting for smoother graphics
- Implement proper measurement and layout handling
- Add animation effects for better user experience
- Consider adding touch interaction for data exploration
- Optimize performance by avoiding unnecessary redraws