自定义view之柱状图

因为公司项目需求,美工的设计图要我画一个柱状图表,我第一时间就想到了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();
	}

接着,onDraw,你懂的,慢慢画吧,少年~

	@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



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章