折线图(自定义view)

//复制粘贴就完事了
//图片和颜色值需要自己替换一下
<color name="color_EE">#EEEEEE</color>
<color name="color_100">#2BE783</color>
<color name="color_999">#999999</color>
//自定义折线图
public class LineView extends View {

    Paint linePaint;//画线
    //数据圆点被选中的画笔
    private Paint pointSelectedPaint = new Paint();
    private Paint bgPaint;//画背景线
    private Paint textPaint;//画文字(值)
    private Paint lastPaint;//画最后一个文字(值)
    private boolean isInit=false;//初始化画笔

    List<DataBean> dataBeans = new ArrayList<>();//数据集合
    private int width, height;//父容器宽高
    private int xW,yH;//父容器的平均宽高
    //private int marginH=50,marginW=75;//上下左右边距(左右边距相等,上下边距相等,废弃了)
    //上下左右边距(只用到上和左边距)
    private int top_margin=50,buttom_margin=50,left_margin=75,right_margin=75;

    private int marginTopButtom=100,marginLeftRight=150;//左右边距和

    private int maxNum=5000;//y轴最大值
    private int averageValue=1000;//平均值
    private boolean isClick;//距离最近的点
    private int clickIndex;//距离最近的点下标
    //是否需要点击事件
    private boolean clickable;//是否点击(可用于控制外部布局)
    private IsVisibility isVisibility;//创造点击事件
    private Bitmap bitmap;//画图片并且压缩图片,防止图片过大造成OOM
    private int j=1;//记录当前天数
    private int m=1;//后端返回开始的(第一个月的)月份
    private int y=2021;//后端返回年份

    public LineView(Context context) {
        super(context);
    }

