android:自定義view--橫向柱形圖
阿新 • • 發佈:2019-02-13
此demo也是從我們真實專案中copy出來的;大致步驟和製作MyTabView差不多,只不過稍微繁瑣一些;
設定自定義屬性需要在styles.xml中宣告:/** * Created by zheng on 2017/12/4. * 橫向的柱形圖 * * 1:繪製XY軸 * 2:繪製XY軸刻度和旁邊的文字 * 3:繪製圓角矩形(六個月份的柱子) * 4:繪製和背景顏色一樣的矩形(遮擋圓角矩形左邊的圓角) * */ public class HPillarView extends View { private Paint mPaint; //線的顏色,柱子右邊顯示**人的顏色 private int lineColor, dataFontColor; //X軸距離左邊和右邊的距離,Y軸距離上邊和下邊的距離 private float xLeftSpace, xRightSpace, yTopSpace, yBottomSpace; //XY軸刻度的大小:X軸刻度的高,Y軸刻度的寬 private float xDividerHeight, yDividerWidth; //XY軸刻度顏色 private int xDividerColor, yDividerColor; //x軸下面的字型距離X軸的距離,Y軸左邊的字型距離Y軸的距離 private float txtXSpace, txtYSpace; //每個柱子的高度 private float pillarHeight; //柱子距離Y軸刻度的距離 private float pillarMarginY = 10; private final float FULL_AMOUNT = 7000; private List<Integer> dataList = new ArrayList<>(); private float xyFontSize; public HPillarView(Context context) { this(context, null); } public HPillarView(Context context, @Nullable AttributeSet attrs) { this(context, attrs, 0); } public HPillarView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); initXmlAttrs(context, attrs); initPaint(); } private void initPaint() { mPaint = new Paint(); mPaint.setAntiAlias(true); mPaint.setStyle(Paint.Style.FILL); mPaint.setStrokeWidth(2); } private void initXmlAttrs(Context context, AttributeSet attrs) { TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.hpillar); if (typedArray == null) return; lineColor = typedArray.getColor(R.styleable.hpillar_line_color_h, Color.BLACK); dataFontColor = typedArray.getColor(R.styleable.hpillar_data_font_color_h, Color.BLACK); xLeftSpace = typedArray.getDimension(R.styleable.hpillar_x_left_space_h, 50); xRightSpace = typedArray.getDimension(R.styleable.hpillar_x_right_space_h, 80); yTopSpace = typedArray.getDimension(R.styleable.hpillar_y_top_space_h, 30); yBottomSpace = typedArray.getDimension(R.styleable.hpillar_y_bottom_space_h, 100); yDividerWidth = typedArray.getDimension(R.styleable.hpillar_y_divider_width, 14); xDividerHeight = typedArray.getDimension(R.styleable.hpillar_x_divider_height, 14); xDividerColor = typedArray.getColor(R.styleable.hpillar_x_divider_color, Color.BLACK); yDividerColor = typedArray.getColor(R.styleable.hpillar_y_divider_color, Color.BLACK); xyFontSize = typedArray.getDimension(R.styleable.hpillar_xy_font_size_h, 30); txtXSpace = typedArray.getDimension(R.styleable.hpillar_txt_x_space, 20); txtYSpace = typedArray.getDimension(R.styleable.hpillar_txt_y_space, 15); pillarHeight = typedArray.getDimension(R.styleable.hpillar_pillar_height, 20); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); if (dataList.size() == 0) return; /** * 繪製XY軸 */ initPaintColor(lineColor); float xLineTop = getHeight() - yBottomSpace;//view的高度 - Y軸距離view頂部的距離 //兩點確定一線 canvas.drawLine( xLeftSpace, xLineTop, getWidth() - xRightSpace, xLineTop, mPaint ); canvas.drawLine( xLeftSpace, yTopSpace, xLeftSpace, getHeight() - yBottomSpace, mPaint ); /** * 繪製Y軸刻度 和 左邊的文字 */ //Y軸每個刻度的高度:獲取Y軸真是高度,然後除以6獲取每個刻度的高度 float spaceVertical = (getHeight() - yTopSpace - yBottomSpace) / 6; //Y軸刻度左右的X座標 float yScaleLeftX = xLeftSpace - yDividerWidth / 2; float yScaleRightX = xLeftSpace + yDividerWidth / 2; float[] pts = { yScaleLeftX, yTopSpace, yScaleRightX, yTopSpace, yScaleLeftX, yTopSpace + spaceVertical, yScaleRightX, yTopSpace + spaceVertical, yScaleLeftX, yTopSpace + spaceVertical * 2, yScaleRightX, yTopSpace + spaceVertical * 2, yScaleLeftX, yTopSpace + spaceVertical * 3, yScaleRightX, yTopSpace + spaceVertical * 3, yScaleLeftX, yTopSpace + spaceVertical * 4, yScaleRightX, yTopSpace + spaceVertical * 4, yScaleLeftX, yTopSpace + spaceVertical * 5, yScaleRightX, yTopSpace + spaceVertical * 5 }; //繪製一組線:pts中得資料四個為一組,同樣代表起點終點座標 canvas.drawLines(pts, mPaint); //繪製Y軸刻度左邊文字 Rect yTxtRect; mPaint.setTextSize(xyFontSize); int[] ys = {2, 4, 6, 8, 10, 12}; for (int i = 0; i < ys.length; i++) { String number = ys[(ys.length - 1 - i)] + ""; yTxtRect = new Rect(); //為了讓六個月份居中,測量最大數 mPaint.getTextBounds("12", 0, 2, yTxtRect); //設定居中 mPaint.setTextAlign(Paint.Align.CENTER); canvas.drawText( number, xLeftSpace - yTxtRect.width() / 2 - txtYSpace, yTopSpace + (spaceVertical * i) + yTxtRect.height() / 2, mPaint ); } /** * 繪製X軸刻度 和 X軸下面的文字 */ //獲取x軸刻度間距 view寬度 - x軸左邊距 - X軸右邊距 - Y軸刻度寬度一半 - 柱子距離刻度的距離 float xSpaceVertical = (getWidth() - xLeftSpace - xRightSpace - yDividerWidth / 2 - pillarMarginY) / 7; //X刻度上下Y座標 float xScaleTopY = getHeight() - yBottomSpace - xDividerHeight / 2; float xScaleBottomY = getHeight() - yBottomSpace + xDividerHeight / 2; float[] xptsx = { xLeftSpace + yDividerWidth / 2 + pillarMarginY + xSpaceVertical, xScaleTopY, xLeftSpace + xSpaceVertical + yDividerWidth / 2 + pillarMarginY, xScaleBottomY, xLeftSpace + yDividerWidth / 2 + pillarMarginY + xSpaceVertical * 2, xScaleTopY, xLeftSpace + xSpaceVertical * 2 + yDividerWidth / 2 + pillarMarginY, xScaleBottomY, xLeftSpace + yDividerWidth / 2 + pillarMarginY + xSpaceVertical * 3, xScaleTopY, xLeftSpace + xSpaceVertical * 3 + yDividerWidth / 2 + pillarMarginY, xScaleBottomY, xLeftSpace + yDividerWidth / 2 + pillarMarginY + xSpaceVertical * 4, xScaleTopY, xLeftSpace + xSpaceVertical * 4 + yDividerWidth / 2 + pillarMarginY, xScaleBottomY, xLeftSpace + yDividerWidth / 2 + pillarMarginY + xSpaceVertical * 5, xScaleTopY, xLeftSpace + xSpaceVertical * 5 + yDividerWidth / 2 + pillarMarginY, xScaleBottomY, xLeftSpace + yDividerWidth / 2 + pillarMarginY + xSpaceVertical * 6, xScaleTopY, xLeftSpace + xSpaceVertical * 6 + yDividerWidth / 2 + pillarMarginY, xScaleBottomY, xLeftSpace + yDividerWidth / 2 + pillarMarginY + xSpaceVertical * 7, xScaleTopY, xLeftSpace + xSpaceVertical * 7 + yDividerWidth / 2 + pillarMarginY, xScaleBottomY }; initPaintColor(xDividerColor); canvas.drawLines(xptsx, mPaint); //繪製X軸刻度下面的文字 initPaintColor(lineColor); mPaint.setTextSize(xyFontSize); int[] xs = {1000, 2000, 3000, 4000, 5000, 6000, 7000}; Rect xNumberBound; for (int i = 0; i < xs.length; i++) { String number = xs[i] + ""; xNumberBound = new Rect(); mPaint.getTextBounds(number, 0, number.length(), xNumberBound); canvas.drawText( number, xLeftSpace + yDividerWidth + xSpaceVertical * (i + 1), getHeight() - yBottomSpace + xNumberBound.height() + txtXSpace, mPaint ); } /** * 繪製六組資料的柱形圖 */ //獲取X軸的寬度 float fullWidth = getWidth() - xLeftSpace - xRightSpace - yDividerWidth / 2 - pillarMarginY; for (int x = 0; x < dataList.size(); x++) { //柱子Y軸上座標:view高度 - Y軸距離view下邊的距離 - Y軸刻度高度*(x+1) - 柱子高度一半 float yTop = getHeight() - yBottomSpace - spaceVertical * (x + 1) - pillarHeight / 2; //柱子Y軸下座標:view高度 - Y軸距離view下邊的距離 - Y軸刻度高度*(x+1) + 柱子高度一半 float yBottom = getHeight() - yBottomSpace - spaceVertical * (x + 1) + pillarHeight / 2; float xRight = xLeftSpace + yDividerWidth / 2 + pillarMarginY + fullWidth * (dataList.get(x) / FULL_AMOUNT); //設定漸變背景 LinearGradient lg = new LinearGradient(xLeftSpace + xDividerHeight, yTop, xRight, yBottom, Color.parseColor("#45b0ff"), Color.parseColor("#5dcaa9"), Shader.TileMode.MIRROR); //Shader就是著色器 mPaint.setShader(lg); //繪製圓角矩形 canvas.drawRoundRect( new RectF( xLeftSpace + yDividerWidth / 2, yTop, xRight, yBottom ), 15, 15, mPaint ); //最後將畫筆去除掉Shader mPaint.setShader(null); initPaintColor(dataFontColor); mPaint.setTextAlign(Paint.Align.LEFT); String number = dataList.get(x) + "人"; Rect numRect = new Rect(); mPaint.getTextBounds(number, 0, number.length(), numRect); canvas.drawText(number, xRight + xDividerHeight, getHeight() - yBottomSpace - spaceVertical * (x + 1) + numRect.height() / 2, mPaint ); } //按著Y軸刻度右邊繪製一個矩形,遮擋圓角矩形左邊的圓角 mPaint.setColor(Color.parseColor("#fff9ef")); canvas.drawRect( new RectF( xLeftSpace + yDividerWidth / 2, 0, xLeftSpace + yDividerWidth / 2 + pillarMarginY, getHeight() - yBottomSpace - 10 ), mPaint ); } private void initPaintColor(int color) { mPaint.setColor(color); } public void setData(List<Integer> data) { dataList.clear(); dataList.addAll(data); invalidate(); } }
<declare-styleable name="hpillar"> <!--XY軸顏色--> <attr name="line_color_h" format="color"/> <!--柱子右邊文字顏色--> <attr name="data_font_color_h" format="color" /> <!--X軸距離view左邊和右邊的距離--> <attr name="x_left_space_h" format="dimension" /> <attr name="x_right_space_h" format="dimension" /> <!--Y軸距離view上邊和下邊的距離--> <attr name="y_bottom_space_h" format="dimension" /> <attr name="y_top_space_h" format="dimension" /> <!--X軸刻度的高,Y軸刻度的寬--> <attr name="x_divider_height" format="dimension" /> <attr name="y_divider_width" format="dimension" /> <!--XY軸刻度顏色--> <attr name="x_divider_color" format="color" /> <attr name="y_divider_color" format="color" /> <!--刻度文字字型大小--> <attr name="xy_font_size_h" format="dimension" /> <!--軸下面文字離X軸距離--> <attr name="txt_x_space" format="dimension" /> <!--Y軸左邊文字離Y軸距離--> <attr name="txt_y_space" format="dimension" /> <!--每個柱子的高度--> <attr name="pillar_height" format="dimension"/> </declare-styleable>
這些屬性都有預設初始值,如需修改可以再佈局檔案中設定賦值:
<com.example.zheng.hpillarview.view.HPillarView android:id="@+id/pillar_h" android:layout_width="match_parent" android:layout_height="200dp" android:background="#fff9ef" app:line_color_h="#4eb0f1" app:xy_font_size_h="10sp" app:data_font_color_h="#5cc9aa" app:y_top_space_h="20dp" app:x_divider_color="#f8cac2"/>
最後在頁面中使用就非常簡單了(一步搞定):
//初始化資料
chartList.add(3999);
chartList.add(6001);
chartList.add(1888);
chartList.add(6666);
chartList.add(4999);
chartList.add(2999);
hPillarView= (HPillarView) findViewById(R.id.pillar_h);
//設定資料(一步搞定)
hPillarView.setData(chartList);