android小进阶- 自定义view Canvas画板

       在公司系统学习RN框架+android 公众号推文学习了一段时间后,终于等到你-公司布置的一个任务 开发纯原生的白板功能,包含画笔的粗细程度,橡皮擦,清空画板功能,裁剪,包含手动裁剪,自动裁剪需求,base64导入,导出功能。翻页功能。html代码导入canvas,啥也不多说,上源码分析和遇到的坑。

  由于还在开发中 目前功能只缺少翻页功能 ,其他功能需求基本完善,已写成demo,可供大家学习和优化,如有写不妥之处,欢迎指点一二,在此谢谢各位。

废话什么的不多说,上源码才是硬道理。

 

canvasDemo的架构很简单 里面就两个activity 一个base64转bitmap的工具类

工具类代码如下:(这个我就不分析了,自行度娘,很多,就是一个base64和bitmap互转的工具类)

package com.example.test.canvas;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.util.Base64;

import java.io.ByteArrayOutputStream;
import java.io.IOException;

public class Base64BitmapUtil {

    /**
     * bitmap转为base64
     *
     * @param bitmap
     * @return
     */
    public static String bitmapToBase64(Bitmap bitmap) {

        String result = null;
        ByteArrayOutputStream baos = null;
        try {
            if (bitmap != null) {
                baos = new ByteArrayOutputStream();
                bitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos);

                baos.flush();
                baos.close();

                byte[] bitmapBytes = baos.toByteArray();
                result = Base64.encodeToString(bitmapBytes, Base64.DEFAULT);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (baos != null) {
                    baos.flush();
                    baos.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return result;
    }

    /**
     * base64转为bitmap
     *
     * @param base64Data
     * @return
     */
    public static Bitmap base64ToBitmap(String base64Data) {
        byte[] bytes = Base64.decode(base64Data, Base64.DEFAULT);
        return BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
    }

}

MainActivity 就是白板  photoAty 这个是保存图片 裁剪后展示的activity,如需更改其他需求,欢迎各位大大自行修改,页面很丑 其他的什么效果可以自行修改 。

该为MainActivity的界面布局,很简单,一个线性布局里面放了一些功能按键,然后就是一整个白板 在imageview呈现

<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:background="#ffffff"
    android:orientation="vertical"
    tools:context=".MainActivity">
    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom">

        <Button
            android:id="@+id/btn_resume"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="清空"
            android:textSize="14sp" />

        <Button
            android:id="@+id/btn_xi"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_toRightOf="@+id/btn_resume"
            android:text="细铅笔"
            android:textSize="14sp" />

        <Button
            android:id="@+id/btn_cu"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_toRightOf="@+id/btn_xi"
            android:text="粗铅笔"
            android:textSize="14sp" />

        <Button
            android:id="@+id/btn_color"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_below="@+id/btn_cu"
            android:text="切换颜色"
            android:textSize="14sp" />

        <Button
            android:id="@+id/btn_clear"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_below="@+id/btn_cu"
            android:layout_toRightOf="@+id/btn_color"
            android:text="橡皮擦"
            android:textSize="14sp" />

        <Button
            android:id="@+id/btn_autoclip"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_below="@+id/btn_xi"
            android:layout_toRightOf="@+id/btn_clear"
            android:text="自动裁剪"
            android:textSize="14sp" />

        <Button
            android:id="@+id/btn_clip"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_below="@+id/btn_cu"
            android:layout_toRightOf="@+id/btn_autoclip"
            android:text="手动裁剪"
            android:textSize="14sp" />

        <Button
            android:id="@+id/btn_extend"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_below="@+id/btn_color"
            android:text="延长"
            android:textSize="14sp" />

        <Button
            android:id="@+id/btn_import"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_below="@+id/btn_clear"
            android:layout_toRightOf="@+id/btn_extend"
            android:text="Base64导入"
            android:textSize="14sp" />

        <Button
            android:id="@+id/btn_output"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_below="@+id/btn_autoclip"
            android:layout_toRightOf="@+id/btn_import"
            android:text="base64导出"
            android:textSize="14sp"
            android:visibility="gone" />

        <Button
            android:id="@+id/btn_html"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_below="@+id/btn_clip"
            android:layout_toRightOf="@+id/btn_import"
            android:text="html"
            android:textSize="14sp" />
    </RelativeLayout>
<FrameLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent">


    <WebView
        android:id="@+id/btn_webview"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:visibility="visible"></WebView>



    <ImageView
        android:id="@+id/iv_canvas"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</FrameLayout>
</LinearLayout>

       界面长这洋,原谅像素不清晰哈 

package com.example.test.canvas;

import android.Manifest;
import android.app.Activity;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.net.Uri;
import android.os.Build;
import android.os.CpuUsageInfo;
import android.os.Environment;
import android.provider.MediaStore;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v4.content.FileProvider;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.webkit.WebView;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.Toast;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;

public class MainActivity extends Activity {
    private Button btn_resume, btn_xi, btn_cu, btn_color, btn_clear, btn_autoclip, btn_clip, btn_extend, btn_import, btn_output, btn_html;  //清空 保存 细 粗 颜色 橡皮擦 自动裁剪 裁剪 延长 base64 导入 base64导出 html->canvas
    private ImageView iv_canvas;
    private Bitmap baseBitmap;
    private Canvas canvas;
    private Paint paint;
    private int color[] =
            {
                    Color.RED, Color.GREEN, Color.BLUE, Color.BLACK
            };
    private int i = 1;
    private int Mode = 0; // 0 铅笔  1 橡皮擦
    private Paint eraserPaint = new Paint();
    private static final int CROP_CODE = 3;//剪切裁剪
    private final static int MY_PERMISSIONS_REQUEST_RECORD_STORAGE = 1;
    private final static int MY_PERMISSIONS_REQUEST_RECORD_STORAGE1 = 2;
    private List<Float> xlist = new ArrayList<>(); // x 坐标集合
    private List<Float> ylist = new ArrayList<>();  // y 坐标集合

    private Bitmap bmp;
    private int screenWidth;
    private int screenHeight;
    private WebView btn_webview;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        DisplayMetrics dm = getResources().getDisplayMetrics();
        screenWidth = dm.widthPixels;
        screenHeight = dm.heightPixels;

        // 初始化一个画笔,笔触宽度为3,铅笔,颜色为红色
        paint = new Paint();
        paint.setStrokeWidth(3);
        paint.setColor(Color.RED);
        paint.setAntiAlias(true);
        paint.setDither(true);

        // 初始化控件
        iv_canvas = (ImageView) findViewById(R.id.iv_canvas);
        btn_webview = (WebView) findViewById(R.id.btn_webview);
        btn_resume = (Button) findViewById(R.id.btn_resume);
        btn_xi = (Button) findViewById(R.id.btn_xi);
        btn_cu = (Button) findViewById(R.id.btn_cu);
        btn_color = (Button) findViewById(R.id.btn_color);
        btn_clear = (Button) findViewById(R.id.btn_clear);
        btn_autoclip = (Button) findViewById(R.id.btn_autoclip);
        btn_clip = (Button) findViewById(R.id.btn_clip);
        btn_extend = (Button) findViewById(R.id.btn_extend);
        btn_import = (Button) findViewById(R.id.btn_import);
        btn_output = (Button) findViewById(R.id.btn_output);
        btn_html = (Button) findViewById(R.id.btn_html);

        //添加点击监听
        btn_resume.setOnClickListener(click);
        btn_clear.setOnClickListener(click);
        btn_color.setOnClickListener(click);
        btn_cu.setOnClickListener(click);
        btn_xi.setOnClickListener(click);
        btn_html.setOnClickListener(click);
        btn_output.setOnClickListener(click);
        btn_import.setOnClickListener(click);
        btn_extend.setOnClickListener(click);
        btn_clip.setOnClickListener(click);
        btn_autoclip.setOnClickListener(click);


        iv_canvas.setOnTouchListener(touch);
    }

    private View.OnTouchListener touch = new View.OnTouchListener() {
        // 定义手指开始触摸的坐标
        float startX;
        float startY;

        @Override
        public boolean onTouch(View v, MotionEvent event) {
            switch (event.getAction()) {
                // 用户按下动作
                case MotionEvent.ACTION_DOWN:
                    // 第一次绘图初始化内存图片,指定背景为白色
                    if (baseBitmap == null) {
                        baseBitmap = Bitmap.createBitmap(iv_canvas.getWidth(),
                                iv_canvas.getHeight(), Bitmap.Config.ARGB_8888);
                        canvas = new Canvas(baseBitmap);
//                        canvas.drawColor(Color.WHITE);

                    }
                    // 记录开始触摸的点的坐标
                    startX = event.getX();
                    startY = event.getY();
                    xlist.add((startX));
                    ylist.add((startY));
                    break;
                // 用户手指在屏幕上移动的动作
                case MotionEvent.ACTION_MOVE:
                    // 记录移动位置的点的坐标
                    float stopX = event.getX();
                    float stopY = event.getY();
                    xlist.add((stopX));
                    ylist.add((stopY));
                    //根据两点坐标,绘制连线
                    if (Mode == 0) {
                        canvas.drawLine(startX, startY, stopX, stopY, paint);
                        canvas.save(Canvas.ALL_SAVE_FLAG);
                        canvas.restore();
                    } else if (Mode == 1) {
                        canvas.drawLine(startX, startY, stopX, stopY, eraserPaint);
                    }
                    // 更新开始点的位置
                    startX = event.getX();
                    startY = event.getY();
                    xlist.add((startX));
                    ylist.add((startY));
                    // 把图片展示到ImageView中
                    iv_canvas.setImageBitmap(baseBitmap);
                    break;
                case MotionEvent.ACTION_UP:

                    break;
                default:
                    break;
            }
            return true;
        }
    };

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (resultCode == Activity.RESULT_OK && data != null) {
            switch (requestCode) {
                case CROP_CODE:
                    //获取到裁剪后的图片的Uri进行处理

                    if (imageUri != null) {
                        Intent intent = new Intent(MainActivity.this, PhotoAty.class);
                        intent.setDataAndType(imageUri, "url");
                        startActivity(intent);
                    }
                    break;

            }
        }
    }

