因为公司项目需求,美工的设计图要我画一个柱状图表,我第一时间就想到了AChartEngine.jar这个玩意。但实际用起来却并没有达到设计图上的细节需求,抱着美工要猿画,猿不得不画的赴死精神,豁出去了,一个字,干~
用过AChartEngine.jar包里的都知道,其设计模式为一个工厂类,通过设置renderer渲染器类,然后将其作为参数传入工厂,从工厂里取出相应的图表。
这里就不搞什么工厂类了,直接就是一个产品类,柱状图。该产品的创建需要一张设计图纸,也就是一个settings吧,在其产品类创建一个settings内部类就OK了,相当于AChartEngine中的渲染器类renderer了。
来个效果图吧:
首先,先看看内部类,settings,用于对柱状图的初始化,自定义view不一定需要attrs.xml的,也可以像这样动态加载属性并初始化,,下面一堆get和set,不用管:
public class BarChartView extends View { //。。。。。。。。省略构造函数和ondraw, onTouchEvent //参数类,用于初始化BarChartView public static class BarChartViewSettings{ //数据 private int[] data; //x轴标签,最好应该和数据长度一致 private String[] labels; //left,top,right,bottom的padding数组 private int[] paddings; //标签文本大小 private int labelsTextSizeInSp; //标签文本颜色 private int labelsTextColor; //是否显示数据竖虚线,即纵网格 private boolean showDashLine; //竖虚线的颜色 private int dataLineColor; //竖虚线的宽 private int dataLineWidthInDp; //竖虚线顶部的圆点边宽 private int dataPointStrokeWidthInDp; //竖虚线顶部的圆点半径 private int dataPointRadiusInDp; //竖虚线顶部的圆点颜色 private int dataPointColor; //圆点是否填充 private boolean fillPoint; //是否在柱头显示值 private boolean showDataValues; //值的颜色 private int dataValuesColor; //值的大小 private int dataValuesSizeInSp; //被选中的数据,即touch事件后对应的柱子序号 private int selectedData; //是否显示柱子被选中后添加背景色 private boolean showSelectedShade; //最大值 private int maxValue; public BarChartViewSettings(){ //表格最大值 maxValue=100; //没有数据则默认为长度为7的随机数组 data=new int[7]; for(int i=0; i<data.length; i++){ data[i]=(int) (Math.random()*maxValue); } //没有标签则默认为长度为7的星期一至星期日 labels=new String[]{"一", "二", "三", "四", "五", "六", "日"}; //默认没有paddings paddings=new int[]{0, 0, 0, 0}; //以下均为初始化的默认值 labelsTextSizeInSp=16; labelsTextColor=Color.BLACK; showDashLine=true; dataLineColor=Color.BLACK; dataLineWidthInDp=1; dataPointStrokeWidthInDp=2; dataPointRadiusInDp=4; dataPointColor=Color.BLACK; fillPoint=false; showDataValues=true; dataValuesColor=Color.BLACK; dataValuesSizeInSp=12; showSelectedShade=true; selectedData=0; } public int[] getPaddings() { return paddings; } public void setPaddings(int left, int top, int right, int bottom) { paddings=new int[4]; paddings[0]=left; paddings[1]=top; paddings[2]=right; paddings[3]=bottom; } public int getSelectedData() { return selectedData; } public void setSelectedData(int selectedData) { this.selectedData = selectedData; } public boolean isShowSelectedShade() { return showSelectedShade; } public void setShowSelectedShade(boolean showSelectedShade) { this.showSelectedShade = showSelectedShade; } public int getLabelsTextSizeInSp() { return labelsTextSizeInSp; } public void setLabelsTextSizeInSp(int labelsTextSizeInSp) { this.labelsTextSizeInSp = labelsTextSizeInSp; } public int getDataLineWidthInDp() { return dataLineWidthInDp; } public void setDataLineWidthInDp(int dataLineWidthInDp) { this.dataLineWidthInDp = dataLineWidthInDp; } public int getDataPointStrokeWidthInDp() { return dataPointStrokeWidthInDp; } public void setDataPointStrokeWidthInDp(int dataPointStrokeWidthInDp) { this.dataPointStrokeWidthInDp = dataPointStrokeWidthInDp; } public int getDataPointRadiusInDp() { return dataPointRadiusInDp; } public void setDataPointRadiusInDp(int dataPointRadiusInDp) { this.dataPointRadiusInDp = dataPointRadiusInDp; } public int getDataValuesSizeInSp() { return dataValuesSizeInSp; } public void setDataValuesSizeInSp(int dataValuesSizeInSp) { this.dataValuesSizeInSp = dataValuesSizeInSp; } public int getMaxValue() { return maxValue; } public void setMaxValue(int maxValue) { this.maxValue = maxValue; } public int[] getData() { return data; } public void setData(int[] data) { this.data = data; } public String[] getLabels() { return labels; } public void setLabels(String[] labels) { this.labels = labels; } public int getLabelsTextColor() { return labelsTextColor; } public void setLabelsTextColor(int labelsTextColor) { this.labelsTextColor = labelsTextColor; } public boolean isShowDashLine() { return showDashLine; } public void setShowDashLine(boolean showDashLine) { this.showDashLine = showDashLine; } public int getDataLineColor() { return dataLineColor; } public void setDataLineColor(int dataLineColor) { this.dataLineColor = dataLineColor; } public int getDataPointColor() { return dataPointColor; } public void setDataPointColor(int dataPointColor) { this.dataPointColor = dataPointColor; } public boolean isFillPoint() { return fillPoint; } public void setFillPoint(boolean fillPoint) { this.fillPoint = fillPoint; } public boolean isShowDataValues() { return showDataValues; } public void setShowDataValues(boolean showDataValues) { this.showDataValues = showDataValues; } public int getDataValuesColor() { return dataValuesColor; } public void setDataValuesColor(int dataValuesColor) { this.dataValuesColor = dataValuesColor; } } }
然后,构造函数,传入内部参数类,初始化成员变量:
//数据 private int[] data; //x轴标签,最好应该和数据长度一致 private String[] labels; //left,top,right,bottom的padding数组 private int[] paddings; //柱条的间隔 private int interval; //被选中的数据,即touch事件后对应的柱子 private int selectedData; //是否显示柱子被选中后添加背景色 private boolean showSelectedShade; //标签文本大小 private int labelsTextSize; //标签文本颜色 private int labelsTextColor; //是否显示数据竖虚线 private boolean showDashLine; //竖虚线的颜色 private int dataLineColor; //竖虚线的宽 private int dataLineWidth; //竖虚线顶部的圆点边宽 private int dataPointStrokeWidth; //竖虚线顶部的圆点半径 private int dataPointRadius; //竖虚线顶部的圆点颜色 private int dataPointColor; //圆点是否填充 private boolean fillPoint; //是否显示值 private boolean showDataValues; //值的颜色 private int dataValuesColor; //值的大小 private int dataValuesSize; //最大值 private int maxValue; private Paint paint; public BarChartView(Context context, BarChartView.BarChartViewSettings settings){ super(context); maxValue=settings.getMaxValue(); data=settings.getData(); labels=settings.getLabels(); paddings=settings.getPaddings(); labelsTextSize=settings.getLabelsTextSizeInSp(); labelsTextColor=settings.getLabelsTextColor(); showDashLine=settings.isShowDashLine(); dataLineColor=settings.getDataLineColor(); dataLineWidth=settings.getDataLineWidthInDp(); dataPointStrokeWidth=settings.getDataPointStrokeWidthInDp(); dataPointRadius=settings.getDataPointRadiusInDp(); dataPointColor=settings.getDataPointColor(); fillPoint=settings.isFillPoint(); showDataValues=settings.isShowDataValues(); dataValuesColor=settings.getDataValuesColor(); dataValuesSize=settings.getDataValuesSizeInSp(); selectedData=settings.getSelectedData(); showSelectedShade=settings.isShowSelectedShade(); paint=new Paint(); }
@Override protected void onDraw(Canvas canvas) { // TODO Auto-generated method stub //数据间的间隔 interval=(getWidth()-paddings[0]-paddings[2])/data.length; //画X轴下的labels paint.setColor(labelsTextColor); paint.setTextAlign(Align.CENTER); paint.setTextSize(sp2px(labelsTextSize)); FontMetrics fm=paint.getFontMetrics(); float textHeight=fm.bottom-fm.top; for(int i=0; i<labels.length; i++){ //减去baseline到bottom的高度,大约为文字高度的1/5 canvas.drawText(labels[i], interval*i+interval/2+paddings[0], getHeight()-textHeight/5-paddings[3], paint); } //画X轴 paint.setStrokeWidth(2f); paint.setStyle(Style.STROKE); Path path=new Path(); path.moveTo(0+paddings[0], getHeight()-textHeight-paddings[3]); path.lineTo(getWidth()-paddings[2], getHeight()-textHeight-paddings[3]); canvas.drawPath(path, paint); //画每个数据位置对应的竖虚线,即纵网格线 if(showDashLine){ paint.setStrokeWidth(dp2px(dataLineWidth)); paint.setColor(dataLineColor); //虚线效果 DashPathEffect effect=new DashPathEffect(new float[]{10, 10}, 0); paint.setPathEffect(effect); for(int i=0; i<data.length; i++){ path.reset(); path.moveTo(interval*i+interval/2+paddings[0], getHeight()-textHeight-paddings[3]); path.lineTo(interval*i+interval/2+paddings[0], 0+paddings[1]); canvas.drawPath(path, paint); } } //画数据的实线和数据的圆点 paint.setColor(dataPointColor); paint.setStrokeWidth(dp2px(dataPointStrokeWidth)); if(fillPoint){ paint.setStyle(Style.FILL_AND_STROKE); }else{ paint.setStyle(Style.STROKE); } //除去刚才画虚线的效果 paint.setPathEffect(null); for(int i=0; i<data.length; i++){ path.reset(); path.moveTo(interval*i+interval/2+paddings[0], getHeight()-textHeight-paddings[3]); path.lineTo(interval*i+interval/2+paddings[0], (getHeight()-textHeight-paddings[3]-paddings[1])*(1-(float)data[i]/maxValue)+paddings[1]+dp2px(dataPointRadius)); canvas.drawPath(path, paint); canvas.drawCircle(interval*i+interval/2+paddings[0], (getHeight()-textHeight-paddings[3]-paddings[1])*(1-(float)data[i]/maxValue)+paddings[1], dp2px(dataPointRadius), paint); } //画值在点上面 if(showDataValues){ paint.setTextSize(sp2px(dataValuesSize)); paint.setColor(dataValuesColor); paint.setStyle(Style.FILL); for(int i=0; i<data.length; i++){ canvas.drawText(Integer.toString(data[i]), interval*i+interval/2+paddings[0], (getHeight()-textHeight-paddings[3]-paddings[1])*(1-(float)data[i]/maxValue)+paddings[1]-dp2px(dataPointRadius+2), paint); } } //如果有显示点击效果,则在该bar的背景用Rect画一个背景色即可 if(showSelectedShade){ Rect rect=new Rect(paddings[0]+interval*selectedData, paddings[1], paddings[0]+interval*(selectedData+1), (int) (getHeight()-paddings[3]-textHeight)); paint.setColor(0x33000000); paint.setStyle(Style.FILL_AND_STROKE); canvas.drawRect(rect, paint); paint.reset(); } }
@Override public boolean onTouchEvent(MotionEvent event) { // TODO Auto-generated method stub if(event.getAction()==MotionEvent.ACTION_DOWN){ if(showSelectedShade){ if(event.getX()>getWidth()-paddings[2]||event.getX()<paddings[0]){ return false; } selectedData=(int)(event.getX()-paddings[0])/interval; invalidate(); } } return super.onTouchEvent(event); }
Activity中设置好初始化变量,用两个布局把视图add进去就可以了
public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //默认的chart LinearLayout chartLayout1=(LinearLayout)findViewById(R.id.chart_layout1); BarChartView.BarChartViewSettings settings1=new BarChartViewSettings(); BarChartView chart1=new BarChartView(this, settings1); chartLayout1.addView(chart1); //设置后的chart LinearLayout chartLayout2=(LinearLayout)findViewById(R.id.chart_layout2); BarChartView.BarChartViewSettings settings2=new BarChartViewSettings(); //设置最大值,加200,即最大值的20%左右,可以防止柱头的值超出顶端边界 settings2.setMaxValue(1000+200); //设置12个月各个月的数据 int[] months=new int[12]; for(int i=0; i<months.length; i++){ months[i]=(int) (1000*Math.random()); } settings2.setData(months); //设置12个月的标签 String[] labels=new String[12]; for(int i=0; i<labels.length; i++){ labels[i]=Integer.toString(i+1); } settings2.setLabels(labels); //因为layout背景为蓝色,所以颜色全设为白色 settings2.setDataLineColor(0xffffffff); settings2.setDataPointColor(0xffffffff); settings2.setDataValuesColor(0xffffffff); settings2.setLabelsTextColor(0xffffffff); BarChartView chart2=new BarChartView(this, settings2); chartLayout2.addView(chart2); } }
代码就不贴出来了,有点好长
代码下载:github