原生Android 以面向对象的方式操作canvas

本文介绍了前端开发者如何模仿HTML/JS的canvas API,创建了一个名为Object-Canvas的轻量级框架,用于在Android中更便捷地绘制图形、添加事件并支持变换和动画。框架包括元素管理、布局、事件响应和滚动条等功能,尽管尚不完善,但展示了跨平台技术的融合创新。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Android 自定义view 用canvas去画图形, 都是以面向过程的方式去一笔一笔的画, 而且画的图形也不能支持添加事件, 而html, js在这方面有大量的封装好的canvas框架, 很奇怪的是android上我也没有搜到类似的封装框架, 我只是个web前端开发者, 可能是我对android不了解没有搜索到, 我就仿照html,js这一套实现了这个Android上的canvas小框架object-canvas。

框架参照html div标签的一些特性实现的

  1. 有盒子模型,支持border,padding, 可以分别设置上下左右的样式
  2. 可以直接设置文本, 在android里显示个文字还要嵌套上一层textview
  3. 仅支持绝对布局, 这个得需要自己计算元素的位置了, 确定元素的宽,高l,eft和top, 相对布局还没实现
  4. 支持添加事件, 可以捕获和冒泡事件
  5. 直接支持滚动条, 在android里还要嵌套一个ScrollView才出滚动条
  6. 支持transform变换,支持平移, 旋转,缩放等, 支持全局坐标转自身坐标, 自身坐标转全局坐标
  7. 支持缓动动画, 支持属性动画和timeline, 动画参数可以设置往返执行, 执行次数, 缓动函数等

当前这个框架只是抛转引玉, 没啥实用性

开源代码: 

object-canvasicon-default.png?t=M85Bhttps://github.com/chengxg/object-canvas

这个图就是以canvas来画的demo

 CanvasDemoView.java

package com.github.chengxg.object_canvas;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;

import com.github.chengxg.object_canvas.Anime;
import com.github.chengxg.object_canvas.Body;
import com.github.chengxg.object_canvas.Element;
import com.github.chengxg.object_canvas.Event;
import com.github.chengxg.object_canvas.shape.CicleShape;
import com.github.chengxg.object_canvas.shape.ImageShape;

public class CanvasDemoView extends View {
    public Body root = null;
    public int screenWidth = 0;
    public int screenHeight = 0;
    public float screenWidthDp = 0;
    public float screenHeightDp = 0;

    public CanvasDemoView(Context context, AttributeSet attrs) {
        super(context, attrs);
        DisplayMetrics dm = getResources().getDisplayMetrics();
        screenWidth = dm.widthPixels;
        screenHeight = dm.heightPixels;
        screenWidthDp = screenWidth / dm.density;
        screenHeightDp = screenHeight / dm.density;
        root = new Body(this);
        root.setDensityScale(dm.density);
        root.layout.setContent((float) screenWidthDp, (float) screenHeightDp);
        root.setBox().setBackgroundColor(0xfff2f2f2);
        initPage(root);
    }