    private View.OnClickListener click = new View.OnClickListener() {

        @Override
        public void onClick(View v) {

            switch (v.getId()) {
                case R.id.btn_resume:
                    resumeCanvas();
                    break;
                case R.id.btn_cu:
                    paint.setStrokeWidth(10);
                    Mode = 0;
                    break;
                case R.id.btn_xi:
                    paint.setStrokeWidth(3);
                    Mode = 0;
                    break;
                case R.id.btn_color:
                    if (Mode == 0) {
                        Log.i("i的值", String.valueOf(i));
                        if (i == 4) {
                            i = 0;
                        }
                        paint.setColor(color[i]);
                        ++i;
                    }
                    break;
                case R.id.btn_clear:
                    Mode = 1;
                    eraserPaint.setAlpha(0);
                    eraserPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));  //设置图像的混合模式
                    eraserPaint.setAntiAlias(true);  //抗锯齿
                    eraserPaint.setDither(true);   //设置防抖动
                    eraserPaint.setStrokeCap(Paint.Cap.ROUND);   //设置线帽 圆形
                    eraserPaint.setStyle(Paint.Style.STROKE);   //设置橡皮擦样式  空心
                    eraserPaint.setStrokeJoin(Paint.Join.ROUND);
                    eraserPaint.setStrokeWidth(15); //设置橡皮擦宽度
//                    eraserPaint.setColor(0xFF00FF00);
//                    eraserPaint.setColor(Color.WHITE);
                    break;
                case R.id.btn_autoclip:
                    /**
                     * 第 1 步: 检查是否有相应的权限
                     */
                    boolean isAllGranted1 = checkPermissionAllGranted(
                            new String[]{
                                    Manifest.permission.RECORD_AUDIO,
                                    Manifest.permission.CAMERA,
                                    Manifest.permission.READ_EXTERNAL_STORAGE,
                                    Manifest.permission.WRITE_EXTERNAL_STORAGE
                            }
                    );
                    if (!isAllGranted1) {

                        ActivityCompat.requestPermissions(MainActivity.this,
                                new String[]{Manifest.permission.RECORD_AUDIO,
                                        Manifest.permission.CAMERA,
                                        Manifest.permission.READ_EXTERNAL_STORAGE,
                                        Manifest.permission.WRITE_EXTERNAL_STORAGE},
                                MY_PERMISSIONS_REQUEST_RECORD_STORAGE1);
                    } else {
                        if (xlist.size() != 0 && ylist.size() != 0) {
//                            removeDuplicate(xlist);
//                            removeDuplicate(ylist);
                            //x 坐标最小值 最大值
                            float max_x = Float.valueOf(Collections.max(xlist));
                            float min_x = Float.valueOf(Collections.min(xlist));
                            //y 坐标最小值 最大值
                            float max_y = Float.valueOf(Collections.max(ylist));
                            float min_y = Float.valueOf(Collections.min(ylist));
//                            canvas.clipRect(min_x, min_y, max_x, max_y);
                            float tip_x = max_x - min_x;
                            float tip_y = max_y - min_y;
                            bmp = Bitmap.createBitmap((int) (tip_x + 10),
                                    (int) (tip_y + 10), Bitmap.Config.ARGB_8888);
                            Canvas canvas1 = new Canvas(bmp);
                            canvas1.drawColor(Color.WHITE);
                            Rect mSrcRect = new Rect((int) min_x - 5, (int) min_y - 5, (int) max_x + 5, (int) max_y + 5);
                            Rect mDestRect = new Rect(0, 0, (int) (tip_x + 10), (int) (tip_y + 10));
                            canvas1.drawBitmap(baseBitmap, mSrcRect, mDestRect, null);  //把baseBitmap中需要的绘制区域mSrcRect 截取出来 放入 新创建的(宽高为需要截取的矩形区域)bmp中的mDestRect位置 在利用bmp的canvas绘制出来

                            canvas1.save(Canvas.ALL_SAVE_FLAG);
                            canvas1.restore();
                            saveBitmap1();
                        }
//                        saveBitmap();
                    }

                    break;
                case R.id.btn_clip:
                    /**
                     * 第 1 步: 检查是否有相应的权限
                     */
                    boolean isAllGranted = checkPermissionAllGranted(
                            new String[]{
                                    Manifest.permission.RECORD_AUDIO,
                                    Manifest.permission.CAMERA,
                                    Manifest.permission.READ_EXTERNAL_STORAGE,
                                    Manifest.permission.WRITE_EXTERNAL_STORAGE
                            }
                    );
                    if (!isAllGranted) {

                        ActivityCompat.requestPermissions(MainActivity.this,
                                new String[]{Manifest.permission.RECORD_AUDIO,
                                        Manifest.permission.CAMERA,
                                        Manifest.permission.READ_EXTERNAL_STORAGE,
                                        Manifest.permission.WRITE_EXTERNAL_STORAGE},
                                MY_PERMISSIONS_REQUEST_RECORD_STORAGE);
                    } else {
                        saveBitmap();
                    }

                    break;
                case R.id.btn_extend:


                    break;
                case R.id.btn_import:

                    break;
                case R.id.btn_output:


                    break;
                case R.id.btn_html:
                    btn_webview.setVisibility(View.VISIBLE);
                    StringBuilder sb = new StringBuilder();
                    // 拼接一段HTML代码
                    sb.append("<html>");
                    sb.append("<head>");
                    sb.append("<title> 欢迎您 </title>");
                    sb.append("</head>");
                    sb.append("<body>");
                    sb.append("<h2> 欢迎您访问<a href=\"http://www.cctv.com\">"
                            + "Java联盟</a></h2>");
                    sb.append("</body>");
                    sb.append("</html>");
                    //  加载、并显示HTML代码
                    btn_webview.loadDataWithBaseURL(null, sb.toString(), "text/html", "utf-8", null);

                    break;
                default:
                    break;
            }
        }
    };

    /**
     * 检查是否拥有指定的所有权限
     */
    private boolean checkPermissionAllGranted(String[] permissions) {
        for (String permission : permissions) {
            if (ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) {
                // 只要有一个权限没有被授予, 则直接返回 false
                return false;
            }
        }
        return true;
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {

        if (requestCode == MY_PERMISSIONS_REQUEST_RECORD_STORAGE) {
            boolean isAllGranted = true;
            // 判断是否所有的权限都已经授予了
            for (int grant : grantResults) {
                if (grant != PackageManager.PERMISSION_GRANTED) {
                    isAllGranted = false;
                    break;
                }
            }

            if (isAllGranted) {
                // 如果所有的权限都授予了, 则执行备份代码
                saveBitmap();

            } else {
                // 弹出对话框告诉用户需要权限的原因, 并引导用户去应用权限管理中手动打开权限按钮
                android.widget.Toast.makeText(MainActivity.this, "Permission Denied", android.widget.Toast.LENGTH_SHORT).show();
            }
            return;
        }
        if (requestCode == MY_PERMISSIONS_REQUEST_RECORD_STORAGE1) {
            boolean isAllGranted = true;
            // 判断是否所有的权限都已经授予了
            for (int grant : grantResults) {
                if (grant != PackageManager.PERMISSION_GRANTED) {
                    isAllGranted = false;
                    break;
                }
            }

            if (isAllGranted) {
                // 如果所有的权限都授予了, 则执行备份代码
                if (xlist.size() != 0 && ylist.size() != 0) {
                    removeDuplicate(xlist);
                    removeDuplicate(ylist);
                    //x 坐标最小值 最大值
                    float max_x = Float.valueOf(Collections.max(xlist));
                    float min_x = Float.valueOf(Collections.min(xlist));
                    //y 坐标最小值 最大值
                    float max_y = Float.valueOf(Collections.max(ylist));
                    float min_y = Float.valueOf(Collections.min(ylist));
//                            canvas.clipRect(min_x, min_y, max_x, max_y);
                    float tip_x = (max_x + Float.valueOf(5)) - min_x;
                    float tip_y = (max_y + Float.valueOf(5)) - min_y;
                    bmp = Bitmap.createBitmap((int) (max_x + 5),
                            (int) (max_y + 5), Bitmap.Config.ARGB_8888);
                    Canvas canvas1 = new Canvas(bmp);
                    canvas1.drawColor(Color.WHITE);

                    canvas1.drawBitmap(baseBitmap, 0, 0, null);

                    canvas1.save(Canvas.ALL_SAVE_FLAG);
                    canvas1.restore();
                    saveBitmap1();
                }

            } else {
                // 弹出对话框告诉用户需要权限的原因, 并引导用户去应用权限管理中手动打开权限按钮
                android.widget.Toast.makeText(MainActivity.this, "Permission Denied", android.widget.Toast.LENGTH_SHORT).show();
            }
            return;
        }

    }

    private Uri imageUri;

    /**
     * 保存图片到SD卡上
     */
    protected void saveBitmap() {


        try {
            File imageStorageDir = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), "Canvas");        // Create the storage directory if it does not exist
            if (!imageStorageDir.exists()) {
                imageStorageDir.mkdirs();
            }
            File file = new File(imageStorageDir + File.separator + "IMG_" + String.valueOf(System.currentTimeMillis()) + ".png");
            FileOutputStream fos = null;
            try {
                fos = new FileOutputStream(file);
                baseBitmap.compress(Bitmap.CompressFormat.PNG, 50, fos);  //压缩 写入

            } catch (FileNotFoundException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            try {
                fos.close();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            if (Build.VERSION.SDK_INT >= 24) {
                imageUri = FileProvider.getUriForFile(MainActivity.this, "com.example.test.canvas.fileprovider", file);
            } else {
                imageUri = Uri.fromFile(file);
            }
//        File file = new File(Environment.getExternalStorageDirectory(),
//                System.currentTimeMillis() + ".png");
//        imageUri = Uri.fromFile(file);
            Intent intent = new Intent("com.android.camera.action.CROP");
            //添加这一句表示对目标应用临时授权该Uri所代表的文件
            intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
            //可以选择图片类型,如果是*表明所有类型的图片
            intent.setDataAndType(imageUri, "image/*");
            // 下面这个crop = true是设置在开启的Intent中设置显示的VIEW可裁剪
            intent.putExtra("crop", "true");
            // aspectX aspectY 是宽高的比例,这里设置的是正方形(长宽比为1:1)
            intent.putExtra("aspectX", 1);
            intent.putExtra("aspectY", 1);
            // outputX outputY 是裁剪图片宽高
//            intent.putExtra("outputX", 500);
//            intent.putExtra("outputY", 500);
            intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);
            intent.putExtra("outputFormat", Bitmap.CompressFormat.JPEG.toString());
            //是否将数据保留在Bitmap中返回,true返回bitmap,false返回uri
            intent.putExtra("return-data", false);
            //裁剪后的图片Uri路径,uritempFile为Uri类变量
//            uritempFile = Uri.parse("file://" + "/" + Environment.getExternalStorageDirectory().getPath() + "/" + "small.jpg");
//            intent.putExtra(MediaStore.EXTRA_OUTPUT, uritempFile);
            startActivityForResult(intent, CROP_CODE);


//            Toast.makeText(MainActivity.this, "保存图片成功", Toast.LENGTH_SHORT).show();
        } catch (
                Exception e)

        {
            Toast.makeText(MainActivity.this, "保存图片失败", Toast.LENGTH_SHORT).show();
            e.printStackTrace();
        }

    }

    private Uri imageUri1;

    /**
     * 保存图片到SD卡上
     */
    protected void saveBitmap1() {


        try {
            File imageStorageDir = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), "Canvas");        // Create the storage directory if it does not exist
            if (!imageStorageDir.exists()) {
                imageStorageDir.mkdirs();
            }
            File file = new File(imageStorageDir + File.separator + "IMG_" + String.valueOf(System.currentTimeMillis()) + ".png");
            FileOutputStream fos = null;
            try {
                fos = new FileOutputStream(file);
                bmp.compress(Bitmap.CompressFormat.PNG, 50, fos);  //压缩 写入

            } catch (FileNotFoundException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            try {
                fos.close();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            if (Build.VERSION.SDK_INT >= 24) {
                imageUri1 = FileProvider.getUriForFile(MainActivity.this, "com.example.test.canvas.fileprovider", file);
            } else {
                imageUri1 = Uri.fromFile(file);
            }

            if (imageUri1 != null) {
                Intent intent = new Intent(MainActivity.this, PhotoAty.class);
                intent.setDataAndType(imageUri1, "url");
                startActivity(intent);
            }
        } catch (Exception e)

        {
            Toast.makeText(MainActivity.this, "保存图片失败", Toast.LENGTH_SHORT).show();
            e.printStackTrace();
        }

    }

    /**
     * 清除画板
     */
    protected void resumeCanvas() {
        // 手动清除画板的绘图,重新创建一个画板
        if (baseBitmap != null) {
            baseBitmap = Bitmap.createBitmap(iv_canvas.getWidth(),
                    iv_canvas.getHeight(), Bitmap.Config.ARGB_8888);
            canvas = new Canvas(baseBitmap);
//            canvas.drawColor(Color.WHITE);
            iv_canvas.setImageBitmap(baseBitmap);
            xlist.clear();
            ylist.clear();
            btn_webview.setVisibility(View.GONE);
            Toast.makeText(MainActivity.this, "清除画板成功,可以重新开始绘图", Toast.LENGTH_SHORT).show();
        }
    }

    public static List removeDuplicate(List list) {
        for (int i = 0; i < list.size() - 1; i++) {
            for (int j = list.size() - 1; j > i; j--) {
                if (list.get(j).equals(list.get(i))) {
                    list.remove(j);
                }
            }
        }
        return list;
    }
}

