1

#过年不停更#HarmonyOS-JAVA之手把手教你绘制冰墩墩

 2 years ago
source link: https://blog.51cto.com/harmonyos/5020247
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

#过年不停更#HarmonyOS-JAVA之手把手教你绘制冰墩墩

推荐 原创

 春节不停更,此文正在参加「星光计划-春节更帖活动」

作者:彭为杰

冰墩墩买不到,金墩墩买不起,毛线墩墩不会勾,橡皮泥墩墩不会捏,雪墩墩不会堆,但我们会用程序绘制一个冰墩墩;

授人以鱼,不如授人以渔。今天手把手教大家来一个冰墩墩!

#过年不停更#HarmonyOS-JAVA之手把手教你绘制冰墩墩_贝塞尔曲线

1 建立坐标系

工欲善其事,必先利其器,有的好的工具,冰墩墩也好,飞机坦克大炮都能绘制出来;

#过年不停更#HarmonyOS-JAVA之手把手教你绘制冰墩墩_开鸿HarmonyOS_02

计算出中心点的问题,这里细节在于中线点和辅助线对齐保证美观

@Override
public boolean onEstimateSize(int widthEstimateConfig, int heightEstimateConfig) {
    int componentWidth = EstimateSpec.getSize(widthEstimateConfig);
    int componentHeight = EstimateSpec.getSize(heightEstimateConfig);
    this.width = componentWidth;
    this.height = componentHeight;
    centerX = this.width / 2;
    centerY = this.height / 2;

    // 保证辅助线取整
    centerX = ((int) (centerX / 50)) * 50;
    centerY = ((int) (centerY / 50)) * 50;

    Logger.d("width:" + width);
    Logger.d("height:" + height);
    Logger.d("centerX:" + centerX);
    Logger.d("centerY:" + centerY);
    recordBg();//初始化时录制坐标系和网格

    setEstimatedSize(
        EstimateSpec.getChildSizeWithMode(componentWidth, componentWidth, EstimateSpec.PRECISE),
        EstimateSpec.getChildSizeWithMode(componentHeight, componentHeight, EstimateSpec.PRECISE)
    );
    return true;
}

初始化坐标系

private Point mCoo;//坐标系
private Picture mPicture;//坐标系和网格的Canvas元件

/**
 * 初始化时录制坐标系和网格
 */
private void recordBg() {
    //准备屏幕尺寸
    Point winSize = new Point(width, height);
    mCoo = new Point(centerX, centerY);
    Paint gridPaint = new Paint();
    mPicture = new Picture();
    Canvas recordCanvas = mPicture.beginRecording(winSize.getPointXToInt(), winSize.getPointYToInt());
    //绘制辅助网格
    HelpDraw2.drawGrid(recordCanvas, winSize, gridPaint);
    //绘制坐标系
    HelpDraw2.drawCoo(recordCanvas, mCoo, winSize, gridPaint);
    mPicture.endRecording();
}

onDraw方法中绘制坐标系

canvas.drawPicture(mPicture);

画布辅助类

/**
 * 辅助画布
 *
 * @since 2022-02-09
 */
public class HelpDraw2 {

    /**
     * 绘制网格
     */
    public static void drawGrid(Canvas recordCanvas, Point winSize, Paint paint) {
        //初始化网格画笔
        paint.setStrokeWidth(2);
        paint.setColor(Color.GRAY);
        paint.setStyle(Paint.Style.STROKE_STYLE);
        //设置虚线效果new float[]{可见长度, 不可见长度},偏移值
        paint.setPathEffect(new PathEffect(new float[]{10, 5}, 0));
        recordCanvas.drawPath(HelpPath.gridPath(50, winSize), paint);
    }


    /**
     *   绘制坐标系
     * @param recording 画布
     * @param coo 坐标系原点
     * @param winSize 屏幕尺寸
     * @param paint 画笔
     */
    public static void drawCoo(Canvas recording, Point coo, Point winSize, Paint paint) {
         //初始化网格画笔
        paint.setStrokeWidth(4);
        paint.setColor(Color.BLACK);
        paint.setStyle(Paint.Style.STROKE_STYLE);
        //设置虚线效果new float[]{可见长度, 不可见长度},偏移值
        paint.setPathEffect(null);

        //绘制直线
        recording.drawPath(HelpPath.cooPath(coo, winSize), paint);
        //左箭头
        recording.drawLine(winSize.getPointX(), coo.getPointY(), winSize.getPointX() - 40, coo.getPointY() - 20, paint);
        recording.drawLine(winSize.getPointX(), coo.getPointY(), winSize.getPointX() - 40, coo.getPointY() + 20, paint);
        //下箭头
        recording.drawLine(coo.getPointX(), winSize.getPointY(), coo.getPointX() - 20, winSize.getPointY() - 40, paint);
        recording.drawLine(coo.getPointX(), winSize.getPointY(), coo.getPointX() + 20, winSize.getPointY() - 40, paint);
        //为坐标系绘制文字
        drawText4Coo(recording, coo, winSize, paint);
    }



