自定义view——芝麻分折线图

自定义属性新建资源文件attrs

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="ZhiMaScore">
        <attr name="max_score" format="integer"/>
        <attr name="min_score" format="integer"/>
        <attr name="broken_line_color" format="color"/>
    </declare-styleable>

</resources>

自定义view

package com.hsg.zhimascoredemo;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.DashPathEffect;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Point;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;

import java.util.ArrayList;
import java.util.List;

/**
 * Created by Joe on 2016/12/27.
 */

public class ZhiMaScoreView extends View {
    private float viewWith;
    private float viewHeight;

    private float brokenLineWith = 0.5f;

    private int brokenLineColor   = 0xff02bbb7;
    private int straightLineColor = 0xffe2e2e2;//0xffeaeaea
    private int textNormalColor   = 0xff7e7e7e;

    private int maxScore = 700;
    private int minScore = 650;

    private int monthCount  = 6;
    private int selectMonth = 6;//选中的月份

    private String[] monthText = new String[]{"6月", "7月", "8月", "9月", "10月", "11月"};
    private int[]    score     = new int[]{660, 663, 669, 678, 682, 689};

    private List<Point> scorePoints;

    private int textSize = dipToPx(15);

    private Paint brokenPaint;
    private Paint straightPaint;
    private Paint dottedPaint;
    private Paint textPaint;

    private Path brokenPath;

    public ZhiMaScoreView(Context context)
    {
        super(context);
        initConfig(context,null);
        init();
    }

    public ZhiMaScoreView(Context context, AttributeSet attrs)
    {
        super(context, attrs);
        initConfig(context,attrs);
        init();
    }

    public ZhiMaScoreView(Context context, AttributeSet attrs, int defStyleAttr)
    {
        super(context, attrs, defStyleAttr);
        initConfig(context,attrs);
        init();

    }

    /**
     * 初始化布局配置
     *
     * @param context
     * @param attrs
     */
    private void initConfig(Context context, AttributeSet attrs)
    {
        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ZhiMaScore);

        maxScore=a.getInt(R.styleable.ZhiMaScore_max_score,700);
        minScore=a.getInt(R.styleable.ZhiMaScore_min_score,650);
        brokenLineColor=a.getColor(R.styleable.ZhiMaScore_broken_line_color,brokenLineColor);

