Android小白的探索:2D绘图之Android简易版Microsoft Visio学习之路 一、组合模式

 

    用Android写一个简易版的Visio对我是个极大的挑战,我学习进度很慢,还要工作,还要学习,不会经常更新博客。

    在Visio中可以实现任意绘图,矩形,线,圆,椭圆等规则或不规则图形,可以对在一片区域内的图形进行组合、拆分,移动。放大、缩小、回退上一步等操作。这其中的难点在于如何实现组合、拆分、移动。

    在2D绘图中,要画一个图形,需要使用画笔Paint、画布Canvas、图元。要让所有绘制的图形在一个View上显示就需要保证Canvas的相同,画笔可以不同。而图元,其实就是数据的意思,比如画一个圆,图元就应该只有三个数据:圆心的X、Y和半径。

    这些都不管,我们先把图画出来。

    画一个圆得需要圆心半径,矩形需要4个点的path,椭圆需要RectF值等,还需要画笔,画笔的Style等属性,为了不仅仅只是画一种图,我们需要把所有要用到的东西都列出来。所以我写个一个画图的父类,让绘制具体图形的类去继承他,在父类里边添加各种的属性,并实现getter、setter方法,而子类就不必再添加这么多的东西。

    private int paintColor;
    private boolean paintAntiAlias;
    private float paintWight;
    private boolean paintSawtooth;
    private int paintAlpha;
    private Paint.Style paintStyle;
    private static Paint paint;
    private Path path = new Path();
    private float[] pts;
    private int offset;
    private int count;
    private RectF rectF = new RectF();
    private BaseData mBaseData;

为了不每次画图都new许多的笔,我实例化了一个笔,因为是静态的,就不会new出许多

192300_4fAZ_2892218.png

public interface DrawInterface {
    void changeState();
    /**
     * 输出对象的自身结构,开始画图
     *
     */
    void drawPicture(Canvas canvas);
    /**
     * 聚集管理方法,增加一个子构件对象
     * @param child 子构件对象
     */
    public void addChild(BaseDraw child);
    /**
     * 聚集管理方法,删除一个子构件对象
     * @param index 子构件对象的下标
     */
    public void removeChild(int index);
    /**
     * 聚集管理方法,返回所有子构件对象
     */
    public List<BaseDraw> getChild();

}

这是绘图的目录结构,写一个接口,是为了从外部调用的时候可以不用管是谁在执行这个方法。

在这个中有一个Draw方法,传入的是一个Canvas,是为了保证,所有的绘制,都是在同一个Canvas上进行的。

圆的绘制

public class CircularDraw extends BaseDraw {

    public CircularDraw(BaseData baseData) {
        super(baseData);
    }

    @Override
    public void changeState() {

    }

    @Override
    public void drawPicture(Canvas canvas) {
        setPaintStyle(getmBaseData().getPaintStyle());
        setPaintAntiAlias(true);
        setPaintWight(getmBaseData().getWight());
        setRectF(getmBaseData().getRectF());
        setOffset(getmBaseData().getOffset());
        setCount(getmBaseData().getCount());
        setPaintAlpha(getmBaseData().getPaintAlpha());
        setPts(getmBaseData().getFloats());

        canvas.drawCircle(getPts()[0], getPts()[1], getPts()[2], getPaint());

    }
}

椭圆的

public class EllipseDraw extends BaseDraw {
    public EllipseDraw(BaseData baseData) {
        super(baseData);
    }


    @Override
    public void drawPicture(Canvas canvas ) {
        setPaintStyle(getmBaseData().getPaintStyle());
        setPaintAntiAlias(true);
        setPaintWight(getmBaseData().getWight());
        setRectF(getmBaseData().getRectF());
        setOffset(getmBaseData().getOffset());
        setCount(getmBaseData().getCount());
        setPaintAlpha(getmBaseData().getPaintAlpha());
        setPts(getmBaseData().getFloats());

        canvas.drawOval(getRectF(), getPaint());
    }
}

其他的大同小异,所有子类所承担的任务有且只有绘制,图形的移动、放大、缩小,其实只是图元的改变

其实整个的绘图到这里就完成了。

    现在开始写图元了。对于图元,因为Visio的图可以组合,拆分,组合图形的移动是内部所有图形的移动,而非组合的移动只是自己的移动。对于图元我们得使用组合模式

    什么是组合模式

195320_9DOy_2892218.png

像这样的感觉,在一个list中,会有树叶、树枝或者只有树叶。我们需要让这一个实例中得包含所有的实例。

