自定義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



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