    public void initPage(Element parent) {
        //@formatter:off
        Element page = new Element(); parent.addChild(page);
            Element section1 = new Element();page.addChild(section1);
                Element title = new Element();section1.addChild(title);
                Element content = new Element();section1.addChild(content);
                    ImageShape leftCnt = new ImageShape();content.addChild(leftCnt);
                    Element rightCnt = new Element();content.addChild(rightCnt);
                        Element cntTitle = new Element();rightCnt.addChild(cntTitle);
                        Element cntDesc = new Element();rightCnt.addChild(cntDesc);
            Element sectionAnimate = new Element();page.addChild(sectionAnimate);
            Element sectionScroll = new Element();page.addChild(sectionScroll);
        //@formatter:on

        page.getLayout().setPadding(10).setContentMatchParent(0);
        page.setScroll();

        section1.setName("testsection1").getLayout().setBorderWidth(1).setPadding(10).setContentMatchParent(150);
        section1.setBox().setBackgroundColor(Color.WHITE).setBorderColor(0xffcccccc).setBorderRadius(10);

        title.setName("testTitle").getLayout().setPadding(10, 5).setBorderBottom(1).setContentMatchParent(40);
        title.setTextContent("Section1").setColor(0xff333333).setTextSize(16);
        title.setBox().setBorderColor(0xffcccccc);

        content.getLayout().setPadding(5).setContentMatchParent(80).setPosition(0, title.layout.top + title.getLayout().getHeight() + 10);

        Bitmap picIcon = BitmapFactory.decodeResource(this.getResources(), R.drawable.picture_icon);
        leftCnt.getLayout().setContent(48, 48);
        leftCnt.setBitmap(picIcon);

        rightCnt.getLayout().setPosition(60, 0).setContent(content.layout.contentWidth - 60, 60);

        cntTitle.getLayout().setPadding(0, 0).setBorderWidth(1).setContentMatchParent(30);
        cntTitle.setTextContent("标题").setColor(0xff333333).setTextSize(16);
        cntTitle.setBox().setBorderColor(0xffcccccc);

        cntDesc.getLayout().setPadding(0, 0).setBorderWidth(1).setContentMatchParent(24).setPosition(0, 30);
        cntDesc.setTextContent("内容描述").setColor(0xff666666).setTextSize(14);
        cntDesc.setBox().setBorderColor(0xffcccccc);

        // 动画
        sectionAnimate.getLayout().setBorderWidth(1).setPadding(10).setPosition(0, section1.getLayout().getHeight() + section1.getLayout().top + 15).setContentMatchParent(300);
        sectionAnimate.setBox().setBackgroundColor(Color.WHITE).setBorderColor(0xffcccccc).setBorderRadius(10);
        float width = sectionAnimate.getLayout().getWidth();
        float height = sectionAnimate.getLayout().getHeight();
        for (int i = 0; i < 500; i++) {
            CicleShape cicle = new CicleShape();
            sectionAnimate.addChild(cicle);
            int color = ((int) (Math.random() * 0xffffff)) | 0xff000000;
            cicle.setFill(color);
            cicle.setR((float) (30 * Math.random() + 5)).setCenter((float) (width * Math.random()), (float) (height * Math.random()));
            cicle.getParams().put("dir", color % 2 == 0 ? 1 : -1);
        }
        Anime.Instance anime = root.anime.create("{loopCount:0,duration:100,isGoBack:true,easing:'easeInOutQuart',change:null}", null);
        anime.change = (double px, Anime.Instance animate, Anime.PropKeyFrame propKeyFrame) -> {
            if (sectionAnimate.childs != null) {
                for (Element item : sectionAnimate.childs) {
                    CicleShape cicle = (CicleShape) item;
                    int dir = (int) cicle.getParams().get("dir");
                    float cx = cicle.x + dir * (float) (Math.random());
                    float cy = cicle.y + dir * (float) (Math.random());
                    if (cx < 0 || cx > width) {
                        dir = dir * -1;
                    }
                    if (cy < 0 || cy > height) {
                        dir = dir * -1;
                    }
                    cicle.getParams().put("dir", dir);
                    cicle.setCenter(cx, cy);
                }
            }
            root.setUpdateView();
        };
        anime.restart();

        //滚动条
        sectionScroll.getLayout().setBorderWidth(1).setPadding(10).setPosition(0, sectionAnimate.getLayout().getHeight() + sectionAnimate.getLayout().top + 15).setContentMatchParent(300);
        sectionScroll.setBox().setBackgroundColor(Color.WHITE).setBorderColor(0xffcccccc).setBorderRadius(10);
        for (int i = 0; i < 20; i++) {
            //@formatter:off
            Element item = new Element();
            sectionScroll.addChild(item);
            Element text = new Element();
            item.addChild(text);
            //@formatter:on
            float itemHeight = 36;
            item.getLayout().setPadding(5).setBorderWidth(1).setBoxSize(sectionScroll.layout.contentWidth, itemHeight).setPosition(0, (itemHeight + 5) * i + 5);
            item.setBox().setBorderColor(0xffcccccc).setBorderRadius(10);

            text.getLayout().setContentMatchParent(14);
            text.setTextContent("scroll item" + i).setTextSize(14).setColor(Color.GREEN);

            final int idx = i;
            item.setSilent(true).getEvent().on(Event.Touchstart, (Event event) -> {
                event.current.setBox().setBackgroundColor(0xffe0e0e0);
                return false;
            }).on(Event.Touchend, (Event event) -> {
                event.current.setBox().setBackgroundColor(Color.WHITE);
                return false;
            }).on(Event.Click, (Event event) -> {
                Log.d("click", "item" + idx);
                return false;
            });
        }
        sectionScroll.setScroll().updateScrollSize();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        long t = System.currentTimeMillis();
        root.render(canvas);
        long end = System.currentTimeMillis();
        //Log.d("onDraw", (end - t) + "ms");
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        root.dispatchEvent(event);
        return true;
    }

    @Override
    public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(screenWidth, screenHeight);
    }
}
MainActivity.java
package com.github.chengxg.object_canvas;

import android.app.Activity;
import android.os.Bundle;
import android.widget.FrameLayout;

public class MainActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.WRAP_CONTENT);

        FrameLayout layout = new FrameLayout(this);
        CanvasDemoView demoView = new CanvasDemoView(this, null);
        demoView.setLayoutParams(layoutParams);
        layout.addView(demoView);

        this.addContentView(layout, layoutParams);
    }

    @Override
    protected void onStart() {
        super.onStart();
    }

    @Override
    protected void onStop() {
        super.onStop();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
    }

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值