自定義控件_柱狀圖


在這裏插入圖片描述

使用

ColumnChart lineChart = findViewById(R.id.lineChart);

        //設定20組數據
        int[][] data = new int[20][5];
        data[0][1] = R.color.blue_rgba_24_261_255;
        data[0][2] = R.color.orange;
        data[0][3] = R.color.red_2;
        data[0][4] = R.color.main_green;

        //設置隨機數
        Random random = new Random();
        for (int i = 0; i < 20; i++) {
            data[i][0]  =random.nextInt(19)+1;
        }

        lineChart.setData(data);//添加數據
        lineChart.start();//開啓動畫

佈局文件

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    >
    <com.example.ocean.charts.column.ColumnChart
        android:id="@+id/lineChart"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="400dp"
        app:graphTitle = "騎行記錄"
        app:xAxisNmae = "第幾天"
        app:yAxisNmae = "騎行公里數"
        app:axisTextSize = "4sp"
        app:axisDevidedSizeX = "20"
        app:axisDevidedSizeY = "20"
        android:layout_marginBottom="30dp"
        />
<!--        app:axisTextColor = "000000"-->
</RelativeLayout>

實現步驟


        drawTitle(canvas,basePaint);//繪製標題
        drawAxisArrowX(canvas,basePaint);//繪製X軸箭頭
        drawAxisArrowY(canvas,basePaint);//繪製Y軸箭頭
        drawAxisX(canvas,basePaint);//繪製X軸
        drawAxisY(canvas,basePaint);//繪製Y軸
        drawAxisScaleX(canvas,basePaint);//繪製X軸刻度
        drawAxisScaleY(canvas,basePaint);//繪製Y軸刻度
        drawAxisScaleValuveX(canvas,basePaint);//繪製X軸刻度值
        drawAxisScaleXValuveY(canvas,basePaint);//繪製Y軸刻度值
        drawColumn(canvas,basePaint);//繪製柱體

圖表類 ColumnChart.java

package com.example.ocean.charts.column;

import android.animation.Animator;
import android.animation.ValueAnimator;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.os.Build;
import android.os.Handler;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;

import com.example.ocean.R;

import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;

public class ColumnChart extends BaseColumnChart {

    private float moveX;//當前手指在屏幕上X軸的座標
    private float moveY;//當前手指在屏幕上Y軸的座標
    private Paint touchPaint;//觸摸時候的畫筆
    int[][] data;//傳入的數據
    public void setData(int[][] data) {
        this.data = data;
    }

    public ColumnChart(Context context) {
        this(context,null);
    }

    public ColumnChart(Context context, @Nullable AttributeSet attrs) {
        this(context,attrs,0);
    }

    public ColumnChart(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        this(context, attrs, defStyleAttr,0);
    }

    public ColumnChart(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        initPaint();
    }


//    初始化畫筆
    private void initPaint() {
        touchPaint = new Paint(basePaint);//觸摸時候的畫筆,觸摸效果
        touchPaint.setStyle(Paint.Style.FILL);
    }

    /**        Explain : 返回當前需要設定的Y軸的長度
    * @author LiXiang */
    @Override
    protected int getSpaceY() {
        return getDefaultSpaceY();
    }

    /**        Explain : 返回當前需要設定的X軸的長度
     * @author LiXiang */
    @Override
    protected int getSpaceX() {
        return getDefaultSpaceX();
    }


    /**        Explain : 繪製柱體
    * @author LiXiang */
    @Override
    protected void drawColumn(Canvas canvas, Paint basePaint) {

        Paint rectPaint = new Paint(basePaint);
        if (data != null) {

        for (int i = 0; i < data.length; i++) {
            getColumnColor(rectPaint, data[i]);
            canvas.drawRect(originalX+segmentX*i,originalY-segmentY*data[i][0]*mAnimatedValue,originalX+segmentX*(i+1)-dip2px(2),originalY,rectPaint);
        }
        }

        if ( onTouch && mAnimatedValue == mAnimatedValueMax) {//曲線繪製完成之後才能進行觸摸繪製
            drawOnTouch(canvas);
        }

    }

