Android 自定义View(画线循迹,雷达图+动画实现)

自定义首先接触到的时候觉得很有兴趣,便学习了一下


  • HenCode自定义View教程,每个章节部分还会有配套的联系项目,难以理解的地方还有视频介绍,很用心的教程了
    • HenCoder Android 开发进阶: 自定义 View 1-1 绘制基础
    • HenCoder Android 开发进阶: 自定义 View 1-2 Paint 详解
    • HenCoder Android 开发进阶:自定义 View 1-3 drawText() 文字的绘制
    • HenCoder Android 开发进阶:自定义 View 1-4 Canvas 对绘制的辅助 clipXXX() 和 Matrix
    • HenCoder Android 开发进阶:自定义 View 1-5 绘制顺序
    • HenCoder Android 自定义 View 1-6:属性动画 Property Animation(上手篇)
    • 绘制HenCoder Android 自定义 View 1-7:属性动画 Property Animation(进阶篇)
    • HenCoder Android 自定义 View 1-8 硬件加速
    • HenCoder Android UI 部分 2-1 布局基础
    • HenCoder Android UI 部分 2-2 全新定义 View 的尺寸
    • HenCoder Android UI 部分 2-3 定制 Layout 的内部布局
    • HenCoder 3-1 触摸反馈

  • GcsSloop大神的自定义View教程也非常不错,覆盖前面且详细

    基础篇

    • 安卓自定义View基础 - 坐标系
    • 安卓自定义View基础 - 角度弧度
    • 安卓自定义View基础 - 颜色

    进阶篇

    • 安卓自定义View进阶 - 分类和流程
    • 安卓自定义View进阶 - Canvas之绘制图形
    • 安卓自定义View进阶 - Canvas之画布操作
    • 安卓自定义View进阶 - Canvas之图片文字
    • 安卓自定义View进阶 - Path之基本操作
    • 安卓自定义View进阶 - Path之贝塞尔曲线
    • 安卓自定义View进阶 - Path完结篇
    • 安卓自定义View进阶 - PathMeasure
    • 安卓自定义View进阶 - Matrix原理
    • 安卓自定义View进阶 - Matrix详解
    • 安卓自定义View进阶 - Matrix Camera
    • 安卓自定义View进阶 - 事件分发机制原理
    • 安卓自定义View进阶 - 事件分发机制详解
    • 安卓自定义View进阶 - MotionEvent详解
    • 安卓自定义View进阶 - 特殊控件的事件处理方案
    • 安卓自定义View进阶 - 多点触控详解
    • 安卓自定义View进阶 - 手势检测(GestureDecetor)
    • 安卓自定义View进阶 - 缩放手势检测(ScaleGestureDecetor)

学习完以上的教程后,还是需要自己动手来实践一下。

1.画线循迹

  • 1.画出轨迹 :
    先试着完成一个触控画线的功能,就是在屏幕上触控画线,画线需要一个Paint个一个Path,然后还要重写onToucheEvent()记录移动的位置,再连线即可。
    onTouchEvent()
public boolean onTouchEvent(MotionEvent event) {

        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                //设置起始点
                mStartX = event.getX();
                mStartY = event.getY();
                //清空原有的轨道
                mMovePath.reset();
                //移动到起始点
                mMovePath.moveTo(mStartX, mStartY);
                return true;
            case MotionEvent.ACTION_MOVE:
                final float endX = (mStartX + event.getX()) / 2;
                final float endY = (mStartY + event.getY()) / 2;
                //画出轨道曲线,quad是二次曲线
                mMovePath.quadTo(mStartX, mStartY, endX, endY);
                mStartX = event.getX();
                mStartY = event.getY();
                //重绘
                invalidate();
                return true;
        }
        return super.onTouchEvent(event);
    }

然后在onDraw()中调用drawPath()即可。

  • 2.跟随轨迹移动
private void drawMove(Canvas canvas) {
        pathMeasure.setPath(mMovePath, false);
        if (isMove) {
            //计算图片的旋转角度,tan是当前点切线值,根据切线值计算角度
            float degree = (float) (Math.atan2(tan[1], tan[0]) * 180.0 / Math.PI);
            //旋转对应的角度
            canvas.rotate(degree, pos[0], pos[1]);
            //设置中心点在运行轨道上
            mRect.set(pos[0] - mRectWidth, pos[1] - mRectWidth, pos[0] + mRectWidth, pos[1] + mRectWidth);
            //绘制draw
            if (mDrawable == null) {
                canvas.drawRect(mRect, mCarPaint);
            } else {
                canvas.drawBitmap(mBitmap, null, mRect, mPaint);
            }
        }
    }

