Android基于Canvas,自定义View实现简单的画图工具

我们要实现的功能:

1、点击相应的按钮,在图上画出相应的图形,并实时展现在画布上。
2、橡皮檫功能,点击橡皮擦按钮,可以擦除画图上的图形。
3、点击确定按钮可以停止当前的绘制。

4、点击保存按钮可以把绘制的图形保存为jpg格式的图片文件。

具体如下图:




具体实现步骤:

1、先自定义view,在view里实现各种画图的操作,代码如下:

public class TestView extends View {
    public int mMode = 0;   //0:空 1:画直线  2:涂鸦  3:橡皮檫
    private float startX, startY, endX, endY;//用来存手指移动后的坐标
    private float mX, mY;
    public Bitmap map;//对图像的一些操作
    public Canvas canvas;
    private Paint paint;//画笔
    private Paint eraserPaint; //橡皮
    private Path mPath;
    public int color;
    private TextView textView;
    private int mWidth, mHeight;
    private float radius;

    public TestView(Context context, int width, int height, TextView textView) {
        super(context);
        this.mWidth = width;
        this.mHeight = height;
        paint = new Paint();
        paint.setAntiAlias(true);//消除锯齿
        paint.setStyle(Paint.Style.STROKE);//设置画笔结尾时的样式为圆润
        paint.setStrokeJoin(Paint.Join.ROUND); //设置拐角的形状 MITER 尖角、 BEVEL 平角和 ROUND 圆角,默认为 MITER
        paint.setStrokeCap(Paint.Cap.SQUARE);  //设置线头的形状  BUTT 平头、ROUND 圆头、SQUARE 方头,默认为 BUTT
        paint.setColor(Color.BLACK);
        paint.setStrokeWidth(3);

        //橡皮擦
        eraserPaint = new Paint();
        eraserPaint.setAlpha(0);
        //这个属性是设置paint为橡皮擦重中之重
        //这是重点
        //下面这句代码是橡皮擦设置的重点
        eraserPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
        //上面这句代码是橡皮擦设置的重点(重要的事是不是一定要说三遍)
        eraserPaint.setAntiAlias(true);
        eraserPaint.setDither(true);
        eraserPaint.setStyle(Paint.Style.STROKE);
        eraserPaint.setStrokeJoin(Paint.Join.ROUND);
        eraserPaint.setStrokeWidth(20);

        mPath = new Path();

        map = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);//绘制一个与手机屏幕大小的BitMap对象
        canvas = new Canvas(map);