    /**        Explain : 設置柱體顏色,依據當前不同高度設定不同顏色
    * @author LiXiang */
    private int getColumnColor(Paint rectPaint, int[] datum) {
        int color = 0;
        if (datum[0]<5){//高度<5的顏色
            color = ContextCompat.getColor(mContext, data[0][1]);
        }else if (datum[0]<10 && datum[0]>4){//4<高度<10的顏色
            color = ContextCompat.getColor(mContext, data[0][2]);
        }else if (datum[0]<15 && datum[0]>9){//9<高度<15的顏色
            color = ContextCompat.getColor(mContext, data[0][3]);
        }else {//14<高度 的顏色
            color = ContextCompat.getColor(mContext, data[0][4]);
        }
            rectPaint.setColor(color);
        return color;
    }

    /**        Explain : 繪製X軸
     * @author LiXiang */
    @Override
    protected void drawAxisX(Canvas canvas, Paint basePaint) {
        canvas.drawLine(originalX,originalY,endX,originalY,basePaint);
    }

    /**        Explain : 繪製Y軸
    * @author LiXiang */
    @Override
    protected void drawAxisY(Canvas canvas, Paint basePaint) {
        canvas.drawLine(originalX,endY,originalX,originalY,basePaint);
    }

    /**        Explain : 繪製X軸刻度線
    * @author LiXiang */
    @Override
    protected void drawAxisScaleX(Canvas canvas, Paint basePaint) {
        for (int i = 0; i < axisDevidedSizeX-1; i++) {
            canvas.drawLine(originalX+i*segmentX,originalY,originalX+i*segmentX,originalY-dip2px(2),basePaint);
        }
    }

    /**        Explain : 繪製Y軸刻度線
     * @author LiXiang */
    @Override
    protected void drawAxisScaleY(Canvas canvas, Paint basePaint) {
        for (int i = 0; i < axisDevidedSizeY-1; i++) {
            canvas.drawLine(originalX,originalY-i*segmentY,originalX+dip2px(2),originalY-i*segmentY,basePaint);
        }
    }

    /**        Explain : 繪製X軸刻度值
    * @author LiXiang */
    @Override
    protected void drawAxisScaleValuveX(Canvas canvas, Paint basePaint) {
        for (int i = 1; i < axisDevidedSizeX+1; i++) {
            canvas.drawText(i+"",originalX+i*segmentX-segmentX/2,originalY+dip2px(15),basePaint);
        }

    }

    /**        Explain : 繪製Y軸刻度值
     * @author LiXiang */
    @Override
    protected void drawAxisScaleXValuveY(Canvas canvas, Paint basePaint) {
        for (int i = 1; i < axisDevidedSizeY+1; i++) {
            canvas.drawText(i+"",originalX-dip2px(10),originalY-i*segmentY+segmentX/2,basePaint);
        }
    }