    /**
     * 为坐标系绘制文字
     *
     * @param canvas 画布
     * @param coo 坐标系原点
     * @param winSize 屏幕尺寸
     * @param paint 画笔
     */
    private static void drawText4Coo(Canvas canvas, Point coo, Point winSize, Paint paint) {
        //绘制文字
        paint.setTextSize(50);
        canvas.drawText(paint, "x", winSize.getPointX() - 60, coo.getPointY() - 40);
        canvas.drawText(paint, "y", coo.getPointX() - 40, winSize.getPointY() - 60);
        paint.setTextSize(25);
        //X正轴文字
        for (int i = 1; i < (winSize.getPointX() - coo.getPointX()) / 50; i++) {
            paint.setStrokeWidth(2);
            canvas.drawText(paint, 100 * i + "", coo.getPointX() - 20 + 100 * i, coo.getPointY() + 40);
            paint.setStrokeWidth(5);
            canvas.drawLine(coo.getPointX() + 100 * i, coo.getPointY(), coo.getPointX() + 100 * i, coo.getPointY() - 10, paint);
        }

        //X负轴文字
        for (int i = 1; i < coo.getPointX() / 50; i++) {
            paint.setStrokeWidth(2);
            canvas.drawText(paint, -100 * i + "", coo.getPointX() - 20 - 100 * i, coo.getPointY() + 40);
            paint.setStrokeWidth(5);
            canvas.drawLine(coo.getPointX() - 100 * i, coo.getPointY(), coo.getPointX() - 100 * i, coo.getPointY() - 10, paint);
        }

        //y正轴文字
        for (int i = 1; i < (winSize.getPointY() - coo.getPointY()) / 50; i++) {
            paint.setStrokeWidth(2);
            canvas.drawText(paint, 100 * i + "", coo.getPointX() + 20, coo.getPointY() + 10 + 100 * i);
            paint.setStrokeWidth(5);
            canvas.drawLine(coo.getPointX(), coo.getPointY() + 100 * i, coo.getPointX() + 10, coo.getPointY() + 100 * i, paint);
        }

        //y负轴文字
        for (int i = 1; i < coo.getPointY() / 50; i++) {
            paint.setStrokeWidth(2);
            canvas.drawText(paint, -100 * i + "", coo.getPointX() + 20, coo.getPointY() + 10 - 100 * i);
            paint.setStrokeWidth(5);
            canvas.drawLine(coo.getPointX(), coo.getPointY() - 100 * i, coo.getPointX() + 10, coo.getPointY() - 100 * i, paint);
        }
    }


}

2 绘制底图

我们选一张喜欢的冰墩墩作为底图,画笔设置透明度50%;

#过年不停更#HarmonyOS-JAVA之手把手教你绘制冰墩墩_开鸿HarmonyOS_03

mPixelMapPaint = new Paint();
mPixelMapPaint.setAlpha(0.5f);

绘制图片方法

private void drawPixelMap(Canvas canvas) {
    Optional<PixelMap> image = PixelMapUtil.getPixelMapFromResource(getContext(), ResourceTable.Media_bdd);
    if (image.isPresent()) {
        PixelMap pixelMap = image.get();
        int pw = pixelMap.getImageInfo().size.width;
        int ph = pixelMap.getImageInfo().size.height;
        int offX = centerX - pw / 2;
        int offY = centerY - ph / 2;
        Logger.d("pw:" + pw + " ;ph:" + ph);
        RectFloat pixelRectFloat = new RectFloat(offX, offY, pw + offX, ph + offY);
        canvas.drawPixelMapHolderRect(new PixelMapHolder(pixelMap), pixelRectFloat, mPixelMapPaint);
    }
}

onDraw方法中绘制图片

drawPixelMap(canvas);

3 绘制冰墩墩轮廓

贝塞尔曲线的了解和认识

1.简单认识:(图来源网络)

#过年不停更#HarmonyOS-JAVA之手把手教你绘制冰墩墩_冰墩墩_04

2.二阶贝塞尔曲线控制点寻找示例:

确定起点,终点,和任意选一个控制点