上面就是Mainactivity主界面的代码了 

这是photoaty的布局加代码 里面逻辑很简单 就是接收MainActivity传过来的url路径进行bitmap转换然后显示出来 还有个base64导出的功能 不需要可以自行去除

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#D3D3D3"
    tools:context=".PhotoAty">

    <Button
        android:id="@+id/photo_output"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:background="#ffffff"
        android:text="base64导出"
        android:textColor="#000000"
        android:textSize="14sp" />

    <ImageView
        android:id="@+id/photo_img"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:scaleType="centerCrop" />
</RelativeLayout>
package com.example.test.canvas;

import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;

import java.io.FileNotFoundException;


public class PhotoAty extends AppCompatActivity {

    private ImageView photo_img;
    private Uri uri;
    private Button photo_output;
    private Bitmap bitmap;
    private String base64 = "";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_photo_aty);
        initView();
        initListener();
    }


    private void initView() {
        bitmap = null;
        base64 = "";
        photo_img = (ImageView) findViewById(R.id.photo_img);
        Intent intent = getIntent();
        uri = intent.getData();
        bitmap = decodeUriAsBitmap(uri);
        // 把解析到的位图显示出来
        photo_img.setImageBitmap(bitmap);
        photo_output = (Button) findViewById(R.id.photo_output);
    }

    private void initListener() {
        photo_output.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (bitmap != null) {
                    base64 = Base64BitmapUtil.bitmapToBase64(bitmap);
                    Log.i("base4------", base64);
                }

            }
        });
    }

    private Bitmap decodeUriAsBitmap(Uri uri) {
        Bitmap bitmap = null;
        try {
            // 先通过getContentResolver方法获得一个ContentResolver实例,
            // 调用openInputStream(Uri)方法获得uri关联的数据流stream
            // 把上一步获得的数据流解析成为bitmap
            bitmap = BitmapFactory.decodeStream(getContentResolver().openInputStream(uri));
        } catch (FileNotFoundException e) {
            e.printStackTrace();
            return null;
        }
        return bitmap;
    }
}

下面来分析下代码

首先 初始化画笔


        // 初始化一个画笔,笔触宽度为3,铅笔,颜色为红色
        paint = new Paint();
        paint.setStrokeWidth(3);
        paint.setColor(Color.RED);
        paint.setAntiAlias(true);

 创建画笔 设置画笔宽度,颜色,抗锯齿(使线条看起来很平滑)

      然后就是手指绘图的时候了!!!

      我们知道,所有的图案都是有一条一条非常小的线段组成,当线段组成多了就成了各种各样的形状,所以我们应该去获取每次手指移动的起点坐标x,y和终点坐标x,y 然后绘制成直线,在组合起来就是我们绘图的样子.

     所以我这边就监听手指的触摸动作事件 触摸动作事件分为3种,ACTION_DOWN(手指按住)->ACTION_MOVE(手指一动)->ACTION_UP(手指抬起) 3种动作 ,我们可以先初始化内存图片baseBitmap, 去需要绘制的区域作为宽高,然后配置Bitmap.Config.ARGB_8888 64位图(android 废除了ARGB_4444位图,采用质量更好的ARGB_8888来绘制),在创建两个参数startx,starty,分别记录手指按下去那个动作记入开始的坐标,然后在手指移动的时候记录移动的坐标,也可以看作移动的时候的终点坐标stopx,stopy,在利用canvas.drawline 绘制直线,更新新的起始坐标保存下来。然后就一直重复,就可以利用canvas绘制成我们想要的形状了,然后在显示在bitmap上 这样你手指在画板上画什么就能显示什么。