就像一颗树一样,他自己就知道自己是树叶还是树枝。

对于图元来说,我们需要他拥有绘制所有图元所必须得值,基于与同样的理由所以我有了

以下的数据结构

195927_Ewi5_2892218.png

CompositeData是树枝,BaseData是父类,其他的都是子类

接口

    public void addChild(BaseData baseData);
    public void removeChild(int index);
    public void move(float moveX ,float moveY);
    public void draw(Canvas canvas);
    public List<BaseData> getChild();
    public DataType[] pointIsInside(float moveX , float moveY);

draw方法传入的Canvas会传入到绘图的draw方法中去完成绘图

BaseData

public class BaseData implements DataInterface {
    @Override
    public void addChild(BaseData baseData) {
        Log.e("BaseData", "对象不支持此功能");
    }

    @Override
    public void removeChild(int index) {
        Log.e("BaseData", "对象不支持此功能");
    }

    @Override
    public List<BaseData> getChild() {
        Log.e("BaseData", "对象不支持此功能");
        return null;
    }

    @Override
    public DataType[] pointIsInside(float moveX , float moveY) {
        return null;
    }

    @Override
    public void move(float moveX, float moveY) {
    }

    @Override
    public void draw(Canvas canvas) {
    }


    private TypeEnum typeEnum;
    private int offset;
    private int count;
    private float[] floats;
    private RectF rectF = new RectF();
    private Paint.Style paintStyle;
    private float wight;
    private int paintAlpha;

    public BaseData(TypeEnum typeEnum, int wight, int paintAlpha, int offset, int count, float[] floats, RectF rectF, Paint.Style style) {
        this.typeEnum = typeEnum;
        this.wight = wight;
        this.paintAlpha = paintAlpha;
        this.offset = offset;
        this.count = count;
        this.floats = floats;
        this.rectF = rectF;
        this.paintStyle = style;
    }


    public TypeEnum getTypeEnum() {
        return typeEnum;
    }

    public void setTypeEnum(TypeEnum typeEnum) {
        this.typeEnum = typeEnum;
    }

    public int getOffset() {
        return offset;
    }

    public void setOffset(int offset) {
        this.offset = offset;
    }

    public int getCount() {
        return count;
    }

    public void setCount(int count) {
        this.count = count;
    }

    public float[] getFloats() {
        return floats;
    }

    public void setFloats(float[] floats) {
        this.floats = floats;
    }

    public RectF getRectF() {
        return rectF;
    }

    public void setRectF(RectF rectF) {
        this.rectF = rectF;
    }

    public Paint.Style getPaintStyle() {
        return paintStyle;
    }

    public void setPaintStyle(Paint.Style paintStyle) {
        this.paintStyle = paintStyle;
    }

    public float getWight() {
        return wight;
    }

    public void setWight(float wight) {
        this.wight = wight;
    }

    public int getPaintAlpha() {
        return paintAlpha;
    }

    public void setPaintAlpha(int paintAlpha) {
        this.paintAlpha = paintAlpha;
    }
}

父类中定义好所有需要的东西,其实就是从BaseDraw中复制出来的属性,对与树叶来说,他们不具备添加另一个树叶或树枝的能力,所以不需要重写add、remove、getChild方法,树枝拥有这些能力,重写这些方法就能实现添加的能力

树叶,以圆的图元为例,其他树叶都是差不多的

public class CircularData extends BaseData {
    public CircularData(TypeEnum typeEnum, int wight, int paintAlpha, int offset, int count, float[] floats, RectF rectF, Paint.Style style) {
        super(typeEnum, wight, paintAlpha, offset, count, floats, rectF, style);
    }

    @Override
    public void move(float moveX, float moveY) {
        float[] floats = {getFloats()[0] + moveX, getFloats()[1] + moveY, getFloats()[2]};
        setFloats(floats);
    }

    @Override
    public void draw(Canvas canvas) {
        CircularDraw circularDraw = new CircularDraw(this);
        circularDraw.drawPicture(canvas);
    }

    @Override
    public DataType[] pointIsInside(float moveX, float moveY) {
        float x = getFloats()[0];
        float y = getFloats()[1];
        float r = getFloats()[2];

        if ((x + r > moveX && moveX > x - r) && (y + r > moveY && moveY > y - r)) {
            return new DataType[]{DataType.In, DataType.Leaf};
        }
        return new DataType[]{DataType.Out, DataType.Leaf};
    }
}