Android 自定义View(画线循迹,雷达图+动画实现)_第1张图片

随意画一条轨道。点击启动开始动画
Android 自定义View(画线循迹,雷达图+动画实现)_第2张图片
正确的完成了轨道。

具体参考Github:DiyViewPracticeDemo

2.雷达图

雷达图的绘制我分为三部分,雷达网的绘制,覆盖区域的绘制,边界文字描述绘制
雷达网的绘制

	/**
     * 绘制雷达图。
     */
    private void drawRader(Canvas canvas) {
        radarPaint.setPathEffect(null);
        float gap = radius / (dataCount - 1);
        //雷达网的绘制
        for (int i = 1; i < dataCount; i++) {
            //不同环的半径,dataCount-1个环
            float currentR = gap * i;
            radarPath.reset();
            for (int j = 0; j < dataCount; j++) {
                //如果是0,也就移到右边的起始点
                if (j == 0) {
                    radarPath.moveTo(centerX + currentR, centerY);
                } else {
                    //其他情况得到60°,120°,180°,240°,300°的cos和sin值,利用cos值得到x轴坐标,sin值得到y轴坐标。
                    radarPath.lineTo((float) (centerX + currentR * Math.cos(angle * j)), (float) (centerY + currentR * Math.sin(angle * j)));
                }
            }
            //闭合,终点连上起点
            radarPath.close();
            canvas.drawPath(radarPath, radarPaint);
        }

        //绘制中心到边界点的虚线
        radarPaint.setPathEffect(dashPathEffect);
        for (int i = 0; i < dataCount; i++) {
            radarPath.reset();
            radarPath.moveTo(centerX, centerY);
            radarPath.lineTo((float) (centerX + radius * Math.cos(angle * i)), (float) (centerY + radius * Math.sin(angle * i)));
            canvas.drawPath(radarPath, radarPaint);
        }
    }

覆盖区域绘制

//绘制覆盖区域
    private void drawRegion(Canvas canvas) {
        int sum = 0;
        for (int i = 0; i < dataCount; i++) {
            double percent = drawed[i] / MAX_Value;
            sum += drawed[i];

            //得到对应分值的对应的x,y坐标
            float x = (float) (centerX + radius * Math.cos(angle * i) * percent);
            float y = (float) (centerY + radius * Math.sin(angle * i) * percent);

            //绘制过程同雷达网的绘制过程
            if (i == 0) {
                regionPath.moveTo(x, centerY);
            } else {
                regionPath.lineTo(x, y);
            }

            //为边界点绘制小圆点
            canvas.drawCircle(x, y, 5, valuePaint);
        }
        //闭合
        regionPath.close();
        canvas.drawPath(regionPath, valuePaint);

        valuePaint.setAlpha(255 / 2);
        valuePaint.setStyle(Paint.Style.FILL_AND_STROKE);
        canvas.drawPath(regionPath, valuePaint);

        canvas.save();
        String centerText = "能力值:%d";
        canvas.drawText(String.format(Locale.getDefault(), centerText, sum), centerX - centerTextPaint.measureText(centerText) / 2, centerY, centerTextPaint);
        canvas.restore();
    }

边界文字描述绘制

//绘制边界点的文字描述
    private void drawText(Canvas canvas) {
        float fontBaseLine = fontMetrics.descent - fontMetrics.ascent;
        for (int i = 0; i < dataCount; i++) {
            //x,y也就是边界点的坐标
            float x = (float) (centerX + (radius + fontBaseLine / 2) * Math.cos(angle * i));
            float y = (float) (centerY + (radius + fontBaseLine / 2) * Math.sin(angle * i));

            //位于左边的需要左移文字长度大小
            if (angle * i >= 0 && angle * i < Math.PI / 2) {//0°<=角度<90°
                canvas.drawText(strings[i], x, y, textPaint);
            } else if (angle * i >= Math.PI / 2 && angle * i < Math.PI) {//90°<=角度<180°
                float dis = textPaint.measureText(strings[i]);//文本长度
                canvas.drawText(strings[i], x - dis, y, textPaint);
            } else if (angle * i >= Math.PI && angle * i < 3 * Math.PI / 2) {//180°<=角度<270°
                float dis = textPaint.measureText(strings[i]);//文本长度
                canvas.drawText(strings[i], x - dis, y, textPaint);
            } else if (angle * i >= 3 * Math.PI / 2 && angle * i < Math.PI * 2) {//270°<=角度<=360°
                canvas.drawText(strings[i], x, y, textPaint);
            }
        }
    }

效果如下图:
Android 自定义View(画线循迹,雷达图+动画实现)_第3张图片

具体参考Github:DiyViewPracticeDemo

你可能感兴趣的:(学习)