        this.textView = textView;
    }

    //设置绘制模式
    public void setMode(int mode) {
        this.mMode = mode;
    }

    @Override
    public void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawColor(Color.WHITE);
        canvas.drawBitmap(map, 0, 0, paint);//从 0 0开始到map中的width,height的一块画布,也就是全屏幕的一块画布
        switch (mMode) {
            case GameActivity.DrawLinePen:
                canvas.drawLine(startX, startY, endX, endY, paint);
                break;
            case GameActivity.DrawPolyPen:

                break;
            case GameActivity.DrawRectPen:
                canvas.drawRect(startX, startY, endX, endY, paint);
                break;
            case GameActivity.DrawCirclePen:
                canvas.drawCircle(startX, startY, radius, paint);
                break;
            case GameActivity.HandWritePen:
                canvas.drawPath(mPath, paint);
                break;
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        //获得手指放下的坐标
        float x = event.getX();
        float y = event.getY();
        int action = event.getAction();
        switch (mMode) {
            case GameActivity.DrawLinePen:
                drawLine(x, y, action);
                break;
            case GameActivity.DrawPolyPen:
                drawPoly(x, y, action);
                break;
            case GameActivity.DrawRectPen:
                drawRect(x, y, action);
                break;
            case GameActivity.DrawCirclePen:
                drawCircle(x, y, action);
                break;
            case GameActivity.HandWritePen:
                handWrite(x, y, action);
                break;
            case GameActivity.Eraser:
                eraser(x, y, action);
                break;
        }
        return true;
    }

    private void drawLine(float x, float y, int action) {
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                textView.setVisibility(VISIBLE);
                startX = x;
                startY = y;
                break;
            case MotionEvent.ACTION_MOVE:
                int angle = Math.abs((int) (Math.atan((y - startY) / (x - startX)) / 3.14 * 180));
                if (angle == 0 || angle == 1) {
                    angle = 0;
                    endX = x;
                    endY = startY;
                } else if (angle == 89 || angle == 90) {
                    angle = 90;
                    endX = startX;
                    endY = y;
                } else {
                    endX = x;
                    endY = y;
                }
                float length = (float) Math.sqrt((endX - startX) * (endX - startX) + (endY - startY) * (endY - startY));
                textView.setText("长度:" + length + ", 角度:" + angle + "°");
                invalidate();
                break;
            case MotionEvent.ACTION_UP:
                textView.setVisibility(GONE);
                canvas.drawLine(startX, startY, endX, endY, paint);
                break;
        }
    }

    private void drawPoly(float x, float y, int action) {
        switch (action) {
            case MotionEvent.ACTION_DOWN:

                break;
            case MotionEvent.ACTION_MOVE:

                break;
            case MotionEvent.ACTION_UP:

                break;
        }
    }

    private void drawRect(float x, float y, int action) {
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                textView.setVisibility(VISIBLE);
                startX = x;
                startY = y;
                break;
            case MotionEvent.ACTION_MOVE:
                endX = x;
                endY = y;
                textView.setText("长:" + Math.abs(endX - startX) + ", 宽:" + Math.abs(endY - startY));
                invalidate();
                break;
            case MotionEvent.ACTION_UP:
                textView.setVisibility(GONE);
                canvas.drawRect(startX, startY, endX, endY, paint);
                break;
        }
    }

    private void drawCircle(float x, float y, int action) {
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                textView.setVisibility(VISIBLE);
                startX = x;
                startY = y;
                break;
            case MotionEvent.ACTION_MOVE:
                endX = x;
                endY = y;
                radius = (float) Math.sqrt((endX - startX) * (endX - startX) + (endY - startY) * (endY - startY));
                textView.setText("半径:" + radius);
                invalidate();
                break;
            case MotionEvent.ACTION_UP:
                textView.setVisibility(GONE);
                canvas.drawCircle(startX, startY, radius, paint);
                break;
        }
    }

    public void handWrite(float x, float y, int action) {
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                mPath.reset();
                mPath.moveTo(x, y);
                mX = x;
                mY = y;
                break;
            case MotionEvent.ACTION_MOVE:
                mPath.quadTo(mX, mY, (x + mX) / 2, (y + mY) / 2);
                mX = x;
                mY = y;
                invalidate();
                break;
            case MotionEvent.ACTION_UP:
                mPath.lineTo(mX, mY);
                canvas.drawPath(mPath, paint);
                break;
        }
    }

    public void eraser(float x, float y, int action) {
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                mPath.reset();
                mPath.moveTo(x, y);
                mX = x;
                mY = y;
                canvas.drawPath(mPath, eraserPaint);
                invalidate();
                break;
            case MotionEvent.ACTION_MOVE:
                mPath.quadTo(mX, mY, (x + mX) / 2, (y + mY) / 2);
                mX = x;
                mY = y;
                canvas.drawPath(mPath, eraserPaint);
                invalidate();
                break;
            case MotionEvent.ACTION_UP:
                mPath.lineTo(mX, mY);
                canvas.drawPath(mPath, eraserPaint);
                invalidate();
                break;
        }
    }

    public void save(Context context) {
        if (mMode == 0) {
            Toast.makeText(context, "请您先绘制图形再保存", Toast.LENGTH_SHORT).show();
        } else {
            try {
                Bitmap whiteBgBitmap = Bitmap.createBitmap(mWidth, mHeight, Bitmap.Config.ARGB_8888);
                Canvas canvas = new Canvas(whiteBgBitmap);
                canvas.drawColor(Color.WHITE);
                canvas.drawBitmap(map, 0, 0, null);
                // 保存图片到SD卡上
                File file = new File(Environment.getExternalStorageDirectory(), System.currentTimeMillis() + ".png");
                FileOutputStream stream = new FileOutputStream(file);
                whiteBgBitmap.compress(Bitmap.CompressFormat.PNG, 100, stream);
                stream.close();
                Toast.makeText(context, "保存图片成功", Toast.LENGTH_SHORT).show();
            } catch (Exception e) {
                Toast.makeText(context, "保存图片失败", Toast.LENGTH_SHORT).show();
                e.printStackTrace();
            }
        }
    }
}