//起点
private Point start = new Point(0, 0);
//终点
private Point end = new Point(400, 0);
//控制点
private Point control = new Point(200, 200);

贝塞尔曲线

@Override
public void onDraw(Component component, Canvas canvas) {
    canvas.save();
    canvas.translate(mCoo.getPointX(), mCoo.getPointY());
    drawHelpElement(canvas);//绘制辅助工具--控制点和基准选
    // 绘制贝塞尔曲线
    mBezierPath.moveTo(start.getPointX(), start.getPointY());
    mBezierPath.quadTo(control.getPointX(), control.getPointY(), end.getPointX(), end.getPointY());
    canvas.drawPath(mBezierPath, mPaint);
    mBezierPath.reset();//清空mBezierPath
    canvas.restore();
    canvas.drawPicture(mPicture);
}

//绘制辅助工具--控制点和基准选
private void drawHelpElement(Canvas canvas) {
    // 绘制数据点和控制点
    mHelpPaint.setColor(new Color(0x8820ECE2));
    mHelpPaint.setStrokeWidth(20);
    canvas.drawPoint(start.getPointX(), start.getPointY(), mHelpPaint);
    canvas.drawPoint(end.getPointX(), end.getPointY(), mHelpPaint);
    canvas.drawPoint(control.getPointX(), control.getPointY(), mHelpPaint);
    // 绘制辅助线
    resetHelpPaint();
    canvas.drawLine(start.getPointX(), start.getPointY(), control.getPointX(), control.getPointY(), mHelpPaint);
    canvas.drawLine(end.getPointX(), end.getPointY(), control.getPointX(), control.getPointY(), mHelpPaint);

}

寻找控制点方法,这个可以找起点,终点,控制点

@Override
public boolean onTouchEvent(Component component, TouchEvent touchEvent) {
    // 根据触摸位置更新控制点,并提示重绘
    MmiPoint point = touchEvent.getPointerPosition(touchEvent.getIndex());
    control.modify(point.getX() -mCoo.getPointX(),point.getY() -mCoo.getPointY());
    Logger.d("touch control x:" + control.getPointX() + " y:" + control.getPointY());
    invalidate();
    return true;
}

触摸点打印

#过年不停更#HarmonyOS-JAVA之手把手教你绘制冰墩墩_开鸿HarmonyOS_05

效果和实际应用

#过年不停更#HarmonyOS-JAVA之手把手教你绘制冰墩墩_冰墩墩_06#过年不停更#HarmonyOS-JAVA之手把手教你绘制冰墩墩_贝塞尔曲线_07

4,绘制过程动效

开头部分动效的实现

原本是采用AnimatorValue的,发现API7才能实现,我们分段式也多;后面演变采用EventHandler,实现一步步绘制;

EventRunner runnerA = EventRunner.getMainEventRunner();
EventHandler handlerA = new EventHandler(runnerA) {
    @Override
    protected void processEvent(InnerEvent event) {
        super.processEvent(event);
        if (drawStep == 0) {
            drawStep = 1;
        }
        invalidate();
        if (!drawLast) {
            handlerA.sendEvent(1, 10);
        }
    }
};

    public void startDraw() {
        Logger.d(":");
        if (drawLast) {
            drawStep = 0;
            handlerA.sendEvent(1);
            drawLast = false;
        }
    }

绘制分段式

不断改变childStep的步长即可改变每次绘制的效果

PathMeasure pathMeasure = new PathMeasure(mBezierPath, false);
childStep = pathMeasure.getLength();
//使用画笔虚线效果+偏移
PathEffect effect = new PathEffect(
    new float[]{pathMeasure.getLength(), pathMeasure.getLength()},
    childStep);
mPaint.setPathEffect(effect);
canvas.drawPath(mBezierPath, mPaint);

1,中心的点的确认,坐标系的建立,可以达到快速开发的效果;

2,本文主要采用二阶贝塞尔曲线绘制;根据第一步的基础,然后再touch事件中找到相对坐标点;

 bdd: HarmonyOS JAVA之手把手教你绘制冰墩墩 (gitee.com)

更多原创内容请关注: 深开鸿技术团队

入门到精通、技巧到案例,系统化分享HarmonyOS开发技术,欢迎投稿和订阅,让我们一起携手前行共建鸿蒙生态。

 想了解更多关于鸿蒙的内容,请访问:

 51CTO和华为官方合作共建的鸿蒙技术社区

 https://harmonyos.51cto.com/#bkwz

#过年不停更#HarmonyOS-JAVA之手把手教你绘制冰墩墩_贝塞尔曲线_08


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK