Android 自定义view 用canvas去画图形, 都是以面向过程的方式去一笔一笔的画, 而且画的图形也不能支持添加事件, 而html, js在这方面有大量的封装好的canvas框架, 很奇怪的是android上我也没有搜到类似的封装框架, 我只是个web前端开发者, 可能是我对android不了解没有搜索到, 我就仿照html,js这一套实现了这个Android上的canvas小框架object-canvas。
框架参照html div标签的一些特性实现的
- 有盒子模型,支持border,padding, 可以分别设置上下左右的样式
- 可以直接设置文本, 在android里显示个文字还要嵌套上一层textview
- 仅支持绝对布局, 这个得需要自己计算元素的位置了, 确定元素的宽,高l,eft和top, 相对布局还没实现
- 支持添加事件, 可以捕获和冒泡事件
- 直接支持滚动条, 在android里还要嵌套一个ScrollView才出滚动条
- 支持transform变换,支持平移, 旋转,缩放等, 支持全局坐标转自身坐标, 自身坐标转全局坐标
- 支持缓动动画, 支持属性动画和timeline, 动画参数可以设置往返执行, 执行次数, 缓动函数等
当前这个框架只是抛转引玉, 没啥实用性
开源代码:
object-canvashttps://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();
}
}