    @Override
    public boolean onTouchEvent(MotionEvent event) {
        //當前動畫沒有加載完成則不進行觸摸監聽
        if (mAnimatedValue != mAnimatedValueMax) {
            return false;
        }
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                if (mAnimatedValue == mAnimatedValueMax) {//當動畫效果展示完成以後纔可以進行觸摸效果
                    onTouch = true;
                    moveX = event.getX();
                    moveY = event.getY();

                }
                break;
            case MotionEvent.ACTION_MOVE:
                    moveX = event.getX();
                    moveY = event.getY();

                    //觸摸效果只能在當前自定義視圖之內
                    if (moveX >= getLeft() && moveX <= getRight() && moveY >= getTop() && moveY <= getBottom()) {//設定範圍邊界
                        getParent().requestDisallowInterceptTouchEvent(true);//繪製區域內 允許子view響應觸摸事件
                        invalidate();
                    } else {//當滑出範圍邊界就對視圖延遲一秒後刷新
                        postDelayedInvalidate();
                    }
                break;
            case MotionEvent.ACTION_UP:
                moveX = event.getX();
                moveY = event.getY();
                postDelayedInvalidate();//手指擡起後就對視圖延遲一秒後刷新
                onTouch = false;
                break;
        }
//        返回值由當前是否正在滑動決定
        return onTouch == true ? true : super.onTouchEvent(event);
    }

    private void drawOnTouch(Canvas canvas) {

        //這裏獲取int整型數值 ,剛好與數據源的索引吻合 ,如果數據長度過短,可能會索引越界,可以對index進行判斷
        int index = (int) ((moveX - originalX) / segmentX);
        if (index >= data.length) index = data.length - 1;


        float x0 = originalX + index * segmentX;


        float y = data[index][0];
        float y0 = originalY - y * segmentY;
        //每次繪製之前都需重新設置漸變屬性
        touchPaint.setColor(ContextCompat.getColor(getContext(), R.color.alpha));
        //畫矩形
        canvas.drawRect(x0, y0, x0 + segmentX -dip2px(2), originalY, touchPaint);

        Paint p = new Paint(touchPaint);
        p.setTextSize(dip2px(15));
        p.setColor(getColumnColor(touchPaint,data[index]));

        float x1 = x0 + 0.5f * segmentX;
        canvas.drawText(y + "公里", x1, y0 - defaultPadding / 2, p);

    }



}

基礎類 BaseColumnChart.java


package com.example.ocean.charts.column;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Typeface;
import android.text.TextUtils;
import android.util.AttributeSet;

import com.example.ocean.R;
import com.example.ocean.charts.BaseChart;

import androidx.annotation.Nullable;

public abstract class BaseColumnChart extends BaseChart {

    protected int axisDevidedSizeX;//軸間距數量
    protected int axisDevidedSizeY;//軸間距數量
    protected int axisTextColor;//刻度值顏色
    protected int axisTextSize;//刻度值字體大小
    protected String xAxisNmae;//X軸名字
    protected String yAxisNmae;//Y軸名字
    protected String graphTitle;//圖表名字
    protected int segmentX;//軸間距
    protected int segmentY;//軸間距

    public BaseColumnChart(Context context) {
            this(context,null);
        }

        public BaseColumnChart(Context context, @Nullable AttributeSet attrs) {
            this(context,attrs,0);
        }

        public BaseColumnChart(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
            this(context, attrs, defStyleAttr,0);
        }

        public BaseColumnChart(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
            super(context, attrs, defStyleAttr, defStyleRes);
            /**        Explain : 獲取樣式
             * @author LiXiang */
            TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.GraphStyle);
            graphTitle = typedArray.getString(R.styleable.GraphStyle_graphTitle);
            xAxisNmae = typedArray.getString(R.styleable.GraphStyle_xAxisNmae);
            yAxisNmae = typedArray.getString(R.styleable.GraphStyle_yAxisNmae);
            axisDevidedSizeX = typedArray.getInteger(R.styleable.GraphStyle_axisDevidedSizeX,10);
            axisDevidedSizeY = typedArray.getInteger(R.styleable.GraphStyle_axisDevidedSizeY,20);
            axisTextColor = typedArray.getColor(R.styleable.GraphStyle_axisTextColor,Color.GRAY);
            axisTextSize= (int) typedArray.getDimension(R.styleable.GraphStyle_axisTextSize,4);
            axisTextSize = dip2px(axisTextSize);
            if (typedArray != null) {
                typedArray.recycle();
            }

