自定义View详解

2018-09-17  本文已影响0人  kjy_112233

一、自定义View

(1)View的绘制流程

(2)坐标系

(3)自定义属性

    <declare-styleable name="study_view">
        <attr name="text" format="string" />
        <attr name="textColor" format="color" />
        <attr name="textSize" format="dimension" />
    </declare-styleable>
public class StudyView extends View {

    private Rect rect;
    private String text;
    private Paint paint;

    //在Java代码中new时调用
    public StudyView(Context context) {
        super(context);
    }

    //在xml布局文件中使用时自动调用
    public StudyView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        initAttr(context, attrs);
    }

    private void initAttr(Context context, AttributeSet attrs) {
        //获取自定义属性的值
        @SuppressLint("CustomViewStyleable")
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.study_view);
        text = typedArray.getString(R.styleable.study_view_text);
        int textColor = typedArray.getColor(R.styleable.study_view_textColor, Color.BLACK);
        float textSize = typedArray.getDimension(R.styleable.study_view_textSize, 100);
        typedArray.recycle();//回收typedArray
        //设置绘制文字画笔
        paint = new Paint();
        paint.setColor(textColor);
        paint.setTextSize(textSize);
        //获得绘制文本的宽和高
        rect = new Rect();
        assert text != null;
        paint.getTextBounds(text, 0, text.length(), rect);
    }


    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //绘制文字
        canvas.drawText(text, getWidth() / 2 - rect.width() / 2, getHeight() / 2 + rect.height() / 2, paint);
    }
}

(4)通过onMeasure设置View的实际宽高

    //MeasureSpec是View的静态内部类。用来描述父控件对子控件尺寸的约束
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        //保存测量宽度和测量高度
        setMeasuredDimension(measureWidth(widthMeasureSpec), measureHeight(heightMeasureSpec));
    }


    /**
     * @return 控件的宽度
     */
    private int measureWidth(int widthMeasureSpec) {
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);//获取宽的模式
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);//获取宽的尺寸
        if (widthMode == MeasureSpec.EXACTLY) {//match_parent、具体值
            return widthSize;//控件的宽度
        } else {//wrap_content
            float textWidth = rect.width();//文本的宽度
            return (int) (getPaddingLeft() + textWidth + getPaddingRight());//控件的宽度
        }
    }

    /**
     * @return 控件的高度
     */
    private int measureHeight(int heightMeasureSpec) {
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);//获取高的模式
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);//获取高的尺寸
        if (heightMode == MeasureSpec.EXACTLY) {//match_parent、具体值
            return heightSize;
        } else {//wrap_content
            float textHeight = rect.height();//文本的高度
            return (int) (getPaddingTop() + textHeight + getPaddingBottom());
        }
    }

(5)实现View自动换行

public class StudyView extends View {

    private String text;
    private int textSize;
    private int textColor;

    private Paint paint;
    private Rect textBranch;
    private int drawTextHeight;
    private List<String> textList = new ArrayList<>();

    //在Java代码中new时调用
    public StudyView(Context context) {
        this(context, null);
    }