        a.recycle();

    }

    private void init()
    {
        brokenPath = new Path();

        brokenPaint = new Paint();
        brokenPaint.setAntiAlias(true);
        brokenPaint.setStyle(Paint.Style.STROKE);
        brokenPaint.setStrokeWidth(dipToPx(brokenLineWith));
        brokenPaint.setStrokeCap(Paint.Cap.ROUND);

        straightPaint = new Paint();
        straightPaint.setAntiAlias(true);
        straightPaint.setStyle(Paint.Style.STROKE);
        straightPaint.setStrokeWidth(brokenLineWith);
        straightPaint.setColor((straightLineColor));
        straightPaint.setStrokeCap(Paint.Cap.ROUND);

        dottedPaint = new Paint();
        dottedPaint.setAntiAlias(true);
        dottedPaint.setStyle(Paint.Style.STROKE);
        dottedPaint.setStrokeWidth(brokenLineWith);
        dottedPaint.setColor((straightLineColor));
        dottedPaint.setStrokeCap(Paint.Cap.ROUND);

        textPaint = new Paint();
        textPaint.setAntiAlias(true);
        textPaint.setTextAlign(Paint.Align.CENTER);
        textPaint.setStyle(Paint.Style.FILL);
        textPaint.setColor((textNormalColor));
        textPaint.setTextSize(dipToPx(15));


    }

    private void initData()
    {
        scorePoints = new ArrayList<>();
        float maxScoreYCoordinate = viewHeight * 0.15f;
        float minScoreYCoordinate = viewHeight * 0.4f;

        Log.v("ScoreTrend", "initData: " + maxScoreYCoordinate);

        float newWith = viewWith - (viewWith * 0.15f) * 2;//分隔线距离最左边和最右边的距离是0.15倍的viewWith
        int   coordinateX;

        for(int i = 0; i < score.length; i++)
        {
            Log.v("ScoreTrend", "initData: " + score[i]);
            Point point = new Point();
            coordinateX = (int) (newWith * ((float) (i) / (monthCount - 1)) + (viewWith * 0.15f));
            point.x = coordinateX;
            if(score[i] > maxScore)
            {
                score[i] = maxScore;
            }
            else if(score[i] < minScore)
            {
                score[i] = minScore;
            }
            point.y = (int) (((float) (maxScore - score[i]) / (maxScore - minScore)) * (minScoreYCoordinate - maxScoreYCoordinate) + maxScoreYCoordinate);
            scorePoints.add(point);
        }
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh)
    {
        super.onSizeChanged(w, h, oldw, oldh);
        viewWith = w;
        viewHeight = h;
        initData();
    }

    @Override
    protected void onDraw(Canvas canvas)
    {
        super.onDraw(canvas);
        drawDottedLine(canvas, viewWith * 0.15f, viewHeight * 0.15f, viewWith, viewHeight * 0.15f);
        drawDottedLine(canvas, viewWith * 0.15f, viewHeight * 0.4f, viewWith, viewHeight * 0.4f);
        drawText(canvas);
        drawMonthLine(canvas);
        drawBrokenLine(canvas);
        drawPoint(canvas);

    }


    @Override
    public boolean onTouchEvent(MotionEvent event)
    {
        this.getParent().requestDisallowInterceptTouchEvent(true);//一旦底层View收到touch的action后调用这个方法那么父层View就不会再调用onInterceptTouchEvent了,也无法截获以后的action

        switch(event.getAction())
        {
            case MotionEvent.ACTION_DOWN:
                break;
            case MotionEvent.ACTION_MOVE:
                break;
            case MotionEvent.ACTION_UP:
                onActionUpEvent(event);
                this.getParent().requestDisallowInterceptTouchEvent(false);
                break;
            case MotionEvent.ACTION_CANCEL:
                this.getParent().requestDisallowInterceptTouchEvent(false);
                break;
        }
        return true;
    }

    private void onActionUpEvent(MotionEvent event)
    {
        boolean isValidTouch = validateTouch(event.getX(), event.getY());

        if(isValidTouch)
        {
            invalidate();
        }
    }

    //是否是有效的触摸范围
    private boolean validateTouch(float x, float y)
    {

        //曲线触摸区域
        for(int i = 0; i < scorePoints.size(); i++)
        {
            // dipToPx(8)乘以2为了适当增大触摸面积
            if(x > (scorePoints.get(i).x - dipToPx(8) * 2) && x < (scorePoints.get(i).x + dipToPx(8) * 2))
            {
                if(y > (scorePoints.get(i).y - dipToPx(8) * 2) && y < (scorePoints.get(i).y + dipToPx(8) * 2))
                {
                    selectMonth = i + 1;
                    return true;
                }
            }
        }

        //月份触摸区域
        //计算每个月份X坐标的中心点
        float monthTouchY = viewHeight * 0.7f - dipToPx(3);//减去dipToPx(3)增大触摸面积

        float newWith       = viewWith - (viewWith * 0.15f) * 2;//分隔线距离最左边和最右边的距离是0.15倍的viewWith
        float validTouchX[] = new float[monthText.length];
        for(int i = 0; i < monthText.length; i++)
        {
            validTouchX[i] = newWith * ((float) (i) / (monthCount - 1)) + (viewWith * 0.15f);
        }

        if(y > monthTouchY)
        {
            for(int i = 0; i < validTouchX.length; i++)
            {
                Log.v("ScoreTrend", "validateTouch: validTouchX:" + validTouchX[i]);
                if(x < validTouchX[i] + dipToPx(8) && x > validTouchX[i] - dipToPx(8))
                {
                    Log.v("ScoreTrend", "validateTouch: " + (i + 1));
                    selectMonth = i + 1;
                    return true;
                }
            }
        }

        return false;
    }


    //绘制折线穿过的点
    protected void drawPoint(Canvas canvas)
    {
        if(scorePoints == null)
        {
            return;
        }
        brokenPaint.setStrokeWidth(dipToPx(1));
        for(int i = 0; i < scorePoints.size(); i++)
        {
            brokenPaint.setColor(brokenLineColor);
            brokenPaint.setStyle(Paint.Style.STROKE);
            canvas.drawCircle(scorePoints.get(i).x, scorePoints.get(i).y, dipToPx(3), brokenPaint);
            brokenPaint.setColor(Color.WHITE);
            brokenPaint.setStyle(Paint.Style.FILL);
            if(i == selectMonth - 1)
            {
                brokenPaint.setColor(0xffd0f3f2);
                canvas.drawCircle(scorePoints.get(i).x, scorePoints.get(i).y, dipToPx(8f), brokenPaint);
                brokenPaint.setColor(0xff81dddb);
                canvas.drawCircle(scorePoints.get(i).x, scorePoints.get(i).y, dipToPx(5f), brokenPaint);

                //绘制浮动文本背景框
                drawFloatTextBackground(canvas, scorePoints.get(i).x, scorePoints.get(i).y - dipToPx(8f));

                textPaint.setColor(0xffffffff);
                //绘制浮动文字
                canvas.drawText(String.valueOf(score[i]), scorePoints.get(i).x, scorePoints.get(i).y - dipToPx(5f) - textSize, textPaint);
            }
            brokenPaint.setColor(0xffffffff);
            canvas.drawCircle(scorePoints.get(i).x, scorePoints.get(i).y, dipToPx(1.5f), brokenPaint);
            brokenPaint.setStyle(Paint.Style.STROKE);
            brokenPaint.setColor(brokenLineColor);
            canvas.drawCircle(scorePoints.get(i).x, scorePoints.get(i).y, dipToPx(2.5f), brokenPaint);
        }
    }

    //绘制月份的直线(包括刻度)
    private void drawMonthLine(Canvas canvas)
    {
        straightPaint.setStrokeWidth(dipToPx(1));
        canvas.drawLine(0, viewHeight * 0.7f, viewWith, viewHeight * 0.7f, straightPaint);

        float newWith = viewWith - (viewWith * 0.15f) * 2;//分隔线距离最左边和最右边的距离是0.15倍的viewWith
        float coordinateX;//分隔线X坐标
        for(int i = 0; i < monthCount; i++)
        {
            coordinateX = newWith * ((float) (i) / (monthCount - 1)) + (viewWith * 0.15f);
            canvas.drawLine(coordinateX, viewHeight * 0.7f, coordinateX, viewHeight * 0.7f + dipToPx(4), straightPaint);
        }
    }

    //绘制折线
    private void drawBrokenLine(Canvas canvas)
    {
        brokenPath.reset();
        brokenPaint.setColor(brokenLineColor);
        brokenPaint.setStyle(Paint.Style.STROKE);
        if(score.length == 0)
        {
            return;
        }
        Log.v("ScoreTrend", "drawBrokenLine: " + scorePoints.get(0));
        brokenPath.moveTo(scorePoints.get(0).x, scorePoints.get(0).y);
        for(int i = 0; i < scorePoints.size(); i++)
        {
            brokenPath.lineTo(scorePoints.get(i).x, scorePoints.get(i).y);
        }
        canvas.drawPath(brokenPath, brokenPaint);

    }

    //绘制文本
    private void drawText(Canvas canvas)
    {
        textPaint.setTextSize(dipToPx(12));
        textPaint.setColor(textNormalColor);

        canvas.drawText(String.valueOf(maxScore), viewWith * 0.1f - dipToPx(10), viewHeight * 0.15f + textSize * 0.25f, textPaint);
        canvas.drawText(String.valueOf(minScore), viewWith * 0.1f - dipToPx(10), viewHeight * 0.4f + textSize * 0.25f, textPaint);

        textPaint.setColor(0xff7c7c7c);

        float newWith = viewWith - (viewWith * 0.15f) * 2;//分隔线距离最左边和最右边的距离是0.15倍的viewWith
        float coordinateX;//分隔线X坐标
        textPaint.setTextSize(dipToPx(12));
        textPaint.setStyle(Paint.Style.FILL);
        textPaint.setColor(textNormalColor);
        textSize = (int) textPaint.getTextSize();
        for(int i = 0; i < monthText.length; i++)
        {
            coordinateX = newWith * ((float) (i) / (monthCount - 1)) + (viewWith * 0.15f);

            if(i == selectMonth - 1)
            {

                textPaint.setStyle(Paint.Style.STROKE);
                textPaint.setColor(brokenLineColor);
                RectF r2 = new RectF();
                r2.left = coordinateX - textSize - dipToPx(4);
                r2.top = viewHeight * 0.7f + dipToPx(4) + textSize / 2;
                r2.right = coordinateX + textSize + dipToPx(4);
                r2.bottom = viewHeight * 0.7f + dipToPx(4) + textSize + dipToPx(8);
                canvas.drawRoundRect(r2, 10, 10, textPaint);

            }
            //绘制月份
            canvas.drawText(monthText[i], coordinateX, viewHeight * 0.7f + dipToPx(4) + textSize + dipToPx(5), textPaint);

            textPaint.setColor(textNormalColor);

        }

    }

    //绘制显示浮动文字的背景
    private void drawFloatTextBackground(Canvas canvas, int x, int y)
    {
        brokenPath.reset();
        brokenPaint.setColor(brokenLineColor);
        brokenPaint.setStyle(Paint.Style.FILL);

        //P1
        Point point = new Point(x, y);
        brokenPath.moveTo(point.x, point.y);

        //P2
        point.x = point.x + dipToPx(5);
        point.y = point.y - dipToPx(5);
        brokenPath.lineTo(point.x, point.y);

        //P3
        point.x = point.x + dipToPx(12);
        brokenPath.lineTo(point.x, point.y);

        //P4
        point.y = point.y - dipToPx(17);
        brokenPath.lineTo(point.x, point.y);

        //P5
        point.x = point.x - dipToPx(34);
        brokenPath.lineTo(point.x, point.y);

        //P6
        point.y = point.y + dipToPx(17);
        brokenPath.lineTo(point.x, point.y);

        //P7
        point.x = point.x + dipToPx(12);
        brokenPath.lineTo(point.x, point.y);

        //最后一个点连接到第一个点
        brokenPath.lineTo(x, y);

        canvas.drawPath(brokenPath, brokenPaint);
    }

    /**
     * 画虚线
     *
     * @param canvas 画布
     * @param startX 起始点X坐标
     * @param startY 起始点Y坐标
     * @param stopX  终点X坐标
     * @param stopY  终点Y坐标
     */
    private void drawDottedLine(Canvas canvas, float startX, float startY, float stopX, float stopY)
    {
        dottedPaint.setPathEffect(new DashPathEffect(new float[]{20, 10}, 4));
        dottedPaint.setStrokeWidth(1);
        // 实例化路径
        Path mPath = new Path();
        mPath.reset();
        // 定义路径的起点
        mPath.moveTo(startX, startY);
        mPath.lineTo(stopX, stopY);
        canvas.drawPath(mPath, dottedPaint);

    }


    public int[] getScore()
    {
        return score;
    }

    public void setScore(int[] score)
    {
        this.score = score;
        initData();
    }

    public void setMaxScore(int maxScore)
    {
        this.maxScore = maxScore;
    }

    public void setMinScore(int minScore)
    {
        this.minScore = minScore;
    }

    /**
     * dip 转换成px
     *
     * @param dip
     * @return
     */
    private int dipToPx(float dip)
    {
        float density = getContext().getResources().getDisplayMetrics().density;
        return (int) (dip * density + 0.5f * (dip >= 0 ? 1 : -1));
    }
}