    public LineView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    public LineView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

    }

    //y轴标签最大值
    public void setMaxNum(int maxNum){

        this.maxNum = maxNum;
    }
    //数据集合
    public void setDataBeans(List<DataBean> dataBeans){

        this.dataBeans = dataBeans;
    }
    //手指按下移动显示,抬起隐藏
    public void isVisibility(IsVisibility isVisibility){

        this.isVisibility = isVisibility;
    }

    public interface IsVisibility{
        void isVisibility(boolean clickable,int i);
    }
    //年和月
    public void setYear(int y,int m){
        this.y = y;
        this.m = m;
    }

    //平均值
    public void setAverageValue(int averageValue){
        this.averageValue = averageValue;
    }
    //设置上下左右边距   marginH*2=marginTopButtom,marginW*2=marginLeftRight  必须相等(需求左右上下不相等时自行调整)
    public void setMargin(int top_margin,int buttom_margin,int marginTopButtom,int left_margin,int right_margin,int marginLeftRight){

        this.top_margin = top_margin;
        this.buttom_margin = buttom_margin;
        this.marginTopButtom = marginTopButtom;
        this.left_margin = left_margin;
        this.right_margin = right_margin;
        this.marginLeftRight = marginLeftRight;
    }


    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (!isInit) {
            init();//初始化画笔
            isInit=true;
        }

        //画Y轴上的横线,所以只有Y轴坐标发生变化
        for (int i=0;i<=maxNum/averageValue;i++){
            //开始的坐标和结束的坐标,背景画笔 xW+marginW 父容器的宽平均值+宽的边距  i*yH+marginH 当前线*父容器高平均值+高的边距
            canvas.drawLine(xW+left_margin,i*yH+top_margin,width+left_margin,i*yH+top_margin,bgPaint);
            String num = i * averageValue + "";//y轴标签值
            //计算文字的宽高(高度准确,宽度不太准确,需要另一个计算宽度的方法,在下面)
            Rect rect = new Rect();
            textPaint.getTextBounds(num,0,num.length(),rect);
            rect.width();//文字的宽度
            int textHeight = rect.height();//文字的高度
            float clickBgWidth = textPaint.measureText(num);//文字的宽度
            canvas.drawText(num,left_margin-clickBgWidth, height -i*yH+top_margin+textHeight/2,textPaint);//画Y轴标签值
        }



        yearM(canvas);
        for (int i=0;i<dataBeans.size();i++){
            if (i<(dataBeans.size()-1)){
                //画出折线(算是折线图的内容吧) xW+i*xW+marginW 父容器的宽+当前平均值+边距
                //height-dataBeans.get(i).pY*height / maxNum+marginH 父容器的高度-值*父容器的高度/最大值+高度的边距
                // dataBeans.get(i).pY*height / maxNum 压缩比例值(类似于图片的二次采样)
                //第二个坐标的(i+1),其他没有变化
                canvas.drawLine(xW+i*xW+left_margin, height-dataBeans.get(i).pY*height / maxNum+top_margin,xW+(i+1)*xW+left_margin, height-dataBeans.get(i+1).pY*height / maxNum+top_margin, linePaint);
            }




            if (i==dataBeans.size()-1){

                canvas.drawCircle(xW+i*xW+left_margin, height-dataBeans.get(i).pY*height / maxNum+top_margin, 5, linePaint);

                String s = dataBeans.get(i).pY + "";
                Rect rect = new Rect();
                textPaint.getTextBounds(s,0,s.length(),rect);
                rect.width();
                int clickBgHeight = rect.height();
                float clickBgWidth = textPaint.measureText(s);
                bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.img_click_icon);
                Bitmap resizeBitmap = resizeBitmap(bitmap, clickBgWidth, clickBgHeight);
                RectF rectF = new RectF();
                rectF.left =xW+i*xW - clickBgWidth-100+left_margin;
                rectF.top = height-dataBeans.get(i).pY*height / maxNum+top_margin-clickBgHeight-50;
                rectF.right = xW+i*xW+left_margin;
                rectF.bottom = height-dataBeans.get(i).pY*height / maxNum+top_margin;

                canvas.drawBitmap(resizeBitmap, null, rectF, linePaint);//绘制选中后的值背景
                //xW+i*xW - (clickBgWidth/2+(clickBgWidth+100)/2) 文字是在X轴的右边,X轴右边点-(所以文字宽度除以二 + 图片宽度除以二)
                //height-dataBeans.get(i).pY*height / maxNum+margin+clickBgHeight/2-(clickBgHeight+50)/2   文字在Y轴的上面,y轴底部点+所以高度除以二-图片高度初二
                canvas.drawText(dataBeans.get(i).pY+"",xW+i*xW - clickBgWidth-50+left_margin,height-dataBeans.get(i).pY*height / maxNum+top_margin-27,lastPaint);
            }

            //选中时的状态
            if (isClick&&clickIndex==i&&clickable){
                canvas.drawLine(xW+i*xW+left_margin, top_margin,xW+i*xW+left_margin, height+top_margin, textPaint);
                //绘制外层圆环
                canvas.drawCircle(xW+i*xW+left_margin, height-dataBeans.get(i).pY*height / maxNum+top_margin, 3, pointSelectedPaint);
            }

        }




    }

    private void yearM(Canvas canvas) {
        //这里不能是全局变量,因为滑动会一直绘制,所以全局时m值会一直变大
        int j=this.j;//记录当前天数
        int m=this.m;//后端返回开始的(第一个月的)月份
        int y=this.y;//后端返回年份
        String h = 1+"月";//x轴标签值
        Rect rectH = new Rect();
        textPaint.getTextBounds(h,0,h.length(),rectH);//获取文字宽高
        //画第一个月(初始化)
        canvas.drawText(1+"月", xW-top_margin+textPaint.measureText(1+"月")/2+left_margin,height+top_margin+rectH.height()+15,textPaint);
        for (int i=0;i<dataBeans.size();i++){
            //获取文字的宽度,在坐标点的右上方,所以文字以坐标点为中心的话需要这样计算(x轴坐标-文字宽度/2,y轴坐标+文字高度/2)
            float xLableWidth = textPaint.measureText((i+1)+"月");
            String num = (i+1)+"月";
            Rect rectM = new Rect();
            textPaint.getTextBounds(num,0,num.length(),rectM);
            rectM.width();//文字的宽度
            int textHeight = rectM.height();//文字的高度

            //计算月份(画出X轴上的月份)
            if (j<getMonthLastDay(y,m)){
                j++;
            }else {
                j=1;
                if (m>=12){
                    m=1;
                }else {
                    m++;
                }

                //月份xW+i*xW-marginH+xLableWidth/2+marginW
                canvas.drawText(m+"月",xW+i*xW-top_margin+xLableWidth/2+left_margin,height+top_margin+textHeight+15,textPaint);

            }
        }
    }

    private void init() {
        linePaint = new Paint();
        bgPaint = new Paint();
        textPaint = new Paint();
        lastPaint = new Paint();


        /*获取父容器的宽和高*/
        width = getWidth()-marginLeftRight;
        height = getHeight()-marginTopButtom;
        //父容器的平均值
        xW = width / dataBeans.size();
        yH = height / (maxNum/averageValue);

        //只是绘制的XY轴
        linePaint.setStyle(Paint.Style.STROKE);
        linePaint.setStrokeWidth((float) 5.0);             //设置线宽
        linePaint.setColor(getResources().getColor(R.color.color_100));
        linePaint.setAntiAlias(true);// 锯齿不显示

        //只是绘制的XY轴
        bgPaint.setStyle(Paint.Style.STROKE);
        bgPaint.setStrokeWidth((float) 3.0);             //设置线宽
        bgPaint.setColor(getResources().getColor(R.color.color_EE));
        bgPaint.setAntiAlias(true);// 锯齿不显示

        //只是绘制的XY轴
        textPaint.setStyle(Paint.Style.FILL);
        textPaint.setStrokeWidth((float) 1.0);             //设置线宽
        textPaint.setColor(getResources().getColor(R.color.color_999));
        textPaint.setTextSize(30);
        textPaint.setAntiAlias(true);// 锯齿不显示

        //点击后被选中的圆
        pointSelectedPaint.setAntiAlias(true);
        pointSelectedPaint.setStrokeWidth(6);
        pointSelectedPaint.setStyle(Paint.Style.STROKE);
        pointSelectedPaint.setColor(getResources().getColor(R.color.color_999));

        //点击后被选中的圆
        lastPaint.setAntiAlias(true);
        lastPaint.setStrokeWidth(1);
        lastPaint.setTextSize(30);
        lastPaint.setStyle(Paint.Style.FILL);
        lastPaint.setColor(getResources().getColor(R.color.white));

    }


    @Override
    public boolean onTouchEvent(MotionEvent event) {
        float touchX = event.getX();

        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN://按下
            case MotionEvent.ACTION_MOVE://移动    ACTION_HOVER_MOVE 来回移动 HOVER徘徊
                for (int i = 0; i < dataBeans.size(); i++) {
                    int dataX = xW + i * xW;
                    // 控制触摸/点击的范围,在有效范围内才触发
                    if (Math.abs(touchX - dataX) < xW / 2) {//Math.abs(touchX - dataX)绝对值
                        isClick = true;
                        clickIndex = i;
                        clickable=true;
                        if (isVisibility!=null){
                            isVisibility.isVisibility(true,i);
                        }
                        invalidate();
                        //显示点击之后的图片
                    }
                }
                break;

            case MotionEvent.ACTION_UP://抬起   ACTION_POINTER_UP//再次按下前走这个抬起
                clickable=false;
                if (isVisibility!=null){
                    isVisibility.isVisibility(false,-1);
                }
                invalidate();
                break;
        }
        return true;
    }

    /**
     * 得到指定月的天数
     * */
    public static int getMonthLastDay(int year, int month)
    {
        Calendar a = Calendar.getInstance();
        a.set(Calendar.YEAR, year);
        a.set(Calendar.MONTH, month - 1);
        a.set(Calendar.DATE, 1);//把日期设置为当月第一天
        a.roll(Calendar.DATE, -1);//日期回滚一天,也就是最后一天
        int maxDate = a.get(Calendar.DATE);
        return maxDate;
    }

    /**
     * 使用Matrix将Bitmap压缩到指定大小
     *
     * @param bitmap
     * @param w
     * @param h
     * @return
     */
    public static Bitmap resizeBitmap(Bitmap bitmap, float w, float h) {
        int width = bitmap.getWidth();
        int height = bitmap.getHeight();

        float scaleWidth = w / width;
        float scaleHeight = h / height;

        Matrix matrix = new Matrix();
        matrix.postScale(scaleWidth, scaleHeight);

        Bitmap resizedBitmap = Bitmap.createBitmap(bitmap, 0, 0, width,
                height, matrix, true);
        return resizedBitmap;
    }

    /**
     * 释放bitmap资源
     */
    public void recycleBitmap() {
        if (bitmap != null && !bitmap.isRecycled()) {
            bitmap.recycle();
        }
    }

}