下面贴代码

在OnTouch回调方法里面做相应操作

  public boolean onTouch(View v, MotionEvent event) {
            switch (event.getAction()) {
                // 用户按下动作
                case MotionEvent.ACTION_DOWN:
                    // 第一次绘图初始化内存图片,指定背景为白色
                    if (baseBitmap == null) {
                        baseBitmap = Bitmap.createBitmap(iv_canvas.getWidth(),
                                iv_canvas.getHeight(), Bitmap.Config.ARGB_8888);
                        canvas = new Canvas(baseBitmap);
                        canvas.drawColor(Color.WHITE);
                    }
                    // 记录开始触摸的点的坐标
                    startX = event.getX();
                    startY = event.getY();
                    xlist.add((startX));
                    ylist.add((startY));
                    break;
                // 用户手指在屏幕上移动的动作
                case MotionEvent.ACTION_MOVE:
                    // 记录移动位置的点的坐标
                    float stopX = event.getX();
                    float stopY = event.getY();
                    xlist.add((stopX));
                    ylist.add((stopY));
                    //根据两点坐标,绘制连线
                    if (Mode == 0) {
                        canvas.drawLine(startX, startY, stopX, stopY, paint);
                        canvas.save(Canvas.ALL_SAVE_FLAG);
                        canvas.restore();
                    } else if (Mode == 1) {
                        canvas.drawLine(startX, startY, stopX, stopY, eraserPaint);
                    }
                    // 更新开始点的位置
                    startX = event.getX();
                    startY = event.getY();
                    xlist.add((startX));
                    ylist.add((startY));
                    // 把图片展示到ImageView中
                    iv_canvas.setImageBitmap(baseBitmap);
                    break;
                case MotionEvent.ACTION_UP:

                    break;
                default:
                    break;
            }
            return true;
        }
    };