            //初始化基礎畫筆
            basePaint.setTextSize(dip2px(12));//設置標題大小
            basePaint.setTextAlign(Paint.Align.CENTER);//標題對其格式
            basePaint.setStrokeWidth(dip2px(0.5f));//畫筆粗細
            basePaint.setStrokeCap(Paint.Cap.ROUND);//設置圓角
            basePaint.setColor(Color.GRAY);//設置畫筆顏色
            Typeface font0 = Typeface.create(Typeface.SANS_SERIF, Typeface.DEFAULT_BOLD.getStyle());
            basePaint.setTypeface(font0);

        }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        //獲取刻度的寬度
        segmentX = (endX -  originalX)/axisDevidedSizeX ;
        segmentY = (originalY -  endY )/axisDevidedSizeY;

        drawTitle(canvas,basePaint);//繪製標題
        drawAxisArrowX(canvas,basePaint);//繪製X軸箭頭
        drawAxisArrowY(canvas,basePaint);//繪製Y軸箭頭

        //以下類交給子類處理
        drawAxisX(canvas,basePaint);//繪製X軸
        drawAxisY(canvas,basePaint);//繪製Y軸
        drawAxisScaleX(canvas,basePaint);//繪製X軸刻度
        drawAxisScaleY(canvas,basePaint);//繪製Y軸刻度
        drawAxisScaleValuveX(canvas,basePaint);//繪製X軸刻度值
        drawAxisScaleXValuveY(canvas,basePaint);//繪製Y軸刻度值
        drawColumn(canvas,basePaint);
    }


    /**        Explain : 繪製X軸箭頭
    * @author LiXiang */
    private void drawAxisArrowX(Canvas canvas, Paint basePaint) {
        Path path = new Path();
        path.moveTo(endX+dip2px(2),originalY);
        path.lineTo(endX,originalY + dip2px(2));
        path.lineTo(endX,originalY - dip2px(2));
        path.close();
        canvas.drawPath(path,basePaint);
        canvas.drawText(xAxisNmae,endX+dip2px(3),originalY+dip2px(15),basePaint);
    }

    /**        Explain : 繪製Y軸箭頭
     * @author LiXiang */
    private void drawAxisArrowY(Canvas canvas, Paint basePaint) {
        Path path = new Path();
        path.moveTo(originalX,endY - dip2px(2));
        path.lineTo(originalX+ dip2px(2),endY );
        path.lineTo(originalX- dip2px(2),endY );
        path.close();
        canvas.drawPath(path,basePaint);
        canvas.drawText(yAxisNmae,originalX+defaultPadding/3,endY - dip2px(3),basePaint);
    }

    /**        Explain : 設置標題
     * @author LiXiang
     * @param canvas
     * @param basePaint  */
    private void drawTitle(Canvas canvas, Paint basePaint) {
        if (!TextUtils.isEmpty(graphTitle)) {
            Paint titlePaint = new Paint(basePaint);
            titlePaint.setFakeBoldText(true);//設置字體加粗
            titlePaint.setTextSize(axisTextSize);
            titlePaint.setColor(axisTextColor);
            canvas.drawText(graphTitle,getWidth() / 2,originalY+dip2px(35),titlePaint);
        }
    }

    protected abstract void drawColumn(Canvas canvas, Paint basePaint);

    protected abstract void drawAxisScaleXValuveY(Canvas canvas, Paint basePaint);

    protected abstract void drawAxisScaleValuveX(Canvas canvas, Paint basePaint);

    protected abstract void drawAxisScaleY(Canvas canvas, Paint basePaint);

    protected abstract void drawAxisScaleX(Canvas canvas, Paint basePaint);

    protected abstract void drawAxisY(Canvas canvas, Paint basePaint);

    protected abstract void drawAxisX(Canvas canvas, Paint basePaint);

}


基礎類 BaseChart.java

package com.example.ocean.charts;

import android.animation.Animator;
import android.animation.ValueAnimator;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Paint;
import android.os.Build;
import android.os.Handler;
import android.util.AttributeSet;
import android.view.View;

import com.example.ocean.R;

import androidx.annotation.Nullable;

public abstract class BaseChart extends View {

    protected final Context mContext;

    protected Paint basePaint;//基礎畫筆
    protected int originalX ;//X軸起點
    protected int originalY ;//Y軸起點
    protected int endX ;//X軸終點
    protected int endY ;//Y軸終點
    protected int defaultPadding = dip2px(30);//默認內邊距
    protected ValueAnimator valueAnimator;//動畫
    protected boolean starting = false;//是否正在執行動畫
    protected float mAnimatedValueMax = 1;//動畫最大值
    protected float mAnimatedValue = 0;//動畫當前值
    protected boolean onTouch = false;//是否正在觸摸