    //在xml布局文件中使用时自动调用
    public StudyView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        initAttr(context, attrs);
        init();
    }

    private void initAttr(Context context, AttributeSet attrs) {
        //获取自定义属性的值
        @SuppressLint("CustomViewStyleable")
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.study_view);
        text = typedArray.getString(R.styleable.study_view_text);
        textColor = typedArray.getColor(R.styleable.study_view_textColor, Color.BLACK);
        textSize = (int) typedArray.getDimension(R.styleable.study_view_textSize, 100);
        typedArray.recycle();//回收typedArray
    }

    private void init() {
        //初始化画笔
        paint = new Paint();
        paint.setColor(textColor);
        paint.setTextSize(textSize);
        paint.setAntiAlias(true);
        paint.setStrokeWidth(1);

        //获得绘制文本的宽高
        textBranch = new Rect();
        paint.getTextBounds(text, 0, text.length(), textBranch);

        //计算各线在位置
        Paint.FontMetrics fontMetrics = paint.getFontMetrics();
        //每行文字的绘制高度,建议低于基线的距离 - 建议距基线以上的距离
        drawTextHeight = (int) (fontMetrics.descent - fontMetrics.ascent);
    }


    //MeasureSpec是View的静态内部类。用来描述父控件对子控件尺寸的约束
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        //设置文本自动分行
        setBranch(widthMeasureSpec);
        setMeasuredDimension(measureWidth(widthMeasureSpec), measureHeight(heightMeasureSpec));
    }

    private void setBranch(int widthMeasureSpec) {
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);//获取宽的尺寸
        if (textList.size() == 0) {
            //显示文本最大宽度
            float specMaxWidth = widthSize - getPaddingLeft() - getPaddingRight();
            //实际显示的行数
            int lineNum;
            if (textBranch.width() > specMaxWidth) {
                //获取带小数的行数
                float lineNumF = textBranch.width() * 1.0f / specMaxWidth;
                //如果有小数就进1
                if ((lineNumF + "").contains(".")) {
                    lineNum = (int) (lineNumF + 0.5);
                } else {
                    lineNum = (int) lineNumF;
                }
                //每行展示文字的长度
                int lineLength = (int) (text.length() / lineNumF);
                for (int i = 0; i < lineNum; i++) {
                    String lineStr;
                    //判断是否可以一行显示
                    if (text.length() < lineLength) {
                        lineStr = text;
                    } else {
                        lineStr = text.substring(0, lineLength);
                    }
                    textList.add(lineStr);
                    //重新赋值text
                    if (!TextUtils.isEmpty(text)) {
                        if (text.length() > lineLength) {
                            text = text.substring(lineLength);
                        }
                    } else {
                        break;
                    }
                }
            } else {
                textList.add(text);
            }
        }
    }

    /**
     * @return 控件的宽度
     */
    private int measureWidth(int widthMeasureSpec) {
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);//获取宽的模式
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);//获取宽的尺寸
        if (widthMode == MeasureSpec.EXACTLY) {//match_parent、具体值
            return widthSize;//控件的宽度
        } else {//wrap_content
            //获取文本的宽度
            float textWidth;
            if (textList.size() > 1) {
                textWidth = widthSize;
            } else {
                textWidth = textBranch.width();
            }
            return (int) (getPaddingLeft() + textWidth + getPaddingRight());//控件的宽度
        }
    }

    /**
     * @return 控件的高度
     */
    private int measureHeight(int heightMeasureSpec) {
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);//获取高的模式
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);//获取高的尺寸
        if (heightMode == MeasureSpec.EXACTLY) {//match_parent、具体值
            return heightSize;
        } else {//wrap_content
            //获取文本的高度
            float textHeight = drawTextHeight * textList.size();
            return (int) (getPaddingTop() + textHeight + getPaddingBottom());
        }
    }


    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        for (int i = 0; i < textList.size(); i++) {
            paint.getTextBounds(textList.get(i), 0, textList.get(i).length(), textBranch);
            canvas.drawText(textList.get(i), getPaddingLeft(), (getPaddingTop() + drawTextHeight * (i + 1)), paint);
        }

    }
}

(6)实现自定义饼状图

public class SectorView extends View {
    private int[] colors = {Color.RED, Color.BLACK, Color.CYAN, Color.MAGENTA, Color.GREEN, Color.BLUE};

    private Paint paint;
    private RectF rectF;

    private float radius;
    private float startC;

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

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

    private void init() {
        paint = new Paint();
        paint.setStyle(Paint.Style.FILL);
        paint.setTextSize(30);
        rectF = new RectF();
    }

    private float centerX;
    private float centerY;

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //获取设置总数

        //获取圆心坐标
        centerX = getPivotX();
        centerY = getPivotY();

        //获取圆的半径
        if (radius == 0)
            radius = (float) (Math.min(getWidth(), getHeight()) / 2) / 2;
        //设置矩形区域
        rectF.set(centerX - radius, centerY - radius, centerX + radius, centerY + radius);