然后这就是绘图白板的基本思路了,切换颜色和更换粗细想必不用我说大家应该也会了吧,利用

paint.setStrokeWidth(variable);

variable 填写你需要改变的画笔粗细大小 值越大越粗了,值越小当然越细了,variable等于0的时候不能再细了 画笔的细度 记住!

切换颜色 

paint.setColor(color);

 color 填写你需要改变的颜色就好了,就可以呈现不一样的颜色,

 现在讲下清空画板功能,也很简单。

    /**
     * 清除画板
     */
    protected void resumeCanvas() {
        // 手动清除画板的绘图,重新创建一个画板
        if (baseBitmap != null) {
            baseBitmap = Bitmap.createBitmap(iv_canvas.getWidth(),
                    iv_canvas.getHeight(), Bitmap.Config.ARGB_8888);
            canvas = new Canvas(baseBitmap);
            canvas.drawColor(Color.WHITE);
            iv_canvas.setImageBitmap(baseBitmap);
            xlist.clear();
            ylist.clear();
            Toast.makeText(MainActivity.this, "清除画板成功,可以重新开始绘图", Toast.LENGTH_SHORT).show();
        }
    }

只要重新创建一个新的画板就好了

 现在讲解下橡皮擦功能,橡皮擦功能其实说起来也很简单,google都给我们提供好了方法

       case R.id.btn_clear:
                    Mode = 1;
                    eraserPaint.setAlpha(0);
                    eraserPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));  //设置图像的混合模式
                    eraserPaint.setAntiAlias(true);  //抗锯齿
                    eraserPaint.setDither(true);   //设置防抖动
                    eraserPaint.setStrokeCap(Paint.Cap.ROUND);   //设置线帽 圆形
                    eraserPaint.setStyle(Paint.Style.STROKE);   //设置橡皮擦样式  空心
                    eraserPaint.setStrokeJoin(Paint.Join.ROUND);  
                    eraserPaint.setStrokeWidth(15); //设置橡皮擦宽度