因为这个类,知道自己就是圆,所以我可以直接new 一个圆的绘制类,然后把View传过来的Canvas,和自己传给绘制类,自己是一个图元,Canvas是唯一的,Paint早就实例化了的各种属性也设置好了,就可以直接再Canvas上画出圆。move方法,是获取屏幕的点击坐标,传入x,y值,更改与画圆有关的属性,pointIsInside方法,是判断,当屏幕被点击的点,是不是在我的绘制区域内

DataType是一个枚举


public enum DataType {
    In , Out , Leaf ,Root
}

返回一个枚举数组就知道这个点在不在之中,这个图形时枝还是叶。

树枝

public class CompositeData extends BaseData {
    public CompositeData(TypeEnum typeEnum, int wight, int paintAlpha, int offset, int count, float[] floats, RectF rectF, Paint.Style style) {
        super(typeEnum, wight, paintAlpha, offset, count, floats, rectF, style);
    }

    private List<BaseData> childComponents = new ArrayList<BaseData>();

    @Override
    public void move(float moveX, float moveY) {
        if (childComponents != null) {
            for (BaseData baseData : childComponents) {
                baseData.move(moveX, moveY);
            }
        }
    }

    @Override
    public DataType[] pointIsInside(float moveX, float moveY) {
        if (childComponents != null) {
            for (BaseData baseData : childComponents) {
                DataType dataType[] = baseData.pointIsInside(moveX, moveY);
                if (dataType[0] == DataType.In) {
                    return new DataType[]{DataType.In, DataType.Root};
                }
            }
        }
        return new DataType[]{DataType.Out, DataType.Root};
    }

    @Override
    public void addChild(BaseData baseData) {
        childComponents.add(baseData);
    }

    @Override
    public void removeChild(int index) {
        childComponents.remove(index);
    }

    @Override
    public List<BaseData> getChild() {
        return childComponents;
    }

    @Override
    public void draw(Canvas canvas) {
        if (childComponents != null) {
            for (BaseData baseData : childComponents) {
                baseData.draw(canvas);
            }
        }
        CompositeDraw compositeDraw = new CompositeDraw(this);
        compositeDraw.drawPicture(canvas);
    }


}

树枝中有一个List,是用来储存添加的叶、枝的,当树枝要移动是树叶也要移动,所以,移动树枝,就是树枝遍历自己list里的图元对象,然后调用图元的move方法。draw也是一样的,而当判断点击的点在不在这个树枝的区域内时,只要这个点在树枝任意一个树叶的范围内,我们所返回的枚举数组就是{在,树枝},这里我只有移动,放大、缩小,后面一步步完善再贴上来。

现在为了绘图我们需要一个View和Canvas

DrawView

public class DrawView extends View {

    Context context;
    BaseData baseData;


    public DrawView(Context context) {
        super(context);
        this.context = context;
    }

    @Override
    protected void onDraw(Canvas canvas) {

        super.onDraw(canvas);
        if (baseData != null) {
            baseData.draw(canvas);
        }

    }

    public BaseData getBaseDraw() {
        return baseData;
    }

    public void upData(BaseData baseData) {
        this.baseData = baseData;
        postInvalidate();
    }

    public void move(float moveX , float moveY){
        if (baseData != null){
            baseData.move(moveX ,moveY);
            postInvalidate();
        }
    }

}

baseData是包含了所有枝、叶的实例,在onDraw中,baseData调用draw时,遇到叶会直接画出来,遇到枝,枝会把自己所包含的枝、叶一个接一个的遍历然后画出来。

MainActivity

public class MainActivity extends AppCompatActivity {
    RelativeLayout relativeLayout;
    DrawView drawView;
    BaseData dataRoot;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        relativeLayout = (RelativeLayout) findViewById(R.id.main_relativeLayout);
        drawView = new DrawView(this);
        relativeLayout.addView(drawView);

        float[] rootFloat = {100, 100, 100, 100, 600, 900, 210, 165, 123, 516, 165, 165};
        dataRoot = new CompositeData(TypeEnum.CompositeDraw, 20, 100, 0, 0, rootFloat, null, Paint.Style.STROKE);


        float[] root1Float = {100, 100, 100, 100, 600, 900, 210, 165, 123, 516, 165, 165};
        BaseData dataroot1 = new CompositeData(TypeEnum.CompositeDraw, 20, 100, 0, 0, root1Float, null, Paint.Style.STROKE);


        float[] root2Float = {100, 100, 100, 100, 700, 900, 210, 165, 123, 516, 165, 165};
        BaseData dataroot2 = new CompositeData(TypeEnum.CompositeDraw, 20, 100, 0, 0, root2Float, null, Paint.Style.STROKE);