布局里面就可以用了

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.hsg.zhimascoredemo.MainActivity">
    <com.hsg.zhimascoredemo.ZhiMaScoreView
        android:layout_width="match_parent"
        android:layout_height="300dp"
        android:background="#ffffff"
        android:layout_alignParentBottom="true"/>
</RelativeLayout>

效果图

这里写图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
package com.example.task; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Paint.Style; import android.graphics.RectF; import android.util.AttributeSet; import android.view.View; public class CircleProgressBar extends View { private int maxProgress = 100; private int progress = 10; private int progressStrokeWidth = 20; // 圆所在的距形区域 RectF oval; Paint paint; public CircleProgressBar(Context context, AttributeSet attrs) { super(context, attrs); // TODO 自动生成的构造函数存根 oval = new RectF(); paint = new Paint(); } @Override protected void onDraw(Canvas canvas) { // TODO 自动生成的方法存根 super.onDraw(canvas); int width = this.getWidth(); int height = this.getHeight(); if (width != height) { int min = Math.min(width, height); width = min; height = min; } paint.setAntiAlias(true); // 设置笔为抗锯齿 paint.setColor(0xFFE2E2E2); // 设置笔颜色 canvas.drawColor(Color.TRANSPARENT); // 白色背景 paint.setStrokeWidth(progressStrokeWidth); // 线宽 paint.setStyle(Style.STROKE); oval.left = progressStrokeWidth / 2; // 左上角x oval.top = progressStrokeWidth / 2; // 左上角y oval.right = width - progressStrokeWidth / 2; // 左下角x oval.bottom = height - progressStrokeWidth / 2; // 右下角y canvas.drawArc(oval, -90, 360, false, paint); // 绘制白色圆圈,即进度条背景 // paint.setColor(Color.rgb(0x57, 0x87, 0xb6)); paint.setColor(0xFFFF4700); canvas.drawArc(oval, -90, ((float) progress / maxProgress) * 360, false, paint); // 绘制进度圆弧,这里是蓝色 paint.setStrokeWidth(1); String text = progress + ""; int textHeight = height / 4; paint.setTextSize(textHeight); int textWidth = (int) paint.measureText(text, 0, text.length()); paint.setStyle(Style.FILL); // canvas.drawText(text, width / 2 - textWidth / 2, height / 2 // + textHeight / 2, paint); } public int getMaxProgress() { return maxProgress; } public void setMaxProgress(int maxProgress) { this.maxProgress = maxProgress; } public void setProgress(int progress) { this.progress = progress; this.invalidate(); } /** * 非UI线程调用 */ public void setProgressNotInUiThread(int progress) { this.progress = progress; this.postInvalidate(); } } 上面的代码就是两个圆弧 package com.example.task; import android.app.Activity; import android.os.Bundle; import android.view.View; import android.view.View.OnClickListener; public class MainActivity extends Activity { CircleProgressBar completedView1; CircleProgressBar completedView2; CircleProgressBar completedView3; CircleProgressBar completedView4; CircleProgressBar completedView5; CircleProgressBar completedView6; CircleProgressBar completedView7; CircleProgressBar completedView8; CircleProgressBar completedView9; CircleProgressBar completedView10; CircleProgressBar completedView11; CircleProgressBar completedView12; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); completedView1 = (CircleProgressBar) findViewById(R.id.circleProgressBar1); completedView2 = (CircleProgressBar) findViewById(R.id.circleProgressBar2); completedView3 = (CircleProgressBar) findViewById(R.id.circleProgressBar3); completedView4 = (CircleProgressBar) findViewById(R.id.circleProgressBar4); completedView5 = (CircleProgressBar) findViewById(R.id.circleProgressBar5); completedView6 = (CircleProgressBar) findViewById(R.id.circleProgressBar6); completedView7 = (CircleProgressBar) findViewById(R.id.circleProgressBar7); completedView8 = (CircleProgressBar) findViewById(R.id.circleProgressBar8); completedView9 = (CircleProgressBar) findViewById(R.id.circleProgressBar9); completedView10 = (CircleProgressBar) findViewById(R.id.circleProgressBar10); completedView11 = (CircleProgressBar) findViewById(R.id.circleProgressBar11); completedView12 = (CircleProgressBar) findViewById(R.id.circleProgressBar12); this.findViewById(R.id.button_show).setOnClickListener( new OnClickListener() { private int i2 = 40; @Override public void onClick(View v) { // completedView1.setProgressNotInUiThread(i2 % 100); // completedView2.setProgressNotInUiThread(i2 % 100); // completedView3.setProgressNotInUiThread(i2 % 100); // completedView4.setProgressNotInUiThread(i2 % 100); // completedView5.setProgressNotInUiThread(i2 % 100); // completedView6.setProgressNotInUiThread(i2 % 100); // completedView7.setProgressNotInUiThread(i2 % 100); // completedView8.setProgressNotInUiThread(i2 % 100); // completedView9.setProgressNotInUiThread(i2 % 100); // completedView10.setProgressNotInUiThread(i2 % 100); // completedView11.setProgressNotInUiThread(i2 % 100); // completedView12.setProgressNotInUiThread(i2 % 100); // i2 += 10; new Thread() { public void run() { int i = 0; while (i <= i2) { completedView1.setProgressNotInUiThread(i); completedView2.setProgressNotInUiThread(i); completedView3.setProgressNotInUiThread(i); completedView4.setProgressNotInUiThread(i); completedView5.setProgressNotInUiThread(i); completedView6.setProgressNotInUiThread(i); completedView7.setProgressNotInUiThread(i); completedView8.setProgressNotInUiThread(i); completedView9.setProgressNotInUiThread(i); completedView10.setProgressNotInUiThread(i); completedView11.setProgressNotInUiThread(i); completedView12.setProgressNotInUiThread(i); i++; try { sleep(10); } catch (InterruptedException e) { // TODO 自动生成的 catch 块 e.printStackTrace(); } } } }.start(); i2 += 10; } }); } } 上面的主活动界面使用线程

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值