        //绘制每个扇形
        for (int color : colors) {
            //当前扇形的颜色
            paint.setColor(color);
            float sweep = 360.0f * (1.0f / colors.length);
            //绘制圆弧
            canvas.drawArc(rectF, startC, sweep, true, paint);
            //计算文字的中心点位置
            float textAngle = startC + sweep / 2;
            float x = (float) (centerX + radius / 2 * Math.cos(textAngle * Math.PI / 180));
            float y = (float) (centerY + radius / 2 * Math.sin(textAngle * Math.PI / 180));
            paint.setColor(Color.WHITE);
            //获取绘制的文字
            String text = (int) (sweep / 360.0f * 100) + "%";
            //根据角度设置字体显示位置
            if (textAngle >= 0 && textAngle < 90)
                canvas.drawText(text, x, y + paint.getTextSize(), paint);
            else if (textAngle >= 90 && textAngle < 180)
                canvas.drawText(text, x - paint.getTextSize(), y + paint.getTextSize(), paint);
            else if (textAngle >= 180 && textAngle <= 270)
                canvas.drawText(text, x - paint.getTextSize(), y, paint);
            else if (textAngle > 270 && textAngle < 360)
                canvas.drawText(text, x, y, paint);
            //下一个扇形的开始角度
            startC += sweep;
        }
    }
}
public class SectorView extends View {
    private float[] endAngles = new float[6];
    private float[] startAngles = new float[6];
    private int[] colors = {Color.RED, Color.BLACK, Color.CYAN, Color.MAGENTA, Color.GREEN, Color.BLUE};

    private Paint paint;
    private RectF rectF;

    private int select = -1;
    private float radius;

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

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

    private void init() {
        paint = new Paint();
        paint.setStyle(Paint.Style.FILL);
        paint.setTextSize(30);
        rectF = new RectF();
    }