//                    eraserPaint.setColor(0xFF00FF00);
//                    eraserPaint.setColor(Color.WHITE);
                    break;

只要设置下透明度 ,然后设置图像的混合模式,

                    eraserPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));  //设置图像的混合模式

上面这句话必须设置,不然就不是橡皮擦了,不懂没关系。

然后就可以形成这个效果

 原谅我像素不好,没拍好。

下面讲重点了,自动裁剪功能,这个功能是因为这个白本初衷是给学生用的,学生写完后,点击提交,需要系统自动裁剪出他绘制的区域,没绘制的区域就不需要,节省文件大小。也方便查看。

原理是这样的,我们知道画布的坐标系是从左往右 X轴,从上往下Y轴。最左上角为(0,0)坐标原点。然后先创建两个对象集合。

private List<Float> xlist = new ArrayList<>(); // x 坐标集合
private List<Float> ylist = new ArrayList<>();  // y 坐标集合

分别存放绘制的所有的startx,starty,stopx,stopy。

这样当学生绘制完毕后这两个对象集合将收集了所有的坐标点,然后取出最大的x_max,y_max.最小的x_min,y_min。然后遵循canvas.drawRect 绘制矩形区域,那么这个矩形区域就是我们裁剪后所需要的学生绘制的区域,在将这个区域放在一个新的bitmap上,这个bitmap宽高跟这个矩形区域一样大小,这样就是截取了我们需要的区域。

             if (xlist.size() != 0 && ylist.size() != 0) {
//                            removeDuplicate(xlist);
//                            removeDuplicate(ylist);
                            //x 坐标最小值 最大值
                            float max_x = Float.valueOf(Collections.max(xlist));
                            float min_x = Float.valueOf(Collections.min(xlist));
                            //y 坐标最小值 最大值
                            float max_y = Float.valueOf(Collections.max(ylist));
                            float min_y = Float.valueOf(Collections.min(ylist));
//                            canvas.clipRect(min_x, min_y, max_x, max_y);
                            float tip_x = max_x - min_x;
                            float tip_y = max_y - min_y;
                            bmp = Bitmap.createBitmap((int) (tip_x + 10),
                                    (int) (tip_y + 10), Bitmap.Config.ARGB_8888);
                            Canvas canvas1 = new Canvas(bmp);
                            canvas1.drawColor(Color.WHITE);
                            Rect mSrcRect = new Rect((int) min_x - 5, (int) min_y - 5, (int) max_x + 5, (int) max_y + 5);
                            Rect mDestRect = new Rect(0, 0, (int) (tip_x+10), (int) (tip_y+10));
                            canvas1.drawBitmap(baseBitmap, mSrcRect, mDestRect, null);  //把baseBitmap中需要的绘制区域mSrcRect 截取出来 放入 新创建的(宽高为需要截取的矩形区域)bmp中的mDestRect位置 在利用bmp的canvas绘制出来

                            canvas1.save(Canvas.ALL_SAVE_FLAG);
                            canvas1.restore();
                            saveBitmap1();
                        }

