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

Posted HarmonyOS技术社区

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了#过年不停更#HarmonyOS-JAVA之手把手教你绘制冰墩墩相关的知识,希望对你有一定的参考价值。

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

作者:彭为杰

简介

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

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

效果演示

实现思路

1 建立坐标系

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

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

@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%;

准备画笔

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.简单认识:(图来源网络)

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;

触摸点打印

效果和实际应用

4,绘制过程动效

开头部分动效的实现

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

::: hljs-center

:::

以上是关于#过年不停更#HarmonyOS-JAVA之手把手教你绘制冰墩墩的主要内容,如果未能解决你的问题,请参考以下文章

#过年不停更#Axios踩坑日记

#过年不停更#HarmonyOS自定义JS组件—灵动的锦鲤

成长经历之新年感触

#打卡不停更#三方库移植之NAPI开发[4]异步调用:Callback&Promise

#打卡不停更#三方库移植之NAPI开发[3]通过IDE开发NAPI工程

原创Huatuo热更框架之手把手部署