//MainActivity使用

public class MainActivity extends AppCompatActivity {

    private ArrayList<DataBean> dataBeans;
    private RelativeLayout rlt_revenue;
    private TextView tv_time;
    private TextView tv_revenue;

    private int j=1;
    private int m=1;//后端返回开始的(第一个月的)月份
    private int y=2021;//后端返回年份
    private LineView lv;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        lv = findViewById(R.id.lv);
        rlt_revenue = findViewById(R.id.rlt_revenue);
        tv_time = findViewById(R.id.tv_time);
        tv_revenue = findViewById(R.id.tv_revenue);

        lv.setMaxNum(1000);//最大值
        lv.setAverageValue(1000);//平均值
        lv.setYear(2021,1);//年和月
        lv.setMargin(50,50,100,100,50,150);//上下左右边距
        dataBeans = new ArrayList<>();
        Random random = new Random();
        for (int i=0;i<90;i++){
            DataBean dataBean = new DataBean();
            dataBean.pY=random.nextInt(1000);//不能大于最大值
            dataBean.mX=i;
            if (j<getMonthLastDay(y,m)){
                dataBean.time="2021-"+m+"-"+j;
                j++;

            }else {
                //月份
                dataBean.time="2021-"+m+"-"+j;
                j=1;
                m++;


            }
            dataBeans.add(dataBean);
        }
        lv.setDataBeans(dataBeans);

        lv.isVisibility(new LineView.IsVisibility() {
            @Override
            public void isVisibility(boolean clickable, int i) {
                Log.d("+++++++++++++++++i=",i+"");
                if (clickable){
                    rlt_revenue.setVisibility(View.VISIBLE);
                    tv_time.setText(dataBeans.get(i).time);
                    tv_revenue.setText("+"+dataBeans.get(i).pY);
                }else {
                    rlt_revenue.setVisibility(View.GONE);
                }

            }
        });


    }

    /**
     * 得到指定月的天数
     * */
    public static int getMonthLastDay(int year, int month)
    {
        Calendar a = Calendar.getInstance();
        a.set(Calendar.YEAR, year);
        a.set(Calendar.MONTH, month - 1);
        a.set(Calendar.DATE, 1);//把日期设置为当月第一天
        a.roll(Calendar.DATE, -1);//日期回滚一天,也就是最后一天
        int maxDate = a.get(Calendar.DATE);
        return maxDate;
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        lv.recycleBitmap();//释放bitmap资源
    }
}


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <com.cheyifu.myapplication.LineView
        android:id="@+id/lv"
        android:layout_width="match_parent"
        android:layout_height="130dp"/>

    <RelativeLayout
        android:id="@+id/rlt_revenue"
        android:visibility="gone"
        android:layout_width="match_parent"
        android:layout_height="30dp"
        android:background="#D9FFEB">

        <TextView
            android:id="@+id/tv_time"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="10dp"
            android:textColor="#333333"
            android:textStyle="bold"
            android:layout_centerVertical="true"
            android:layout_marginLeft="15dp"
            android:text="2021-05-26"/>


        <!--Cumulative 累计-->
        <TextView
            android:id="@+id/tv_revenue"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="10dp"
            android:textColor="#ff0000"
            android:textStyle="bold"
            android:layout_centerVertical="true"
            android:layout_alignParentRight="true"
            android:layout_marginRight="15dp"
            android:text="+3625.14"/>

        <TextView
            android:layout_toLeftOf="@+id/tv_revenue"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="10dp"
            android:textColor="#333333"
            android:textStyle="bold"
            android:layout_centerVertical="true"
            android:text="累计收益:"/>
    </RelativeLayout>
</LinearLayout>

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值