//把baseBitmap中需要的绘制区域mSrcRect 截取出来 放入 新创建的(宽高为需要截取的矩形区域)bmp中的mDestRect位置 在利用bmp的canvas绘制出来 记得留点空白,这样用户看起来很舒服,不会紧贴着截取

然后在执行savebitmap方法进行保存就行了

 protected void saveBitmap1() {


        try {
            File imageStorageDir = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), "Canvas");        // Create the storage directory if it does not exist
            if (!imageStorageDir.exists()) {
                imageStorageDir.mkdirs();
            }
            File file = new File(imageStorageDir + File.separator + "IMG_" + String.valueOf(System.currentTimeMillis()) + ".png");
            FileOutputStream fos = null;
            try {
                fos = new FileOutputStream(file);
                bmp.compress(Bitmap.CompressFormat.PNG, 50, fos);  //压缩 写入

            } catch (FileNotFoundException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            try {
                fos.close();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            if (Build.VERSION.SDK_INT >= 24) {
                imageUri1 = FileProvider.getUriForFile(MainActivity.this, "com.example.test.canvas.fileprovider", file);
            } else {
                imageUri1 = Uri.fromFile(file);
            }

            if (imageUri1 != null) {
                Intent intent = new Intent(MainActivity.this, PhotoAty.class);
                intent.setDataAndType(imageUri1, "url");
                startActivity(intent);
            }
        } catch (Exception e)

        {
            Toast.makeText(MainActivity.this, "保存图片失败", Toast.LENGTH_SHORT).show();
            e.printStackTrace();
        }

    }

找到图片的路径地址,将bmp压缩写入进去就行了。

这样imageUrl1就是我们自动裁剪后的图片的地址。

大家只要拿到这个然后根据自己所需要的需求进行操作就行了。

对了,这里要进行android系统适配和检查权限。

   

 <!-- 在SDCard中创建与删除文件权限 -->
    <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />
    <!-- 往SDCard写入数据权限 -->
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />


 

android7.0 适配需要使用fileprovider,android6.0进行动态权限管理,这个我在代码里都写好了,自己去看,很简单。

手动裁剪我动用系统自带的裁剪功能

 Intent intent = new Intent("com.android.camera.action.CROP");
            //添加这一句表示对目标应用临时授权该Uri所代表的文件
            intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
            //可以选择图片类型,如果是*表明所有类型的图片
            intent.setDataAndType(imageUri, "image/*");
            // 下面这个crop = true是设置在开启的Intent中设置显示的VIEW可裁剪
            intent.putExtra("crop", "true");
            // aspectX aspectY 是宽高的比例,这里设置的是正方形(长宽比为1:1)
            intent.putExtra("aspectX", 1);
            intent.putExtra("aspectY", 1);
            // outputX outputY 是裁剪图片宽高
//            intent.putExtra("outputX", 500);
//            intent.putExtra("outputY", 500);
            intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);
            intent.putExtra("outputFormat", Bitmap.CompressFormat.JPEG.toString());
            //是否将数据保留在Bitmap中返回,true返回bitmap,false返回uri
            intent.putExtra("return-data", false);
            //裁剪后的图片Uri路径,uritempFile为Uri类变量
//            uritempFile = Uri.parse("file://" + "/" + Environment.getExternalStorageDirectory().getPath() + "/" + "small.jpg");
//            intent.putExtra(MediaStore.EXTRA_OUTPUT, uritempFile);
            startActivityForResult(intent, CROP_CODE);

代码里写的很清楚了,拿到图片的url地址 imageUri,传入自动自带的裁剪功能进行裁剪就好了,然后根据回调方法

   @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (resultCode == Activity.RESULT_OK && data != null) {
            switch (requestCode) {
                case CROP_CODE:
                    //获取到裁剪后的图片的Uri进行处理

                    if (imageUri != null) {
                        Intent intent = new Intent(MainActivity.this, PhotoAty.class);
                        intent.setDataAndType(imageUri, "url");
                        startActivity(intent);
                    }
                    break;

            }
        }
    }

做相应的操作就行了。

这就是我的画板的功能,如有不足欢迎大家指教。非常感谢各位大大能观看到最后,写的不好见谅。

       贴上我写好的demo地址,需要的可以自行下载。

这个demo 包含了我上面所说的所有功能,包括了android动态权限管理,android7.0系统适配,大家拿了可以直接应用在项目中。

CSDN地址:https://download.csdn.net/download/qq_33266474/10596433

猜你喜欢

转载自blog.csdn.net/qq_33266474/article/details/81539843