    private float centerX;
    private float centerY;

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //饼状图的起始角度
        float startC = 0;
        //获取圆心坐标
        centerX = getPivotX();
        centerY = getPivotY();
        //获取圆的半径
        if (radius == 0)
            radius = (float) (Math.min(getWidth(), getHeight()) / 2) / 2;
        //绘制每个扇形
        for (int i = 0; i < colors.length; i++) {
            //设置矩形区域
            rectF.set(centerX - radius, centerY - radius, centerX + radius, centerY + radius);
            //当前扇形的颜色
            paint.setColor(colors[i]);
            //当前扇形的角度
            float sweep = 360.0f * (1.0f / colors.length);
            //保存起始角度
            startAngles[i] = startC;
            //保存结束角度
            endAngles[i] = startC + sweep;
            //计算文字的中心点位置
            float textAngle = startC + sweep / 2;
            float x = (float) (centerX + radius / 2 * Math.cos(textAngle * Math.PI / 180));
            float y = (float) (centerY + radius / 2 * Math.sin(textAngle * Math.PI / 180));
            //获取绘制的文字
            String text = (int) (sweep / 360.0f * 100) + "%";
            //j计算文字的起始点位
            if (textAngle >= 0 && textAngle < 90) {
                if (select == i) {
                    int top = (int) (Math.sin(Math.toRadians(textAngle)) * 25);
                    int left = (int) (Math.cos(Math.toRadians(textAngle)) * 25);
                    rectF.left += left;
                    rectF.right += left;
                    rectF.top += top;
                    rectF.bottom += top;
                    x = (float) (centerX + radius / 2 * Math.cos(textAngle * Math.PI / 180)) + left;
                    y = (float) (centerY + radius / 2 * Math.sin(textAngle * Math.PI / 180)) + top;
                } else {
                    x = (float) (centerX + radius / 2 * Math.cos(textAngle * Math.PI / 180));
                    y = (float) (centerY + radius / 2 * Math.sin(textAngle * Math.PI / 180));
                }
                y += paint.getTextSize();
            } else if (textAngle >= 90 && textAngle < 180) {
                if (select == i) {
                    int top = (int) (Math.sin(Math.toRadians(180 - textAngle)) * 25);
                    int left = (int) (Math.cos(Math.toRadians(180 - textAngle)) * 25);
                    rectF.left -= left;
                    rectF.right -= left;
                    rectF.top += top;
                    rectF.bottom += top;
                    x = (float) (centerX + radius / 2 * Math.cos(textAngle * Math.PI / 180)) - left;
                    y = (float) (centerY + radius / 2 * Math.sin(textAngle * Math.PI / 180)) + top;
                } else {
                    x = (float) (centerX + radius / 2 * Math.cos(textAngle * Math.PI / 180));
                    y = (float) (centerY + radius / 2 * Math.sin(textAngle * Math.PI / 180));
                }
                x -= paint.getTextSize();
                y += paint.getTextSize();
            } else if (textAngle >= 180 && textAngle <= 270) {
                if (select == i) {
                    int top = (int) (Math.sin(Math.toRadians(270 - textAngle)) * 25);
                    int left = (int) (Math.cos(Math.toRadians(270 - textAngle)) * 25);
                    rectF.left -= left;
                    rectF.right -= left;
                    rectF.top -= top;
                    rectF.bottom -= top;
                    //获取扇形弧度的中心点坐标
                    x = (float) (centerX + radius / 2 * Math.cos(textAngle * Math.PI / 180)) - left;
                    y = (float) (centerY + radius / 2 * Math.sin(textAngle * Math.PI / 180)) - top;
                } else {
                    x = (float) (centerX + radius / 2 * Math.cos(textAngle * Math.PI / 180));
                    y = (float) (centerY + radius / 2 * Math.sin(textAngle * Math.PI / 180));
                }
                x -= paint.getTextSize();
            } else if (textAngle > 270 && textAngle < 360) {
                if (select == i) {
                    int top = (int) (Math.sin(Math.toRadians(360 - textAngle)) * 25);
                    int left = (int) (Math.cos(Math.toRadians(360 - textAngle)) * 25);
                    rectF.left += left;
                    rectF.right += left;
                    rectF.top -= top;
                    rectF.bottom -= top;
                    //获取扇形弧度的中心点坐标
                    x = (float) (centerX + radius / 2 * Math.cos(textAngle * Math.PI / 180)) + left;
                    y = (float) (centerY + radius / 2 * Math.sin(textAngle * Math.PI / 180)) - top;
                } else {
                    x = (float) (centerX + radius / 2 * Math.cos(textAngle * Math.PI / 180));
                    y = (float) (centerY + radius / 2 * Math.sin(textAngle * Math.PI / 180));
                }
            }
            //绘制圆弧
            canvas.drawArc(rectF, startC, sweep, true, paint);
            paint.setColor(Color.WHITE);
            //绘制百分比文字
            canvas.drawText(text, x, y, paint);
            //下一个扇形的开始角度
            startC += sweep;
        }
    }

    @SuppressLint("ClickableViewAccessibility")
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (event.getAction() == MotionEvent.ACTION_DOWN) {
            //获取点击位置的坐标
            float x = event.getX();
            float y = event.getY();
            //获取点击角度
            float angle = 0;
            //判断当前点击位置在第几象限
            if (x >= centerX && y >= centerY)
                angle = (float) (Math.atan((y - centerY) / (x - centerX)) * 180 / Math.PI);
            else if (x <= centerX && y >= centerY)
                angle = (float) (Math.atan((centerX - x) / (y - centerY)) * 180 / Math.PI + 90);
            else if (x <= centerX && y <= centerY)
                angle = (float) (Math.atan((centerY - y) / (centerX - x)) * 180 / Math.PI + 180);
            else if (x >= centerX && y <= centerY)
                angle = (float) (Math.atan((x - centerX) / (centerY - y)) * 180 / Math.PI + 270);
            for (int i = 0; i < startAngles.length; i++) {
                if (startAngles[i] <= angle && endAngles[i] >= angle) {
                    select = i;
                    invalidate();
                    return true;
                }
            }
            return true;
        }
        return super.onTouchEvent(event);
    }
}
上一篇下一篇

猜你喜欢

热点阅读