    public BaseChart(Context context) {
        this(context,null);
    }

    public BaseChart(Context context, @Nullable AttributeSet attrs) {
        this(context,attrs,0);
    }

    public BaseChart(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        this(context, attrs, defStyleAttr,0);
    }

    public BaseChart(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        mContext = context;
        initBasePaint();
    }

    /**        Explain : 初始化基礎畫筆
    * @author LiXiang */
    private void initBasePaint() {
        if (basePaint == null) {
        basePaint = new Paint();
        basePaint.setAntiAlias(true);//抗鋸齒
        basePaint.setDither(true);//防抖動
        }
    }


    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        originalX = getPaddingLeft() + defaultPadding;
        originalY = getMeasuredHeight() - getPaddingBottom() - 2*defaultPadding;
        endX = getSpaceX()>getDefaultSpaceX()?getSpaceX():getDefaultSpaceX();
        endY = getSpaceY()>getDefaultSpaceY()?getSpaceY():getDefaultSpaceY();

    }

    protected int getDefaultSpaceY() { return getPaddingTop() + defaultPadding; }

    protected int getDefaultSpaceX() { return getMeasuredWidth() - getPaddingRight() - defaultPadding; }

    protected abstract int getSpaceY();

    abstract protected int getSpaceX();


    protected int dip2px(float dipValue) {
        final float scale = Resources.getSystem().getDisplayMetrics().density;
        return (int) (dipValue * scale + 0.5f);
    }



    public void start() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            startAnimator();
        } else {
            this.post(new Runnable() {//可以避免頁面未初始化完成造成的 空白
                @Override
                public void run() {
                    startAnimator();
                }
            });
        }
    }

    private void startAnimator() {
        if ( starting) {//只能繪製一次 或者正在繪製過程中的話不能再次繪製
            return;
        }
        starting = true;
        valueAnimator = ValueAnimator.ofFloat(0, mAnimatedValueMax).setDuration(5000);
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator) {
                mAnimatedValue = (float) valueAnimator.getAnimatedValue();
                if (starting) {
                    System.out.println("mAnimatedValue:" + mAnimatedValue);
                    invalidateOtherData();
                    invalidate();
                }
            }
        });
        valueAnimator.addListener(new Animator.AnimatorListener() {

            @Override
            public void onAnimationStart(Animator animation) {

            }

            @Override
            public void onAnimationEnd(Animator animation) {
                starting = false;
            }

            @Override
            public void onAnimationCancel(Animator animation) {

            }

            @Override
            public void onAnimationRepeat(Animator animation) {

            }
        });
        valueAnimator.start();
    }

    protected  void invalidateOtherData(){};

    public void postDelayedInvalidate() {
        onTouch = false;//置爲響應觸摸操作的繪製
        getParent().requestDisallowInterceptTouchEvent(false);//離開繪製區域,攔截觸摸事件
        handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                invalidate();
            }
        }, 1000);
    }

    @SuppressLint("HandlerLeak")
    private Handler handler = new Handler();

}

Color

    <color name="blue_rgba_24_261_255">#17A1FF</color>
    <color name="orange">#FFBB4D</color>
    <color name="red_2">#F76567</color>
    <color name="main_green">#7EC501</color>

style樣式屬性

 <!--    柱狀圖圖表樣式-->
    <declare-styleable name="GraphStyle">
        <attr name="graphTitle" format="string"/>
        <attr name="xAxisNmae" format="string"/>
        <attr name="yAxisNmae" format="string"/>
        <attr name="axisTextSize" format="dimension|integer"/>
        <attr name="axisTextColor" format="color|integer"/>
        <attr name="axisDevidedSizeX" format="integer"/>
        <attr name="axisDevidedSizeY" format="integer"/>
    </declare-styleable>

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