        float[] leaf1Float = {100, 300, 100, 500, 100, 100, 100, 100, 100, 100, 100, 100};
        RectF rectF = new RectF();
        rectF.top = 40;
        rectF.right = 700;
        rectF.left = 50;
        rectF.bottom = 200;
//        float left, float top, float right, float bottom
        BaseData dataLeaf1 = new EllipseData(TypeEnum.Ellipse, 20, 100, 100, 100, leaf1Float, rectF, Paint.Style.STROKE);


        float[] leaf2Float = {100, 100, 100, 300, 200, 500, 310, 265, 223, 316, 265, 265};
        BaseData dataLeaf2 = new PathData(TypeEnum.Path, 20, 100, 0, 0, leaf2Float, null, Paint.Style.STROKE);


        float[] leaf3Float = {100, 100, 100, 100, 300, 600, 410, 165, 423, 216, 665, 465};
        RectF rectF1 = new RectF(100, 300, 400, 500);
        BaseData dataLeaf3 = new RectangleData(TypeEnum.Rectangle, 20, 100, 0, 0, leaf3Float, rectF1, Paint.Style.STROKE);


        float[] leaf4Float = {100, 100, 100, 100, 300, 600, 410, 165, 423, 216, 665, 465};
        BaseData dataLeaf4 = new CircularData(TypeEnum.Circular, 20, 100, 0, 0, leaf4Float, null, Paint.Style.STROKE);

        float[] leaf5Float = {200, 100, 300, 200, 100, 400, 410, 165, 423, 216, 665, 465};
        BaseData dataLeaf5 = new LineData(TypeEnum.Line, 20, 100, 0, 0, leaf5Float, null, Paint.Style.STROKE);

        dataRoot.addChild(dataroot1);
        dataRoot.addChild(dataroot2);
        dataroot1.addChild(dataLeaf1);
        dataroot1.addChild(dataLeaf2);
        dataroot2.addChild(dataLeaf3);
        dataroot2.addChild(dataLeaf4);
        dataroot2.addChild(dataLeaf5);
        dataLeaf3.addChild(dataLeaf1);

        drawView.upData(dataRoot);

        relativeLayout.setOnTouchListener(new TouchListenerImp());
    }


    List<BaseData> dataList;
    String szie = null;
    float x = 0;
    float y = 0;

    private class TouchListenerImp implements View.OnTouchListener {
        public boolean onTouch(View v, MotionEvent event) {
            String show = " ";

            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    System.out.println("---action down-----");
//                    show = ("起始位置为:" + "(" + event.getX() + " , " + event.getY() + ")");
                    dataList = dataRoot.getChild();
                    for (int i = 0; i < dataList.size(); i++) {
                        DataType[] dataType = dataList.get(i).pointIsInside(event.getX(), event.getY());
                        if (dataType[0] == DataType.In) {
                            x = event.getX();
                            y = event.getY();
                            szie = String.valueOf(i);
                            Log.e("axz", "a = "+Integer.parseInt(szie));
                            break;
                        }
                    }
                    break;
                case MotionEvent.ACTION_MOVE:
                    System.out.println("---action move-----");
//                    show = ("移动中坐标为:" + "(" + event.getX() + " , " + event.getY() + ")");
                    break;
                case MotionEvent.ACTION_UP:
                    System.out.println("---action up-----");
                    if (szie != null) {
                        dataRoot.getChild().get(Integer.parseInt(szie)).move(event.getX() - x, event.getY() - y);
                        Log.e("axz", "a = "+Integer.parseInt(szie)+"x =  " + x + "Y = " + y);
                        drawView.upData(dataRoot);
                        szie = null;
                        x = 0;
                        y = 0;
                    }

//                    show = ("最后位置为:" + "(" + event.getX() + " , " + event.getY() + ")");
            }
//            Toast.makeText(MainActivity.this, show, Toast.LENGTH_SHORT).show();
            return true;
        }
    }
}

204715_m7JX_2892218.jpg

204736_sJoY_2892218.jpg

204744_ZaTB_2892218.jpg

204755_aprD_2892218.jpg

204805_lC5o_2892218.jpg

现在是对枝和叶手动添加的,能够运行,能够对两个枝进行分别的拖动,添加叶也能单独的拖动,拆分、组合。其实就是对枝里list的增、删、改、查。暂时代表组合模式基本成功,如果我理解的有什么不对的,欢迎指正。

转载于:https://my.oschina.net/zhenghaoLi/blog/1519550

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值