2、定义好view之后,我们创界主Activity的xml布局文件,activity_test.xml,代码如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".GameActivity">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="right"
        android:orientation="horizontal"
        android:background="@color/colorGray">

        <TextView
            android:id="@+id/tv_text"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textColor="@color/colorWhite"
            android:layout_marginRight="400dp"
            android:visibility="gone"/>
        <Button
            android:id="@+id/btn_ok"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="确定" />

        <Button
            android:id="@+id/btn_save"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="保存" />
    </LinearLayout>

    <LinearLayout
        android:id="@+id/ll_canvas"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:orientation="horizontal">

    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:gravity="center"
        android:background="@color/colorPrimary">
        <Button
            android:id="@+id/btn_drawLine"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="直线" />

        <Button
            android:id="@+id/btn_drawPoly"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="多边形" />

        <Button
            android:id="@+id/btn_drawRect"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="矩形" />

        <Button
            android:id="@+id/btn_drawCircle"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="圆" />

        <Button
            android:id="@+id/btn_handWrite"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="手绘线" />

        <Button
            android:id="@+id/btn_eraser"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="橡皮擦" />

    </LinearLayout>
</LinearLayout>

3、再创建我们的主Activity文件,GameActivity.java(文件名我是随便取的,不要在意)。在该文件中引入我们的布局文件和自定义view。代码如下:

public class GameActivity extends Activity implements View.OnClickListener {
    private Button btn_drawLine, btn_drawPoly, btn_drawRect, btn_drawCircle, btn_handWrite, btn_eraser, btn_ok, btn_save;
    private TextView textView;
    private TestView testView;
    public static final int DrawLinePen = 1;
    public static final int DrawPolyPen = 2;
    public static final int DrawRectPen = 3;
    public static final int DrawCirclePen = 4;
    public static final int HandWritePen = 5;
    public static final int Eraser = 6;
    public static final int DrawFinish = 9;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //设置手机为全屏模式
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
        setContentView(R.layout.activity_test);
        //初始化组件
        textView = findViewById(R.id.tv_text);
        btn_drawLine = findViewById(R.id.btn_drawLine);
        btn_drawPoly = findViewById(R.id.btn_drawPoly);
        btn_drawRect = findViewById(R.id.btn_drawRect);
        btn_drawCircle = findViewById(R.id.btn_drawCircle);
        btn_handWrite = findViewById(R.id.btn_handWrite);
        btn_eraser = findViewById(R.id.btn_eraser);
        btn_ok = findViewById(R.id.btn_ok);
        btn_save = findViewById(R.id.btn_save);

        btn_drawLine.setOnClickListener(this);
        btn_drawPoly.setOnClickListener(this);
        btn_drawRect.setOnClickListener(this);
        btn_drawCircle.setOnClickListener(this);
        btn_handWrite.setOnClickListener(this);
        btn_eraser.setOnClickListener(this);
        btn_ok.setOnClickListener(this);
        btn_save.setOnClickListener(this);

        //获取我们xml布局中view的宽高,并把画布view放进去
        // 获取创建的宽度和高度
        DisplayMetrics displayMetrics = new DisplayMetrics();
        getWindowManager().getDefaultDisplay().getRealMetrics(displayMetrics);
        LinearLayout ll_canvas = findViewById(R.id.ll_canvas);
        testView = new TestView(this, displayMetrics.widthPixels, displayMetrics.heightPixels, textView);
        ll_canvas.addView(testView);
        testView.requestFocus();
    }

    @Override
    public void onClick(View view) {
        switch (view.getId()) {
            case R.id.btn_drawLine:
                testView.setMode(DrawLinePen);
                break;
            case R.id.btn_drawPoly:
                testView.setMode(DrawPolyPen);
                break;
            case R.id.btn_drawRect:
                testView.setMode(DrawRectPen);
                break;
            case R.id.btn_drawCircle:
                testView.setMode(DrawCirclePen);
                break;
            case R.id.btn_handWrite:
                testView.setMode(HandWritePen);
                break;
            case R.id.btn_eraser:
                testView.setMode(Eraser);
                break;
            case R.id.btn_ok:
                testView.setMode(DrawFinish);
                break;
            case R.id.btn_save:
                testView.save(this);
                break;
        }
    }
}

至此,我们简单的画图demo就实现了微笑

demo下载地址

原文链接:加载失败,请重新获取