Android 白板代码实现

版权声明:本文为博主原创文章,未经博主允许不得转载。原文链接http://blog.csdn.net/adayabetter?viewmode=contents https://blog.csdn.net/adayabetter/article/details/81035262

Android 白板代码实现

上一篇文章
Android白板方案调研
讲述了Android 白板实现的一些细节和问题整理。本篇主要从代码角度展现Android 白板。

白板的调用:

public class CanvasDemoActivity extends Activity {
 @Override   
        protected void onCreate(Bundle savedInstanceState) {   
            super.onCreate(savedInstanceState);   
             setContentView(R.layout.activity_map);
            FragmentTransaction ts = getFragmentManager().beginTransaction();
            ts.add(R.id.fl_main, new WhiteBoardFragment(), "wb").commit();

activity_map.xml 布局如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >
    <FrameLayout 
        android:id="@+id/fl_main"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>



</LinearLayout>

从中可以看出直接使用了 WhiteBoardFragment, 下面看 WhiteBoardFragment.java

项目结构如下图所示:

项目结构

WhiteBoardFragment.java

package com.example.whiteboard.fragment;

import static com.example.whiteboard.bean.StrokeRecord.STROKE_TYPE_CIRCLE;
import static com.example.whiteboard.bean.StrokeRecord.STROKE_TYPE_DRAW;
import static com.example.whiteboard.bean.StrokeRecord.STROKE_TYPE_ERASER;
import static com.example.whiteboard.bean.StrokeRecord.STROKE_TYPE_LINE;
import static com.example.whiteboard.bean.StrokeRecord.STROKE_TYPE_RECTANGLE;
import static com.example.whiteboard.bean.StrokeRecord.STROKE_TYPE_TEXT;

import java.io.File;
import java.io.FileOutputStream;

import android.app.Activity;
import android.app.AlertDialog;
import android.app.Fragment;
import android.content.Context;
import android.content.DialogInterface;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.Point;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.PopupWindow;
import android.widget.RadioGroup;
import android.widget.RadioGroup.OnCheckedChangeListener;
import android.widget.SeekBar;
import android.widget.SeekBar.OnSeekBarChangeListener;
import android.widget.TextView;
import android.widget.Toast;

import com.example.test.R;
import com.example.whiteboard.SketchView;
import com.example.whiteboard.SketchView.OnDrawChangedListener;
import com.example.whiteboard.SketchView.TextWindowCallback;
import com.example.whiteboard.bean.StrokeRecord;
import com.example.whiteboard.utils.CommonUtils;
import com.example.whiteboard.utils.ScreenUtils;
import com.example.whiteboard.utils.SdCardStatus;
import com.example.whiteboard.utils.TimeUtils;
import com.example.whiteboard.utils.ViewUtils;

public class WhiteBoardFragment extends Fragment implements OnClickListener,OnDrawChangedListener{
    static final int COLOR_BLACK = Color.parseColor("#ff000000");
    static final int COLOR_RED = Color.parseColor("#ffff4444");
    static final int COLOR_GREEN = Color.parseColor("#ff99cc00");
    static final int COLOR_ORANGE = Color.parseColor("#ffffbb33");
    static final int COLOR_BLUE = Color.parseColor("#ff33b5e5");
    private static final float BTN_ALPHA = 0.4f;
    final String TAG = getClass().getSimpleName();
    public static final String FILE_PATH = SdCardStatus.getDefaulstCacheDirInSdCard() + File.separator + "sketchPhoto";
    public static final String SCREENSHOT_PATH = SdCardStatus.getDefaulstCacheDirInSdCard() + File.separator +"screenShot";

    Activity activity;//上下文
    private SketchView mSketchView; // 画布
    private ImageView btn_pen; // 画笔
    private ImageView btn_eraser;
    private ImageView btn_undo;
    private ImageView btn_redo;
    private ImageView btn_pic;
    private ImageView btn_background;
    private ImageView btn_drag;
    private ImageView btn_save;
    private ImageView btn_empty;
    private ImageView btn_screenshot;

    private int penBaseSize;
    private int textOffX,textOffY;

    private EditText saveET; // 保存文件的EditText
    private EditText textET; // 输入文字的EditText
    private AlertDialog saveDialog,dialog, screenshotDialog;

    private int mPenType = STROKE_TYPE_DRAW; // 画笔类型
    private int mPenColor = COLOR_BLACK; // 画笔颜色

    private PopupWindow mPenPopupWindow; // 画笔弹窗
    private PopupWindow mEraserPopupWindow;// 橡皮弹窗
    private PopupWindow mTextPopupWindow;// 文字输入

    private View mPenLayoutView; // 画笔布局
    private View mEraserLayoutView;// 橡皮布局
    private View mTextLayoutView; // 文字输入框

    private ImageView mPenSizeCircle, mPenAlphaCircle;
    private SeekBar mPenSizeSeekBar, mPenAlphaSeekBar;
    private RadioGroup mPenTypeGroup, mPenColorGroup;

    private ImageView mEraserSizeCircle;
    private SeekBar mEraserSizeSeekBar;

    private static boolean isScreenShoting = false; // 是否正在截屏,保持两次截屏时间间隔大于3秒

    private static Handler mHandler = new Handler(){

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            isScreenShoting = false;
        }

    };


    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        activity = getActivity();//初始化上下文
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        View rootView = inflater.inflate(R.layout.fragment_white_board, container,  false);
        initView(rootView, inflater);
        initDrawParams();//初始化绘画参数
        initPopupWindow();//初始化弹窗
        initSaveDialog();//初始化保存文件对话框
        return rootView;
    }
    private void initDrawParams() {
        //画笔宽度缩放基准参数
        Drawable circleDrawable = getResources().getDrawable(R.drawable.circle);
        if (circleDrawable != null){
            penBaseSize = circleDrawable.getIntrinsicWidth();
        }
    }

    private void initPopupWindow() {
        initPenPopupWindow();
        initEraserPopupWindow();
        initTextPop();
    }

    private void initSaveDialog(){
        saveET = new EditText(activity);
        saveET.setHint("新文件名");
        saveET.setGravity(Gravity.CENTER);
        saveET.setSingleLine();
        saveET.setInputType(EditorInfo.TYPE_CLASS_TEXT);
        saveET.setImeOptions(EditorInfo.IME_ACTION_DONE);
        saveET.setSelectAllOnFocus(true);
        saveET.setOnEditorActionListener(new TextView.OnEditorActionListener() {
            @Override
            public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
                if (actionId == EditorInfo.IME_ACTION_DONE) {
                    ScreenUtils.hideInput(saveDialog.getCurrentFocus());
                    saveDialog.dismiss();
                    String input = saveET.getText().toString();
                    Log.e("wmb", "--onEditorAction--input:"+input);
                    saveInUI(input + ".png");
                }
                return true;
            }
        });
        saveDialog = new AlertDialog.Builder(activity)
                .setTitle("请输入保存文件名")
                .setMessage("")
                .setView(saveET)
                .setPositiveButton("确定", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        ScreenUtils.hideInput(saveDialog.getCurrentFocus());
                        String input = saveET.getText().toString();
                        Log.e("wmb", "--onClick--input:"+input);
                        saveInUI(input + ".png");
                    }
                }).setNegativeButton("取消", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        ScreenUtils.hideInput(saveDialog.getCurrentFocus());
                    }
                })
                .setCancelable(false)
                .create();
    }

    /**
     * 初始化橡皮弹窗
     */
    private void initEraserPopupWindow() {
        mEraserPopupWindow = new PopupWindow(activity);
        mEraserPopupWindow.setContentView(mEraserLayoutView);
        mEraserPopupWindow.setWidth(550);
        mEraserPopupWindow.setHeight(240);
        mEraserPopupWindow.setBackgroundDrawable(new BitmapDrawable());
        mEraserPopupWindow.setOutsideTouchable(true);

        mEraserSizeSeekBar.setOnSeekBarChangeListener(new OnSeekBarChangeListener() {

            @Override
            public void onStopTrackingTouch(SeekBar arg0) {

            }

            @Override
            public void onStartTrackingTouch(SeekBar arg0) {

            }

            @Override
            public void onProgressChanged(SeekBar arg0, int progress, boolean arg2) {
                setSeekBarProgress(progress, STROKE_TYPE_ERASER);
            }
        });
        mEraserSizeSeekBar.setProgress(SketchView.DEFAULT_ERASER_SIZE);
    }

    /**
     * 初始化画笔弹窗
     */
    private void initPenPopupWindow() {
        mPenPopupWindow = new PopupWindow(activity);
        mPenPopupWindow.setContentView(mPenLayoutView);
        mPenPopupWindow.setWidth(550);
        mPenPopupWindow.setHeight(650);
        mPenPopupWindow.setBackgroundDrawable(new BitmapDrawable());
        mPenPopupWindow.setOutsideTouchable(true);
        // 画笔类型
        mPenTypeGroup.setOnCheckedChangeListener(new OnCheckedChangeListener() {

            @Override
            public void onCheckedChanged(RadioGroup group, int checkedId) {
                int resId = R.drawable.stroke_type_rbtn_draw_checked;
                switch (checkedId) {
                case R.id.type_pen:
                    mPenType = STROKE_TYPE_DRAW;
                    break;
                case R.id.type_line:
                    mPenType = STROKE_TYPE_LINE;
                    resId = R.drawable.stroke_type_rbtn_line_checked;
                    break;
                case R.id.type_circle:
                    mPenType = STROKE_TYPE_CIRCLE;
                    resId = R.drawable.stroke_type_rbtn_circle_checked;
                    break;
                case R.id.type_rectangle:
                    mPenType = STROKE_TYPE_RECTANGLE;
                    resId = R.drawable.stroke_type_rbtn_rectangle_checked;
                    break;
                case R.id.type_text:
                    mPenType = STROKE_TYPE_TEXT;
                    resId = R.drawable.stroke_type_rbtn_text_checked;
                    break;
                default:
                    break;
                }
                btn_pen.setImageResource(resId);
                mSketchView.setStrokeType(mPenType);
                mPenPopupWindow.dismiss();
            }
        });
        // 画笔颜色
        mPenColorGroup.setOnCheckedChangeListener(new OnCheckedChangeListener() {

            @Override
            public void onCheckedChanged(RadioGroup group, int checkedId) {
                switch (checkedId) {
                case R.id.color_black:
                    mPenColor = COLOR_BLACK;
                    break;
                case R.id.color_red:
                    mPenColor = COLOR_RED;
                    break;
                case R.id.color_green:
                    mPenColor = COLOR_GREEN;
                    break;
                case R.id.color_orange:
                    mPenColor = COLOR_ORANGE;
                    break;
                case R.id.color_blue:
                    mPenColor = COLOR_BLUE;
                    break;

                default:
                    break;
                }
                mSketchView.setStrokeColor(mPenColor);
            }
        });
        // 画笔大小
        mPenSizeSeekBar.setOnSeekBarChangeListener(new OnSeekBarChangeListener() {

            @Override
            public void onStopTrackingTouch(SeekBar arg0) {

            }

            @Override
            public void onStartTrackingTouch(SeekBar arg0) {

            }

            @Override
            public void onProgressChanged(SeekBar arg0, int progress, boolean arg2) {
                setSeekBarProgress(progress, STROKE_TYPE_DRAW);
            }
        });
        mPenSizeSeekBar.setProgress(SketchView.DEFAULT_STROKE_SIZE);
        // 画笔透明度
        mPenAlphaSeekBar.setOnSeekBarChangeListener(new OnSeekBarChangeListener() {

            @Override
            public void onStopTrackingTouch(SeekBar arg0) {

            }

            @Override
            public void onStartTrackingTouch(SeekBar arg0) {

            }

            @Override
            public void onProgressChanged(SeekBar arg0, int progress, boolean arg2) {
                int alpha = progress * 255 / 100; // 百分比转换为透明度
                mSketchView.setStrokeAlpha(alpha);
                mPenAlphaCircle.setAlpha(alpha);
            }
        });
        mPenAlphaSeekBar.setProgress(SketchView.DEFAULT_STROKE_ALPHA);
    }
    // 初始化文字输入
    private void initTextPop() {
        mTextPopupWindow = new PopupWindow(activity);
        mTextPopupWindow.setContentView(mTextLayoutView);
        mTextPopupWindow.setWidth(WindowManager.LayoutParams.WRAP_CONTENT);
        mTextPopupWindow.setHeight(WindowManager.LayoutParams.WRAP_CONTENT);
        mTextPopupWindow.setFocusable(true);
        mTextPopupWindow.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
        mTextPopupWindow.setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED);
//        mTextPopupWindow.setOnDismissListener(new PopupWindow.OnDismissListener() {
//            @Override
//            public void onDismiss() {
//                if (!textET.getText().toString().equals("")) {
//                    StrokeRecord record = new StrokeRecord(mPenType);
//                    record.text = textET.getText().toString();
//                }
//            }
//        });
    }

    protected void setSeekBarProgress(int progress, int strokeTypeDraw) {
        int realProgress = progress > 1 ? progress : 1;
        int newSize = Math.round(penBaseSize * realProgress / 100); // 百分比转换为大小
        int offset = Math.round((penBaseSize - newSize) / 2);
        LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(newSize, newSize);
        lp.setMargins(offset, offset, offset, offset);
        if(strokeTypeDraw == STROKE_TYPE_DRAW){
            mPenSizeCircle.setLayoutParams(lp);
        }else{
            // 橡皮
            mEraserSizeCircle.setLayoutParams(lp);
        }
        mSketchView.setSize(newSize, strokeTypeDraw);
    }

    private void initView(View root, LayoutInflater inflater){
        mSketchView = ViewUtils.findViewById(root, R.id.id_sketch_view);
        btn_pen = ViewUtils.findViewById(root, R.id.id_pen);
        btn_eraser = ViewUtils.findViewById(root, R.id.id_eraser);
        btn_undo = ViewUtils.findViewById(root, R.id.id_undo);
        btn_redo = ViewUtils.findViewById(root, R.id.id_redo);
        btn_pic = ViewUtils.findViewById(root, R.id.id_pic);
        btn_background = ViewUtils.findViewById(root, R.id.id_bacground);
        btn_drag = ViewUtils.findViewById(root, R.id.id_drag);
        btn_save = ViewUtils.findViewById(root, R.id.id_save);
        btn_empty = ViewUtils.findViewById(root, R.id.id_empty);
        btn_screenshot = ViewUtils.findViewById(root, R.id.id_screenshot);


        btn_pen.setOnClickListener(this);
        btn_eraser.setOnClickListener(this);
        btn_undo.setOnClickListener(this);
        btn_redo.setOnClickListener(this);
        btn_pic.setOnClickListener(this);
        btn_background.setOnClickListener(this);
        btn_drag.setOnClickListener(this);
        btn_save.setOnClickListener(this);
        btn_empty.setOnClickListener(this);
        btn_screenshot.setOnClickListener(this);
        mSketchView.setOnDrawChangedListener(this); // 设置绘画监听
        mSketchView.setTextWindowCallback(new TextWindowCallback() {

            @Override
            public void onText(View view, StrokeRecord record) {
                textOffX = record.textOffX;
                textOffY = record.textOffY;
                showTextPopupWindow(view, record);
            }
        });
        // 初始化画笔弹窗views
        mPenLayoutView = inflater.inflate(R.layout.popup_pen, null);
        mPenSizeCircle = ViewUtils.findViewById(mPenLayoutView, R.id.pen_size_circle);
        mPenAlphaCircle = ViewUtils.findViewById(mPenLayoutView, R.id.pen_alpha_circle);
        mPenSizeSeekBar = ViewUtils.findViewById(mPenLayoutView, R.id.pen_size_seek_bar);
        mPenAlphaSeekBar = ViewUtils.findViewById(mPenLayoutView, R.id.pen_alpha_seek_bar);
        mPenTypeGroup = ViewUtils.findViewById(mPenLayoutView, R.id.pen_type_radio_group);
        mPenColorGroup = ViewUtils.findViewById(mPenLayoutView, R.id.pen_color_radio_group);

        //初始化橡皮弹窗views
        mEraserLayoutView = inflater.inflate(R.layout.popup_eraser, null);
        mEraserSizeCircle = ViewUtils.findViewById(mEraserLayoutView, R.id.eraser_size_circle);
        mEraserSizeSeekBar = ViewUtils.findViewById(mEraserLayoutView, R.id.eraser_size_seek_bar);

        //初始化文字输入
        mTextLayoutView = inflater.inflate(R.layout.popup_text, null);
        textET = ViewUtils.findViewById(mTextLayoutView, R.id.text_pupwindow_et);
        btn_undo.setAlpha(0.4f);
        btn_redo.setAlpha(0.4f);
        showBtn(btn_pen);
    }

    @Override
    public void onClick(View view) {
        switch (view.getId()) {
        case R.id.id_pen:
            if(mSketchView.getEditMode() == SketchView.EDIT_STROKE && mSketchView.getStrokeType() != STROKE_TYPE_ERASER){
                showPopupWindow(view, STROKE_TYPE_DRAW);
            }else{
                int checkedId = mPenTypeGroup.getCheckedRadioButtonId();
                switch (checkedId) {
                case R.id.type_pen:
                    mPenType = STROKE_TYPE_DRAW;
                    break;
                case R.id.type_line:
                    mPenType = STROKE_TYPE_LINE;
                    break;
                case R.id.type_circle:
                    mPenType = STROKE_TYPE_CIRCLE;
                    break;
                case R.id.type_rectangle:
                    mPenType = STROKE_TYPE_RECTANGLE;
                    break;
                case R.id.type_text:
                    mPenType = STROKE_TYPE_TEXT;
                    break;
                default:
                    break;
                }
                mSketchView.setStrokeType(mPenType);
            }
            mSketchView.setEditMode(SketchView.EDIT_STROKE);
            showBtn(btn_pen);
            break;
        case R.id.id_eraser:
            if(mSketchView.getEditMode() == SketchView.EDIT_STROKE && mSketchView.getStrokeType() == STROKE_TYPE_ERASER){
                showPopupWindow(view, STROKE_TYPE_ERASER);
            }else{
                mSketchView.setStrokeType(STROKE_TYPE_ERASER);
            }
            mSketchView.setEditMode(SketchView.EDIT_STROKE);
            showBtn(btn_eraser);
            break;
        case R.id.id_undo:
            mSketchView.undo();
            break;
        case R.id.id_redo:
            mSketchView.redo();
            break;
        case R.id.id_pic:
            mSketchView.addPhotoByPath("persion.jpg");
            mSketchView.setEditMode(SketchView.EDIT_PHOTO);
            showBtn(btn_drag);
            break;
        case R.id.id_bacground:
            mSketchView.setBackgroundByPath("najing.jpeg");
            break;
        case R.id.id_drag:
            mSketchView.setEditMode(SketchView.EDIT_PHOTO);
            showBtn(btn_drag);
            break;
        case R.id.id_save:
            if(mSketchView.getRecordCount() == 0){
                CommonUtils.showToast("您还没有绘图呢~", 3000);
            }else{
                showSaveDialog();
            }
            break;
        case R.id.id_empty:
            askforErase();
            break;
        case R.id.id_screenshot:
            if(!isScreenShoting){
                isScreenShoting = true;
                new screenShotTast().execute(TimeUtils.getNowTimeString()+".png");
                mHandler.sendEmptyMessageDelayed(0, 3000);
            }else{
                CommonUtils.showToast("正在截屏,请降低操作速度", 3000);
            }

            break;

        default:
            break;
        }

    }
    /**
     * 截屏方法
     */
    private File screenShot(String filepath, String fileName){
        if(null == activity)
            return null;
        Point screenPoint = ScreenUtils.getScreenSize(activity);
        if(null == screenPoint)
            return null;
        int statusBarAndTitleHeight = ScreenUtils.getStatusBarAndTitleHeight(activity);
        View view = activity.getWindow().getDecorView();
        view.setDrawingCacheEnabled(true);
        Bitmap bitmapTemp = view.getDrawingCache();
        Bitmap bmp = Bitmap.createBitmap(bitmapTemp, 0, statusBarAndTitleHeight, screenPoint.x, screenPoint.y - statusBarAndTitleHeight);
        view.destroyDrawingCache();
        try {
            File dir = new File(filepath);
            if(!dir.exists()){
                boolean mk = dir.mkdirs();
            }
            File f = new File(dir, fileName);
            if(!f.exists()){
                f.createNewFile();
            }else{
                f.delete();
            }
            FileOutputStream out = new FileOutputStream(f);
            bmp.compress(Bitmap.CompressFormat.PNG, 90, out);
            out.flush();
            out.close();
            bitmapTemp.recycle();
            bmp.recycle();
            bitmapTemp = null;
            bmp = null;
            return f;

        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
    private void showTextPopupWindow(View anchor, final StrokeRecord record) {
        textET.requestFocus();
        mTextPopupWindow.showAsDropDown(anchor, record.textOffX, record.textOffY - mSketchView.getHeight());
        mTextPopupWindow.setSoftInputMode(PopupWindow.INPUT_METHOD_NEEDED);
        InputMethodManager imm = (InputMethodManager) activity
                .getSystemService(Context.INPUT_METHOD_SERVICE);
        imm.toggleSoftInput(0, InputMethodManager.HIDE_NOT_ALWAYS);
//        ScreenUtils.showInput(textET);
        mTextPopupWindow.setOnDismissListener(new PopupWindow.OnDismissListener() {
            @Override
            public void onDismiss() {
                if (!textET.getText().toString().equals("")) {
                    record.text = textET.getText().toString();
                    record.textPaint.setTextSize(textET.getTextSize());
                    record.textWidth = textET.getMaxWidth();
                    mSketchView.addStrokeRecord(record);
                }
            }
        });
    }

    private void showSaveDialog() {
        saveDialog.show();
        saveET.setText(TimeUtils.getNowTimeString());
        saveET.selectAll();
        ScreenUtils.showInput(mSketchView);
    }
    private void askforErase() {
        new AlertDialog.Builder(activity)
                .setMessage("擦除手绘?")
                .setPositiveButton("确定", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        mSketchView.erase();
                    }
                })
                .setNegativeButton("取消", new DialogInterface.OnClickListener() {

                    @Override
                    public void onClick(DialogInterface arg0, int arg1) {

                    }
                })
                .create()
                .show();
    }

    private void showBtn(ImageView iv) {
        btn_eraser.setAlpha(BTN_ALPHA);
        btn_pen.setAlpha(BTN_ALPHA);
        btn_drag.setAlpha(BTN_ALPHA);
        iv.setAlpha(1f);
    }
    private void showPopupWindow(View anchor, int drawMode){
        if(drawMode == STROKE_TYPE_DRAW){
            mPenPopupWindow.showAsDropDown(anchor, 0, 0);
        }else if(drawMode == STROKE_TYPE_ERASER){
            mEraserPopupWindow.showAsDropDown(anchor, 0, 0);
        }
    }
    private void saveInUI(final String imgName) {
        new saveToFileTask().execute(imgName);
    }
    public File saveInOI(String filePath, String imgName) {
        return saveInOI(filePath, imgName, 80);
    }

    public File saveInOI(String filePath, String imgName, int compress) {
        Log.e("wmb", "--saveInOI--filePath:" + filePath + "--imgName:"
                + imgName + "--compress:" + compress);
        if (!imgName.contains(".png")) {
            imgName += ".png";
        }
        Log.e(TAG, "saveInOI: " + System.currentTimeMillis());
        Bitmap newBM = mSketchView.getResultBitmap();
        Log.e(TAG, "saveInOI: " + System.currentTimeMillis());

        try {
            File dir = new File(filePath);
            if (!dir.exists()) {
                boolean mk = dir.mkdirs();
                Log.e("wmb", "--saveInOI--mk:" + mk);
            }
            File f = new File(filePath, imgName);
            if (!f.exists()) {
                boolean cr = f.createNewFile();
                Log.e("wmb", "--saveInOI--cr:" + cr);
            } else {
                f.delete();
            }
            FileOutputStream out = new FileOutputStream(f);
            Log.e(TAG, "saveInOI: " + System.currentTimeMillis());

            if (compress >= 1 && compress <= 100)
                newBM.compress(Bitmap.CompressFormat.PNG, compress, out);
            else {
                newBM.compress(Bitmap.CompressFormat.PNG, 80, out);
            }
            Log.e(TAG, "saveInOI: " + System.currentTimeMillis());

            out.close();
            newBM.recycle();
            newBM = null;
            return f;
        } catch (Exception e) {
            Log.e("wmb", "--e:" + e.getStackTrace());
            return null;
        }
    }

    class screenShotTast extends AsyncTask<String, Void, File> {


        @Override
        protected File doInBackground(String... screenShotName) {
            Log.e("wmb", "screenShotTast--doInBackground");
            return screenShot(SCREENSHOT_PATH, screenShotName[0]);
        }

        @Override
        protected void onPostExecute(File result) {
            super.onPostExecute(result);
            if(null != result && result.exists()){
                CommonUtils.showToast("截图成功,正在设置为画板背景...", 1000);
//              mSketchView.setBackgroundByPath(result.getAbsolutePath());
                mSketchView.addPhotoByPath(result.getAbsolutePath());
                mSketchView.setEditMode(SketchView.EDIT_PHOTO);
                showBtn(btn_drag);
            }else{
                CommonUtils.showToast("截图失败,请稍后重试", 3000);
            }
            screenshotDialog.dismiss();
        }

        @Override
        protected void onPreExecute() {
            super.onPreExecute();
            Log.e("wmb", "screenShotTast--onPreExecute");
            screenshotDialog = new AlertDialog.Builder(activity).setTitle("保存截图")
                    .setMessage("截图保存中...").show();
        }


    }

    class saveToFileTask extends AsyncTask<String, Void, File> {
        @Override
        protected void onPreExecute() {
            super.onPreExecute();
            Log.e("wmb", "--onPreExecute");
            dialog = new AlertDialog.Builder(activity)
                    .setTitle("保存画板")
                    .setMessage("保存中...")
                    .show();
        }

        @Override
        protected File doInBackground(String... photoName) {
             Log.e("wmb", "--doInBackground");
            return saveInOI(FILE_PATH, photoName[0]);
        }

        @Override
        protected void onPostExecute(File file) {
            super.onPostExecute(file);
            if (null != file && file.exists())
                Toast.makeText(activity, file.getAbsolutePath(), Toast.LENGTH_SHORT).show();
            else
                Toast.makeText(activity, "保存失败!", Toast.LENGTH_SHORT).show();
            dialog.dismiss();
        }
    }

    @Override
    public void onDrawChanged() {
        Log.e("wmb", "--onDrawChanged-StrokeRecordCount:"+mSketchView.getStrokeRecordCount()+"--RedoCount:"+mSketchView.getRedoCount());
        if(mSketchView.getStrokeRecordCount() > 0){
            btn_undo.setAlpha(1f);
        }else{
            btn_undo.setAlpha(0.4f);
        }
        if(mSketchView.getRedoCount() > 0){
            btn_redo.setAlpha(1f);
        }else{
            btn_redo.setAlpha(0.4f);
        }
    }


}

StrokeRecord.java

package com.example.whiteboard.bean;

import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PointF;
import android.graphics.RectF;
import android.text.TextPaint;
/**
 * stroke record
 * @author WMB
 *
 */
public class StrokeRecord {
    public static final int STROKE_TYPE_ERASER = 1;
    public static final int STROKE_TYPE_DRAW = 2;
    public static final int STROKE_TYPE_LINE = 3;
    public static final int STROKE_TYPE_CIRCLE = 4;
    public static final int STROKE_TYPE_RECTANGLE = 5;
    public static final int STROKE_TYPE_TEXT = 6;

    public int type;//记录类型
    public Paint paint;//笔类
    public Path path;//画笔路径数据
    public PointF[] linePoints; //线数据
    public RectF rect; //圆、矩形区域
    public String text;//文字
    public TextPaint textPaint;//笔类

    public int textOffX;
    public int textOffY;
    public int textWidth;//文字位置

    public StrokeRecord(int type) {
        this.type = type;
    }
}

PhotoRecord.java

package com.example.whiteboard.bean;

import android.graphics.Bitmap;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PointF;
import android.graphics.RectF;
import android.text.TextPaint;
/**
 * photo record
 * @author WMB
 *
 */
public class PhotoRecord {

    public Bitmap bitmap;//图形
    public Matrix matrix;//图形
    public RectF photoRectSrc = new RectF();
    public float scaleMax = 3;

}

SketchData.java

package com.example.whiteboard.bean;

import android.graphics.Bitmap;

import java.io.File;
import java.util.ArrayList;
import java.util.List;

/**
 * sketch data
 */
public class SketchData {
    public List<PhotoRecord> photoRecordList;
    public List<StrokeRecord> strokeRecordList;
    public List<StrokeRecord> strokeRedoList;
    public Bitmap thumbnailBM;//缩略图文件
    public Bitmap backgroundBM;

    public SketchData() {
        strokeRecordList = new ArrayList<>();
        photoRecordList = new ArrayList<>();
        strokeRedoList = new ArrayList<>();
        backgroundBM = null;
        thumbnailBM = null;
    }

}

DrawView.java

package com.example.whiteboard;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;

public class DrawView extends View {

    float currentX, currentY;
    Paint p = new Paint();
    Bitmap bp;

    public DrawView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    public DrawView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public DrawView(Context context) {
        super(context);
        bp = Bitmap.createBitmap(2000, 1000, Config.ARGB_4444);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        Log.e("wmb", "--onDraw--canvas:"+canvas.toString());
        p.setColor(Color.RED);
        canvas.drawCircle(currentX, currentY, 30, p);
        canvas.saveLayer(0, 0, 2000, 1000, p, Canvas.ALL_SAVE_FLAG);
//      p.setColor(Color.GREEN);
////        bp = Bitmap.createBitmap(2000, 1000, Config.ARGB_4444);
//      Canvas tmpcs = new Canvas(bp);
//      p.setStyle(Style.STROKE);
//      tmpcs.drawRect(new Rect(2, 2, 2000, 900), p);
////        tmpcs.drawColor(Color.GRAY);
//      tmpcs.drawCircle(currentX, currentY, 30, p);
//      canvas.drawBitmap(bp, 0, 0, null);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        currentX = event.getX();
        currentY = event.getY();
        Log.e("wmb", "--onTouchEvent--currentX:"+currentX+"--currentY:"+currentY);
        this.invalidate();
        return true;
    }
}

SketchView.java

package com.example.whiteboard;

import static com.example.whiteboard.bean.StrokeRecord.STROKE_TYPE_CIRCLE;
import static com.example.whiteboard.bean.StrokeRecord.STROKE_TYPE_DRAW;
import static com.example.whiteboard.bean.StrokeRecord.STROKE_TYPE_ERASER;
import static com.example.whiteboard.bean.StrokeRecord.STROKE_TYPE_LINE;
import static com.example.whiteboard.bean.StrokeRecord.STROKE_TYPE_RECTANGLE;
import static com.example.whiteboard.bean.StrokeRecord.STROKE_TYPE_TEXT;

import java.io.File;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.PaintFlagsDrawFilter;
import android.graphics.Path;
import android.graphics.PointF;
import android.graphics.PorterDuff.Mode;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.graphics.RectF;
import android.os.Environment;
import android.support.annotation.NonNull;
import android.text.Layout;
import android.text.StaticLayout;
import android.text.TextPaint;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.SurfaceHolder;
import android.view.View;
import android.view.View.OnTouchListener;
import android.widget.Toast;

import com.example.test.R;
import com.example.whiteboard.bean.PhotoRecord;
import com.example.whiteboard.bean.SketchData;
import com.example.whiteboard.bean.StrokeRecord;
import com.example.whiteboard.utils.BitmapUtils;
import com.example.whiteboard.utils.ScreenUtils;

/**
 * 画板整体布局
 *
 */
public class SketchView extends View implements OnTouchListener/*,Runnable,SurfaceHolder.Callback*/ {

    final String TAG = getClass().getSimpleName();

    public interface TextWindowCallback {
        void onText(View view, StrokeRecord record);
    }


    public void setTextWindowCallback(TextWindowCallback textWindowCallback) {
        this.textWindowCallback = textWindowCallback;
    }

    private TextWindowCallback textWindowCallback;
    private static final float TOUCH_TOLERANCE = 4;

    public static final int EDIT_STROKE = 1;
    public static final int EDIT_PHOTO = 2;

    private static final int ACTION_NONE = 0;
    private static final int ACTION_DRAG = 1;
    private static final int ACTION_SCALE = 2;
    private static final int ACTION_ROTATE = 3;

    public static final int DEFAULT_STROKE_SIZE = 7;
    public static final int DEFAULT_STROKE_ALPHA = 100;
    public static final int DEFAULT_ERASER_SIZE = 50;


    private float strokeSize = DEFAULT_STROKE_SIZE;
    private int strokeRealColor = Color.BLACK;//画笔实际颜色
    private int strokeColor = Color.BLACK;//画笔颜色
    private int strokeAlpha = 255;//画笔透明度
    private float eraserSize = DEFAULT_ERASER_SIZE;

    Paint boardPaint;

    Bitmap mirrorMarkBM = BitmapFactory.decodeResource(getResources(), R.drawable.mark_copy);
    Bitmap deleteMarkBM = BitmapFactory.decodeResource(getResources(), R.drawable.mark_delete);
    Bitmap rotateMarkBM = BitmapFactory.decodeResource(getResources(), R.drawable.mark_rotate);
    Bitmap resetMarkBM = BitmapFactory.decodeResource(getResources(), R.drawable.mark_reset);
    //    Bitmap rotateMarkBM = BitmapFactory.decodeResource(getResources(), R.drawable.test);
    RectF markerCopyRect = new RectF(0, 0, mirrorMarkBM.getWidth(), mirrorMarkBM.getHeight());//镜像标记边界
    RectF markerDeleteRect = new RectF(0, 0, deleteMarkBM.getWidth(), deleteMarkBM.getHeight());//删除标记边界
    RectF markerRotateRect = new RectF(0, 0, rotateMarkBM.getWidth(), rotateMarkBM.getHeight());//旋转标记边界
    RectF markerResetRect = new RectF(0, 0, resetMarkBM.getWidth(), resetMarkBM.getHeight());//旋转标记边界

    private Path strokePath;
    private Paint strokePaint;
//    private Paint erasePaint;
    private float downX, downY, preX, preY, curX, curY;
    private int mWidth, mHeight;
    private Bitmap   mEraseMaskBitmap = null;// 橡皮擦
    private Canvas mEraseCanvas;

    SketchData curSketchData = new SketchData();

    private Context mContext;
    Rect backgroundSrcRect = new Rect();
    Rect backgroundDstRect = new Rect();
    StrokeRecord curStrokeRecord;
    PhotoRecord curPhotoRecord;

    int actionMode;

    private int editMode = EDIT_STROKE;
    private static float SCALE_MAX = 4.0f;
    private static float SCALE_MIN = 0.2f;
    private static float SCALE_MIN_LEN;
    private boolean isFirstIn = true;
    private int mScreenWidth = 720,mScreenHeight = 1134;

    float simpleScale = 0.5f;//图片载入的缩放倍数
    /**
     * 缩放手势
     */
    private ScaleGestureDetector mScaleGestureDetector = null;
    public void setStrokeType(int strokeType) {
        this.strokeType = strokeType;
    }

    public int getStrokeType() {
        return strokeType;
    }

    private int strokeType = StrokeRecord.STROKE_TYPE_DRAW;

    private OnDrawChangedListener onDrawChangedListener;

    public void setSketchData(SketchData sketchData) {
        this.curSketchData = sketchData;
        curPhotoRecord = null;
    }

    public void updateSketchData(SketchData sketchData) {
        if (curSketchData != null)
            curSketchData.thumbnailBM = getThumbnailResultBitmap();//更新数据前先保存上一份数据的缩略图
        setSketchData(sketchData);
    }



    public SketchView(Context context, AttributeSet attr) {
        super(context, attr);
        this.mContext = context;
        setFocusable(true);
        initParams(context);
        if (isFocusable()) {
            this.setOnTouchListener(this);
            mScaleGestureDetector = new ScaleGestureDetector(context, new ScaleGestureDetector.OnScaleGestureListener() {
                @Override
                public boolean onScale(ScaleGestureDetector detector) {
                    onScaleAction(detector);
                    return true;
                }


                @Override
                public boolean onScaleBegin(ScaleGestureDetector detector) {
                    return true;
                }

                @Override
                public void onScaleEnd(ScaleGestureDetector detector) {

                }
            });
        }
        invalidate();
    }

    //初始化
    private void initBmpMask(Canvas canvas) {
        mScreenWidth = getWidth();
        mScreenHeight = getHeight();
        Log.e("iwmb", "--mScreenWidth:"+mScreenWidth+"--mScreenHeight:"+mScreenHeight);

    }

    private void initParams(Context context) {

        strokePaint = new Paint();
        strokePaint.setAntiAlias(true);
        strokePaint.setDither(true);
        strokePaint.setColor(strokeRealColor);
        strokePaint.setStyle(Paint.Style.STROKE);
        strokePaint.setStrokeJoin(Paint.Join.ROUND);
        strokePaint.setStrokeCap(Paint.Cap.ROUND);
        strokePaint.setStrokeWidth(strokeSize);

//        erasePaint = new Paint();
//        erasePaint.setAntiAlias(true);
//        erasePaint.setDither(true);
//        erasePaint.setColor(0xFF000000);
//        erasePaint.setStyle(Paint.Style.STROKE);
//        erasePaint.setStrokeJoin(Paint.Join.ROUND);
//        erasePaint.setStrokeCap(Paint.Cap.ROUND);
//        erasePaint.setStrokeWidth(200);
//        erasePaint.setXfermode(new PorterDuffXfermode(Mode.DST_OUT));

        boardPaint = new Paint();
        boardPaint.setColor(Color.GREEN);
        boardPaint.setStrokeWidth(ScreenUtils.dip2px(mContext, 0.8f));
        boardPaint.setStyle(Paint.Style.STROKE);

        SCALE_MIN_LEN = ScreenUtils.dip2px(context, 20);
    }


    public void setStrokeAlpha(int mAlpha) {
        this.strokeAlpha = mAlpha;
        calculColor();
        strokePaint.setStrokeWidth(strokeSize);
    }


    public void setStrokeColor(int color) {
        strokeColor = color;
        calculColor();
        strokePaint.setColor(strokeRealColor);
    }


    private void calculColor() {
        strokeRealColor = Color.argb(strokeAlpha, Color.red(strokeColor), Color.green(strokeColor), Color.blue(strokeColor));
    }



    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        mWidth = MeasureSpec.getSize(widthMeasureSpec);
        mHeight = MeasureSpec.getSize(heightMeasureSpec);
        setMeasuredDimension(mWidth, mHeight);
    }


    @Override
    public boolean onTouch(View arg0, MotionEvent event) {
        curX = event.getX();
        curY = event.getY();

        switch (event.getAction() & MotionEvent.ACTION_MASK) {

            case MotionEvent.ACTION_POINTER_DOWN:
                float downDistance = spacing(event);
                Log.e("wmb", "--onTouch--"+event.getAction()+"--space:"+downDistance);
                if (actionMode == ACTION_DRAG && downDistance > 10)//防止误触
                    actionMode = ACTION_SCALE;
                break;
            case MotionEvent.ACTION_DOWN:
                Log.e("wmb", "--onTouch--action down");
                touch_down(event);
                invalidate();
                break;
            case MotionEvent.ACTION_MOVE:
                Log.e("wmb", "--onTouch--action move");
                touch_move(event);
                invalidate();
                break;
            case MotionEvent.ACTION_UP:
                Log.e("wmb", "--onTouch--action up");
                touch_up();
                invalidate();
                break;
        }
        preX = curX;
        preY = curY;
        return true;
    }

    private float spacing(MotionEvent event) {
        float x = event.getX(0) - event.getX(1);
        float y = event.getY(0) - event.getY(1);
        return (float) Math.sqrt(x * x + y * y);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        if(isFirstIn){
            initBmpMask(canvas);
            Log.e("iwmb", "--drawRecord isFristIN");
            isFirstIn = false;
        }
        drawBackground(canvas);
        drawRecord(canvas);
//        if (onDrawChangedListener != null)
//            onDrawChangedListener.onDrawChanged();
    }

    private void drawBackground(Canvas canvas) {
        if (curSketchData.backgroundBM != null) {
            //缩放图片,使之能够在屏幕中完整显示(大图压缩、小图放大)
            Matrix matrix = new Matrix();
            float wScale = (float) canvas.getWidth() / curSketchData.backgroundBM.getWidth();
            float hScale = (float) canvas.getHeight() / curSketchData.backgroundBM.getHeight();
            matrix.postScale(wScale, hScale);
            canvas.drawBitmap(curSketchData.backgroundBM, matrix, null);
            Log.e("iwmb", "--canvas.getWidth():"+canvas.getWidth()+"--canvas.getHeight():"+canvas.getHeight());
            Log.d(TAG, "drawBackground:src= " + backgroundSrcRect.toString() + ";dst=" + backgroundDstRect.toString());
        } else {
            canvas.drawColor(Color.WHITE);
        }

    }

    private void drawRecord(Canvas canvas) {
        drawRecord(canvas, true);
    }

    private void drawRecord(Canvas canvas, boolean isDrawBoard) {

        mEraseMaskBitmap = Bitmap.createBitmap(mScreenWidth, mScreenHeight, Bitmap.Config.ARGB_8888);

        mEraseCanvas = new Canvas(mEraseMaskBitmap);

        mEraseCanvas.drawColor(Color.TRANSPARENT);

        if (curSketchData != null) {
            for (PhotoRecord record : curSketchData.photoRecordList) {
                if (record != null)
                    canvas.drawBitmap(record.bitmap, record.matrix, null);
            }
            if (isDrawBoard && editMode == EDIT_PHOTO && curPhotoRecord != null) {
                SCALE_MAX = curPhotoRecord.scaleMax;
                float[] photoCorners = calculateCorners(curPhotoRecord);//计算图片四个角点和中心点
                drawBoard(canvas, photoCorners);//绘制图形边线
                drawMarks(canvas, photoCorners);//绘制边角图片
            }

            for (StrokeRecord record : curSketchData.strokeRecordList) {
                int type = record.type;
                if (type == StrokeRecord.STROKE_TYPE_ERASER){ // 橡皮擦
                    mEraseCanvas.drawPath(record.path, record.paint);

                } else if (type == StrokeRecord.STROKE_TYPE_DRAW || type == StrokeRecord.STROKE_TYPE_LINE) {
                    Log.e("iwmb", "--drawRecord:--path:"+record.path.toString()+"--type:"+type);
//                    canvas.drawPath(record.path, record.paint);
                    mEraseCanvas.drawPath(record.path, record.paint);
                } else if (type == STROKE_TYPE_CIRCLE) {
                    Log.e("iwmb", "--drawcircle:--path:"+record.rect.toString()+"--type:"+type);
//                    canvas.drawOval(record.rect, record.paint);
                    mEraseCanvas.drawOval(record.rect, record.paint);
                } else if (type == STROKE_TYPE_RECTANGLE) {
//                    canvas.drawRect(record.rect, record.paint);
                    mEraseCanvas.drawRect(record.rect, record.paint);
                } else if (type == STROKE_TYPE_TEXT) {
                    if (record.text != null) {
                        StaticLayout layout = new StaticLayout(record.text, record.textPaint, record.textWidth, Layout.Alignment.ALIGN_NORMAL, 1.0F, 0.0F, true);
//                        canvas.translate(record.textOffX, record.textOffY);
//                        layout.draw(canvas);
//                        canvas.translate(-record.textOffX, -record.textOffY);
                        mEraseCanvas.translate(record.textOffX, record.textOffY);
                        layout.draw(mEraseCanvas);
                        mEraseCanvas.translate(-record.textOffX, -record.textOffY);
                    }
                }

            }
            canvas.drawBitmap(mEraseMaskBitmap, 0, 0, null);
        }

    }

    //绘制图像边线(由于图形旋转或不一定是矩形,所以用Path绘制边线)
    private void drawBoard(Canvas canvas, float[] photoCorners) {
        Path photoBorderPath = new Path();
        photoBorderPath.moveTo(photoCorners[0], photoCorners[1]);
        photoBorderPath.lineTo(photoCorners[2], photoCorners[3]);
        photoBorderPath.lineTo(photoCorners[4], photoCorners[5]);
        photoBorderPath.lineTo(photoCorners[6], photoCorners[7]);
        photoBorderPath.lineTo(photoCorners[0], photoCorners[1]);
        canvas.drawPath(photoBorderPath, boardPaint);
    }

    //绘制边角操作图标
    private void drawMarks(Canvas canvas, float[] photoCorners) {
        float x;
        float y;
        x = photoCorners[0] - markerCopyRect.width() / 2;
        y = photoCorners[1] - markerCopyRect.height() / 2;
        markerCopyRect.offsetTo(x, y);
        canvas.drawBitmap(mirrorMarkBM, x, y, null);

        x = photoCorners[2] - markerDeleteRect.width() / 2;
        y = photoCorners[3] - markerDeleteRect.height() / 2;
        markerDeleteRect.offsetTo(x, y);
        canvas.drawBitmap(deleteMarkBM, x, y, null);

        x = photoCorners[4] - markerRotateRect.width() / 2;
        y = photoCorners[5] - markerRotateRect.height() / 2;
        markerRotateRect.offsetTo(x, y);
        canvas.drawBitmap(rotateMarkBM, x, y, null);

        x = photoCorners[6] - markerResetRect.width() / 2;
        y = photoCorners[7] - markerResetRect.height() / 2;
        markerResetRect.offsetTo(x, y);
        canvas.drawBitmap(resetMarkBM, x, y, null);
    }

    private float[] calculateCorners(PhotoRecord record) {
        float[] photoCornersSrc = new float[10];//0,1代表左上角点XY,2,3代表右上角点XY,4,5代表右下角点XY,6,7代表左下角点XY,8,9代表中心点XY
        float[] photoCorners = new float[10];//0,1代表左上角点XY,2,3代表右上角点XY,4,5代表右下角点XY,6,7代表左下角点XY,8,9代表中心点XY
        RectF rectF = record.photoRectSrc;
        photoCornersSrc[0] = rectF.left;
        photoCornersSrc[1] = rectF.top;
        photoCornersSrc[2] = rectF.right;
        photoCornersSrc[3] = rectF.top;
        photoCornersSrc[4] = rectF.right;
        photoCornersSrc[5] = rectF.bottom;
        photoCornersSrc[6] = rectF.left;
        photoCornersSrc[7] = rectF.bottom;
        photoCornersSrc[8] = rectF.centerX();
        photoCornersSrc[9] = rectF.centerY();
        curPhotoRecord.matrix.mapPoints(photoCorners, photoCornersSrc);
        return photoCorners;
    }

    private float getMaxScale(RectF photoSrc) {
        return Math.max(getWidth(), getHeight()) / Math.max(photoSrc.width(), photoSrc.height());
//        SCALE_MIN = SCALE_MAX / 5;
    }

    public void addStrokeRecord(StrokeRecord record) {
        curSketchData.strokeRecordList.add(record);
        invalidate();
    }

    private void touch_down(MotionEvent event) {
        downX = event.getX();
        downY = event.getY();
        if (editMode == EDIT_STROKE) {
            curSketchData.strokeRedoList.clear();
            curStrokeRecord = new StrokeRecord(strokeType);
            if (strokeType == STROKE_TYPE_ERASER) {
                strokePath = new Path();
                strokePath.moveTo(downX, downY);
                curStrokeRecord.paint = new Paint(strokePaint); // Clones the mPaint object
                curStrokeRecord.paint.setXfermode(new PorterDuffXfermode(Mode.DST_OUT));
                curStrokeRecord.paint.setStrokeWidth(eraserSize);
                curStrokeRecord.path = strokePath;
            } else if (strokeType == STROKE_TYPE_DRAW || strokeType == STROKE_TYPE_LINE) {
                strokePath = new Path();
                strokePath.moveTo(downX, downY);
                curStrokeRecord.path = strokePath;
                strokePaint.setColor(strokeRealColor);
                strokePaint.setStrokeWidth(strokeSize);
                curStrokeRecord.paint = new Paint(strokePaint); // Clones the mPaint object
            } else if (strokeType == STROKE_TYPE_CIRCLE || strokeType == STROKE_TYPE_RECTANGLE) {
                RectF rect = new RectF(downX, downY, downX, downY);
                curStrokeRecord.rect = rect;
                strokePaint.setColor(strokeRealColor);
                strokePaint.setStrokeWidth(strokeSize);
                curStrokeRecord.paint = new Paint(strokePaint); // Clones the mPaint object
            } else if (strokeType == STROKE_TYPE_TEXT) {
                curStrokeRecord.textOffX = (int) downX;
                curStrokeRecord.textOffY = (int) downY;
                TextPaint tp = new TextPaint();
                tp.setColor(strokeRealColor);
                curStrokeRecord.textPaint = tp; // Clones the mPaint object
                textWindowCallback.onText(this, curStrokeRecord);
                return;
            }
            curSketchData.strokeRecordList.add(curStrokeRecord);
        } else if (editMode == EDIT_PHOTO) {
            float[] downPoint = new float[]{downX, downY};
            if (isInMarkRect(downPoint)) {// 先判操作标记区域
                return;
            }
            if (isInPhotoRect(curPhotoRecord, downPoint)) {//再判断是否点击了当前图片
                actionMode = ACTION_DRAG;
                return;
            }
            selectPhoto(downPoint);//最后判断是否点击了其他图片
        }
    }

    //judge click which photo,then can edit the photo
    private void selectPhoto(float[] downPoint) {
        PhotoRecord clickRecord = null;
        for (int i = curSketchData.photoRecordList.size() - 1; i >= 0; i--) {
            PhotoRecord record = curSketchData.photoRecordList.get(i);
            if (isInPhotoRect(record, downPoint)) {
                clickRecord = record;
                break;
            }
        }
        if (clickRecord != null) {
            setCurPhotoRecord(clickRecord);
            actionMode = ACTION_DRAG;
        } else {
            actionMode = ACTION_NONE;
        }
    }

    private boolean isInMarkRect(float[] downPoint) {
        if (markerRotateRect.contains(downPoint[0], (int) downPoint[1])) {//判断是否在区域内
            actionMode = ACTION_ROTATE;
            return true;
        }
        if (markerDeleteRect.contains(downPoint[0], (int) downPoint[1])) {//判断是否在区域内
            curSketchData.photoRecordList.remove(curPhotoRecord);
            setCurPhotoRecord(null);
            actionMode = ACTION_NONE;
            return true;
        }
        if (markerCopyRect.contains(downPoint[0], (int) downPoint[1])) {//判断是否在区域内
            PhotoRecord newRecord = initPhotoRecord(curPhotoRecord.bitmap);
            newRecord.matrix = new Matrix(curPhotoRecord.matrix);
            newRecord.matrix.postTranslate(ScreenUtils.dip2px(mContext, 20), ScreenUtils.dip2px(mContext, 20));//偏移小段距离以分辨新复制的图片
            setCurPhotoRecord(newRecord);
            actionMode = ACTION_NONE;
            return true;
        }
        if (markerResetRect.contains(downPoint[0], (int) downPoint[1])) {//判断是否在区域内
            curPhotoRecord.matrix.reset();
            curPhotoRecord.matrix.setTranslate(getWidth() / 2 - curPhotoRecord.photoRectSrc.width() / 2,
                    getHeight() / 2 - curPhotoRecord.photoRectSrc.height() / 2);
            actionMode = ACTION_NONE;
            return true;
        }
        return false;
    }

    private boolean isInPhotoRect(PhotoRecord record, float[] downPoint) {
        if (record != null) {
            float[] invertPoint = new float[2];
            Matrix invertMatrix = new Matrix();
            record.matrix.invert(invertMatrix);
            invertMatrix.mapPoints(invertPoint, downPoint);
            return record.photoRectSrc.contains(invertPoint[0], invertPoint[1]);
        }
        return false;
    }


    private void touch_move(MotionEvent event) {
        if (editMode == EDIT_STROKE) {
            if (strokeType == STROKE_TYPE_ERASER) {
                strokePath.quadTo(preX, preY, (curX + preX) / 2, (curY + preY) / 2);
//              curStrokeRecord.path.quadTo(preX, preY, (curX + preX) / 2, (curY + preY) / 2);
            } else if (strokeType == STROKE_TYPE_DRAW) {
                strokePath.quadTo(preX, preY, (curX + preX) / 2, (curY + preY) / 2);
//              curStrokeRecord.path.quadTo(preX, preY, (curX + preX) / 2, (curY + preY) / 2);
            } else if (strokeType == STROKE_TYPE_LINE) {
                strokePath.reset();
                strokePath.moveTo(downX, downY);
                strokePath.lineTo(curX, curY);
//              curStrokeRecord.path.reset();
//              curStrokeRecord.path.moveTo(downX, downY);
//              curStrokeRecord.path.lineTo(curX, curY);
            } else if (strokeType == STROKE_TYPE_CIRCLE || strokeType == STROKE_TYPE_RECTANGLE) {
                curStrokeRecord.rect.set(downX < curX ? downX : curX, downY < curY ? downY : curY, downX > curX ? downX : curX, downY > curY ? downY : curY);
            } else if (strokeType == STROKE_TYPE_TEXT) {

            }
        } else if (editMode == EDIT_PHOTO && curPhotoRecord != null) {
            if (actionMode == ACTION_DRAG) {
                onDragAction(curX - preX, curY - preY);
            } else if (actionMode == ACTION_ROTATE) {
                onRotateAction(curPhotoRecord);
            } else if (actionMode == ACTION_SCALE) {
                mScaleGestureDetector.onTouchEvent(event);
            }
        }
        preX = curX;
        preY = curY;
    }

    private void onScaleAction(ScaleGestureDetector detector) {
        float[] photoCorners = calculateCorners(curPhotoRecord);
        //目前图片对角线长度
        float len = (float) Math.sqrt(Math.pow(photoCorners[0] - photoCorners[4], 2) + Math.pow(photoCorners[1] - photoCorners[5], 2));
        double photoLen = Math.sqrt(Math.pow(curPhotoRecord.photoRectSrc.width(), 2) + Math.pow(curPhotoRecord.photoRectSrc.height(), 2));
        float scaleFactor = detector.getScaleFactor();
        //设置Matrix缩放参数
        if ((scaleFactor < 1 && len >= photoLen * SCALE_MIN && len >= SCALE_MIN_LEN) || (scaleFactor > 1 && len <= photoLen * SCALE_MAX)) {
            Log.e(scaleFactor + "", scaleFactor + "");
            curPhotoRecord.matrix.postScale(scaleFactor, scaleFactor, photoCorners[8], photoCorners[9]);
        }
    }

    private void onRotateAction(PhotoRecord record) {
        float[] corners = calculateCorners(record);
        //放大
        //目前触摸点与图片显示中心距离
        float a = (float) Math.sqrt(Math.pow(curX - corners[8], 2) + Math.pow(curY - corners[9], 2));
        //目前上次旋转图标与图片显示中心距离
        float b = (float) Math.sqrt(Math.pow(corners[4] - corners[0], 2) + Math.pow(corners[5] - corners[1], 2)) / 2;

        //设置Matrix缩放参数
        double photoLen = Math.sqrt(Math.pow(record.photoRectSrc.width(), 2) + Math.pow(record.photoRectSrc.height(), 2));
        if (a >= photoLen / 2 * SCALE_MIN && a >= SCALE_MIN_LEN && a <= photoLen / 2 * SCALE_MAX) {
            //这种计算方法可以保持旋转图标坐标与触摸点同步缩放
            float scale = a / b;
            record.matrix.postScale(scale, scale, corners[8], corners[9]);
        }

        //旋转
        //根据移动坐标的变化构建两个向量,以便计算两个向量角度.
        PointF preVector = new PointF();
        PointF curVector = new PointF();
        preVector.set(preX - corners[8], preY - corners[9]);//旋转后向量
        curVector.set(curX - corners[8], curY - corners[9]);//旋转前向量
        //计算向量长度
        double preVectorLen = getVectorLength(preVector);
        double curVectorLen = getVectorLength(curVector);
        //计算两个向量的夹角.
        double cosAlpha = (preVector.x * curVector.x + preVector.y * curVector.y)
                / (preVectorLen * curVectorLen);
        //由于计算误差,可能会带来略大于1的cos,例如
        if (cosAlpha > 1.0f) {
            cosAlpha = 1.0f;
        }
        //本次的角度已经计算出来。
        double dAngle = Math.acos(cosAlpha) * 180.0 / Math.PI;
        // 判断顺时针和逆时针.
        //判断方法其实很简单,这里的v1v2其实相差角度很小的。
        //先转换成单位向量
        preVector.x /= preVectorLen;
        preVector.y /= preVectorLen;
        curVector.x /= curVectorLen;
        curVector.y /= curVectorLen;
        //作curVector的逆时针垂直向量。
        PointF verticalVec = new PointF(curVector.y, -curVector.x);

        //判断这个垂直向量和v1的点积,点积>0表示俩向量夹角锐角。=0表示垂直,<0表示钝角
        float vDot = preVector.x * verticalVec.x + preVector.y * verticalVec.y;
        if (vDot > 0) {
            //v2的逆时针垂直向量和v1是锐角关系,说明v1在v2的逆时针方向。
        } else {
            dAngle = -dAngle;
        }
        record.matrix.postRotate((float) dAngle, corners[8], corners[9]);
    }

    /**
     * 获取p1到p2的线段的长度
     *
     * @return
     */
    double getVectorLength(PointF vector) {
        return Math.sqrt(vector.x * vector.x + vector.y * vector.y);
    }
    private void onDragAction(float distanceX, float distanceY) {
            curPhotoRecord.matrix.postTranslate((int) distanceX, (int) distanceY);
    }


    private void touch_up() {
        if (onDrawChangedListener != null)
          onDrawChangedListener.onDrawChanged();
    }


    @NonNull
    public Bitmap getResultBitmap() {
        Bitmap newBM = Bitmap.createBitmap(mWidth, mHeight, Bitmap.Config.RGB_565);
        Canvas canvas = new Canvas(newBM);
        canvas.setDrawFilter(new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG));//抗锯齿
        //绘制背景
        drawBackground(canvas);
        drawRecord(canvas, false);
        canvas.save(Canvas.ALL_SAVE_FLAG);
        canvas.restore();
        return newBM;
    }

    @NonNull
    public void createCurThumbnailBM() {
        curSketchData.thumbnailBM = getThumbnailResultBitmap();
    }

    @NonNull
    public Bitmap getThumbnailResultBitmap() {
        return BitmapUtils.createBitmapThumbnail(getResultBitmap(), true, ScreenUtils.dip2px(mContext, 200), ScreenUtils.dip2px(mContext, 200));
    }
    /*
     * 删除一笔
     */
    public void undo() {
        if (curSketchData.strokeRecordList.size() > 0) {
            curSketchData.strokeRedoList.add(curSketchData.strokeRecordList.get(curSketchData.strokeRecordList.size() - 1));
            curSketchData.strokeRecordList.remove(curSketchData.strokeRecordList.size() - 1);
            invalidate();
            touch_up(); // refresh redo/undo icon status
        }
    }


    /*
     * 撤销
     */
    public void redo() {
        if (curSketchData.strokeRedoList.size() > 0) {
            curSketchData.strokeRecordList.add(curSketchData.strokeRedoList.get(curSketchData.strokeRedoList.size() - 1));
            curSketchData.strokeRedoList.remove(curSketchData.strokeRedoList.size() - 1);
            invalidate();
            touch_up(); // refresh redo/undo icon status
        }

    }


    public int getRedoCount() {
        return curSketchData.strokeRedoList != null ? curSketchData.strokeRedoList.size() : 0;
    }


    public int getRecordCount() {
        return (curSketchData.strokeRecordList != null && curSketchData.photoRecordList != null) ? curSketchData.strokeRecordList.size() + curSketchData.photoRecordList.size() : 0;
    }

    public int getStrokeRecordCount() {
        return curSketchData.strokeRecordList != null ? curSketchData.strokeRecordList.size() : 0;
    }


    public int getStrokeSize() {
        return Math.round(this.strokeSize);
    }


    public void setSize(int size, int eraserOrStroke) {
        switch (eraserOrStroke) {
            case STROKE_TYPE_DRAW:
                strokeSize = size;
                break;
            case STROKE_TYPE_ERASER:
                eraserSize = size;
                break;
        }

    }


    public void erase() {
        // 先判断是否已经回收
        for (PhotoRecord record : curSketchData.photoRecordList) {
            if (record != null && record.bitmap != null && !record.bitmap.isRecycled()) {
                record.bitmap.recycle();
                record.bitmap = null;
            }
        }
        if (curSketchData.backgroundBM != null && !curSketchData.backgroundBM.isRecycled()) {
            // 回收并且置为null
            curSketchData.backgroundBM.recycle();
            curSketchData.backgroundBM = null;
        }
        curSketchData.strokeRecordList.clear();
        curSketchData.photoRecordList.clear();
        curSketchData.strokeRedoList.clear();
        curPhotoRecord = null;
        System.gc();
        invalidate();
    }


    public void setOnDrawChangedListener(OnDrawChangedListener listener) {
        this.onDrawChangedListener = listener;
    }

    public interface OnDrawChangedListener {

        public void onDrawChanged();
    }

    public void addPhotoByPath(String path) {
        Bitmap sampleBM = getSampleBitMap(path);
        if (sampleBM != null) {
            PhotoRecord newRecord = initPhotoRecord(sampleBM);
            setCurPhotoRecord(newRecord);
        } else {
            Toast.makeText(mContext, "图片文件路径有误!", Toast.LENGTH_SHORT).show();
        }
    }

    public void setBackgroundByPath(String path) {
        Bitmap sampleBM = getSampleBitMap(path);
        if (sampleBM != null) {
            curSketchData.backgroundBM = sampleBM;
            backgroundSrcRect = new Rect(0, 0, curSketchData.backgroundBM.getWidth(), curSketchData.backgroundBM.getHeight());
            backgroundDstRect = new Rect(0, 0, mWidth, mHeight);
            invalidate();
        } else {
            Toast.makeText(mContext, "图片文件路径有误!", Toast.LENGTH_SHORT).show();
        }
    }

    public Bitmap getSampleBitMap(String path) {
        Bitmap sampleBM = null;
        if (path.contains(Environment.getExternalStorageDirectory().toString())) {
            sampleBM = getSDCardPhoto(path);
        } else {
            sampleBM = getAssetsPhoto(path);
        }
        return sampleBM;
    }

    @NonNull
    private PhotoRecord initPhotoRecord(Bitmap bitmap) {
        PhotoRecord newRecord = new PhotoRecord();
        newRecord.bitmap = bitmap;
        newRecord.photoRectSrc = new RectF(0, 0, newRecord.bitmap.getWidth(), newRecord.bitmap.getHeight());
        newRecord.scaleMax = getMaxScale(newRecord.photoRectSrc);//放大倍数
        newRecord.matrix = new Matrix();
        newRecord.matrix.postTranslate(getWidth() / 2 - bitmap.getWidth() / 2, getHeight() / 2 - bitmap.getHeight() / 2);
        return newRecord;
    }

    private void setCurPhotoRecord(PhotoRecord record) {
        curSketchData.photoRecordList.remove(record);
        curSketchData.photoRecordList.add(record);
        curPhotoRecord = record;
        invalidate();
    }

    public Bitmap getSDCardPhoto(String path) {
        File file = new File(path);
        if (file.exists()) {
            return BitmapUtils.decodeSampleBitMapFromFile(mContext, path, simpleScale);
        } else {
            return null;
        }
    }

    public Bitmap getAssetsPhoto(String path) {
        return BitmapUtils.getBitmapFromAssets(mContext, path);
    }

    public void setEditMode(int editMode) {
        this.editMode = editMode;
        invalidate();
    }

    public int getEditMode() {
        return editMode;
    }

}

WhiteBoardView.java

package com.example.whiteboard;

import java.util.ArrayList;
import java.util.List;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.graphics.Path;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
/**
 * white board view
 * you can draw on it 
 * @author WMB
 *
 */
public class WhiteBoardView extends View{
    private Paint mPaint =  new Paint();
    private List<Path> mPathList = new ArrayList<Path>();
    private Path mPath = new Path();
    private float mX,mY;
    private Bitmap tempBM;

    public WhiteBoardView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    public WhiteBoardView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public WhiteBoardView(Context context) {
        super(context);
        mPaint.setAntiAlias(true);
        mPaint.setStyle(Style.STROKE);
        mPaint.setStrokeWidth(10);
        mPaint.setColor(Color.BLACK);
        tempBM = Bitmap.createBitmap(2000, 1500, Config.ARGB_4444);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction() & MotionEvent.ACTION_MASK) {
        case MotionEvent.ACTION_POINTER_DOWN:
            Log.e("wmb", "--ACTION_POINTER_DOWN");
            break;
        case MotionEvent.ACTION_POINTER_UP:
            Log.e("wmb", "--ACTION_POINTER_UP");
            break;
        case MotionEvent.ACTION_DOWN:
            Log.e("wmb", "--action donw");
            touch_down(event);
            invalidate();
            break;
        case MotionEvent.ACTION_MOVE:
            Log.e("wmb", "--action move");
            touch_move(event);
            invalidate();
            break;

        case MotionEvent.ACTION_UP:
            Log.e("wmb", "--action up");
            touch_up(event);
            invalidate();
            break;

        default:
            break;
        }

        return true;
    }

    private void touch_up(MotionEvent event) {
    }

    private void touch_move(MotionEvent event) {
        float x = event.getX();
        float y = event.getY();
        float preX = mX;
        float preY = mY;
        float dx = Math.abs(preX - x);
        float dy = Math.abs(preY - y); 
//      if(dx >= 3 || dy >= 3){
            float cX = (x + preX) / 2;
            float cY = (y + preY) / 2;
            mPath.reset();
            mPath.moveTo(preX, preY);
            mPath.lineTo(x, y);/*(x, y, cX, cY);*/
//          mX = x;
//          mY = y;
//      }

    }

    private void touch_down(MotionEvent event) {
//      mPath.reset();
        float x = event.getX();
        float y = event.getY();
        mX = x;
        mY = y;
        mPath.moveTo(x, y);
    }

    @Override
    protected void onDraw(Canvas canvas) {
//      for (int i = 0; i < mPathList.size(); i++) {
//          Log.e("wmb", "--ondraw--i:"+i);
        canvas.drawColor(Color.GREEN);
        tempBM = Bitmap.createBitmap(2000, 1500, Config.ARGB_4444);
        Canvas tmpCs = new Canvas(tempBM);
        tmpCs.drawPath(mPath, mPaint);
        canvas.drawBitmap(tempBM, 0, 0, null);
//          canvas.drawPath(mPath, mPaint);
//          canvas.save();
        }

//      super.onDraw(canvas);
    }

BitmapUtils.java

package com.example.whiteboard.utils;

import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Matrix;
import android.util.Log;
import android.view.WindowManager;

import java.io.InputStream;

/**
 * Created by TangentLu on 2015/8/19.
 */
public class BitmapUtils {

    /**
     * 是否为横屏
     * @param context
     * @return
     */
    public static boolean isLandScreen(Context context) {
        int ori =context.getResources().getConfiguration().orientation;//获取屏幕方向
        return ori == Configuration.ORIENTATION_LANDSCAPE;
    }

    public static Bitmap decodeSampleBitMapFromFile(Context context, String filePath, float sampleScale) {
        //先得到bitmap的高宽
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeFile(filePath, options);
        //再用屏幕一半高宽、缩小后的高宽对比,取小值进行缩放
        WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
        int reqWidth = wm.getDefaultDisplay().getWidth();
        int reqHeight = wm.getDefaultDisplay().getWidth();
        int scaleWidth = (int) (options.outWidth * sampleScale);
        int scaleHeight = (int) (options.outHeight * sampleScale);
        reqWidth = Math.min(reqWidth, scaleWidth);
        reqHeight = Math.min(reqHeight, scaleHeight);
        options = sampleBitmapOptions(context, options, reqWidth, reqHeight);
        Bitmap bm = BitmapFactory.decodeFile(filePath, options);
        Log.e("xxx", bm.getByteCount() + "");
        return bm;
    }
    public static Bitmap decodeSampleBitMapFromResource(Context context, int resId, int reqWidth, int reqHeight) {
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeResource(context.getResources(), resId, options);
        options = sampleBitmapOptions(context, options, reqWidth, reqHeight);
        Bitmap bm = BitmapFactory.decodeResource(context.getResources(), resId, options);
        Log.e("xxx", bm.getByteCount() + "");
        return bm;
    }


    public static Bitmap createBitmapThumbnail(Bitmap bitMap, boolean needRecycle, int newHeight, int newWidth) {
        int width = bitMap.getWidth();
        int height = bitMap.getHeight();
        // 计算缩放比例
        float scale = Math.min((float) newWidth / width, (float) (newHeight) / height);
        // 取得想要缩放的matrix参数
        Matrix matrix = new Matrix();
        matrix.postScale(scale, scale);
        // 得到新的图片
        Bitmap newBitMap = Bitmap.createBitmap(bitMap, 0, 0, width, height, matrix, true);
        if (needRecycle)
            bitMap.recycle();
        return newBitMap;
    }

    public static BitmapFactory.Options sampleBitmapOptions(
            Context context, BitmapFactory.Options options, int reqWidth, int reqHeight) {
        int targetDensity = context.getResources().getDisplayMetrics().densityDpi;
        // Calculate inSampleSize
        options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
        double xSScale = ((double) options.outWidth) / ((double) reqWidth);
        double ySScale = ((double) options.outHeight) / ((double) reqHeight);

        double startScale = xSScale > ySScale ? xSScale : ySScale;

        options.inScaled = true;
        options.inDensity = (int) (targetDensity * startScale);
        options.inTargetDensity = targetDensity;
        options.inJustDecodeBounds = false;
        options.inPreferredConfig = Bitmap.Config.RGB_565;
        return options;
    }
    public static int calculateInSampleSize(
            BitmapFactory.Options options, int reqWidth, int reqHeight) {
        // Raw height and width of image
        final int height = options.outHeight;
        final int width = options.outWidth;
        int inSampleSize = 1;

        if (height > reqHeight || width > reqWidth) {

            final int halfHeight = height / 2;
            final int halfWidth = width / 2;

            // Calculate the largest inSampleSize value that is a power of 2 and keeps both
            // height and width larger than the requested height and width.
            while ((halfHeight / inSampleSize) > reqHeight
                    && (halfWidth / inSampleSize) > reqWidth) {
                inSampleSize *= 2;
            }
        }

        return inSampleSize;
    }
    public static Bitmap getBitmapFromAssets(Context context,String path){
        InputStream open = null;
        Bitmap bitmap = null;
        try {
            String temp =  path;
            open = context.getAssets().open(temp);
            BitmapFactory.Options options = new BitmapFactory.Options();
            options = sampleBitmapOptions(context, options, 10, 10);
            bitmap = BitmapFactory.decodeStream(open, null, options);
            return bitmap;
        } catch (Exception e) {e.printStackTrace();
            return null;
        }
    }

}

CommonUtils.java

package com.example.whiteboard.utils;

import android.content.Context;
import android.util.Log;
import android.widget.Toast;

public class CommonUtils {

    public static Context mContext;
    public static final String TAG_WHITEBOARD = "--whiteboard";

    public static void init(Context context){
        mContext = context;
    }
    public static void showToast(String text, int time){
        if(null != mContext){
            Toast.makeText(mContext, text, time).show();
        }
        else{
            Log.e(TAG_WHITEBOARD, "--showToast--mcontext is null");
        }

    }
    public static void destory() {
         mContext = null;       
    }

}

ScreenUtils.java

package com.example.whiteboard.utils;

import android.app.Activity;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Point;
import android.os.Build;
import android.util.Log;
import android.view.Display;
import android.view.View;
import android.view.WindowManager;
import android.view.inputmethod.InputMethodManager;

import java.lang.reflect.Field;

/**
 *  屏幕工具
 * Created by nereo on 15/11/19.
 * Updated by nereo on 2016/1/19.
 */
public class ScreenUtils {

    public static Point getScreenSize(Context context){
        WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
        Display display = wm.getDefaultDisplay();
        Point out = new Point();
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2) {
            display.getSize(out);
        }else{
            int width = display.getWidth();
            int height = display.getHeight();
            out.set(width, height);
        }
        return out;
    }
    /**
     * 获取状态栏高度
     */
    public static int getStatusBarHeight(Context context) {
        Class<?> c = null;
        Object obj = null;
        Field field = null;
        int x = 0, sbar = 0;
        try {
            c = Class.forName("com.android.internal.R$dimen");
            obj = c.newInstance();
            field = c.getField("status_bar_height");
            x = Integer.parseInt(field.get(obj).toString());
            sbar = context.getResources().getDimensionPixelSize(x);
        } catch (Exception e1) {
            Log.e("getStatusBarHight()", "get status bar height fail");
            e1.printStackTrace();
        }
        int statusBarHeight = sbar;
        Log.i("onPreDraw", "statusBarHeight: "+statusBarHeight);
        return statusBarHeight;
    }
    /**
     * 获取状态栏和标题栏的高度
     * @param activity
     * @return
     */
    public static int getStatusBarAndTitleHeight(Activity activity){
        if(null == activity){
            return 0;
        }
        View view = activity.getWindow().findViewById(android.R.id.content);
        if(null == view){
            return 0;
        }
        int height = view.getTop();
        return height;
    }

    public static void hideInput(View v) {
        InputMethodManager inputManager = (InputMethodManager) v
                .getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
        //调用系统输入法
        inputManager.hideSoftInputFromWindow(v.getWindowToken(), 0);

    }
    public static void showInput(View v) {
        InputMethodManager inputManager = (InputMethodManager) v
                .getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
        //调用系统输入法
        inputManager.showSoftInput(v, InputMethodManager.SHOW_FORCED);
    }

    public static void toggleInput(Context context) {
        InputMethodManager inputMethodManager =
                (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
        inputMethodManager.toggleSoftInput(0, InputMethodManager.HIDE_NOT_ALWAYS);
    }

    public static int px2dip(Context context, float pxValue) {
        final float scale = context.getResources().getDisplayMetrics().density;
        return (int) (pxValue / scale + 0.5f);
    }

    public static int dip2px(Context context, float dpValue) {
        final float scale = context.getResources().getDisplayMetrics().density;
        return (int) (dpValue * scale + 0.5f);
    }
    /**
     * 返回当前屏幕是否为竖屏
     * @param context
     * @return
     */
    public static boolean isScreenOriatationPortrait(Context context) {
         return context.getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT;
         }

}

SdCardStatus.java

package com.example.whiteboard.utils;

import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

import android.content.Context;
import android.content.Intent;
import android.os.Environment;
import android.os.IBinder;
import android.os.storage.StorageManager;
import android.text.TextUtils;
import android.util.Log;

/**
 * Created by gpy on 2015/10/20.
 */
public class SdCardStatus {
    private static String CACHE_FOLDER_NAME = "WMBWhiteBoard";
    private static String NONE_SD_CARD_PROMPT = "您的手机中sd卡不存在";

    public static void init(String cacheFolderName) {
        CACHE_FOLDER_NAME = cacheFolderName;
    }
    public static String getDefaulstCacheDirInSdCard() throws IllegalStateException {
        String sdCardPath = null;
        sdCardPath = getSDPath();
        if (null == sdCardPath) {
            throw new IllegalStateException(NONE_SD_CARD_PROMPT);
        }
        return sdCardPath + File.separator + CACHE_FOLDER_NAME;
    }

    /**
     * when not exist sd card,return null.
     *
     * @return
     */
    public static String getSDPath() {
        boolean sdCardExist = Environment.getExternalStorageState().equals(
                Environment.MEDIA_MOUNTED);
        if (sdCardExist) {
            return Environment.getExternalStorageDirectory().getAbsolutePath();
        } else {
                return null;
        }
    }
    public static int getReflactField(String className,String fieldName){
        int result = -1;
        try {
            Class<?> clz = Class.forName(className);
            Field field = clz.getField(fieldName);
            field.setAccessible(true);
            result = field.getInt(null); 
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }
    int title_id = getReflactField("com.android.internal.R$id", "title");
    int icon_id = getReflactField("com.android.internal.R$id", "icon");

//    public static StorageVolume[] getVolumeList(StorageManager storageManager){
//      try {
//          Class clz = StorageManager.class;
//          Method getVolumeList = clz.getMethod("getVolumeList", null);
//          StorageVolume[] result = (StorageVolume[]) getVolumeList.invoke(storageManager, null);
//          return result;
//      } catch (Exception e) {
//          e.printStackTrace();
//      }
//      return null;
//    }

    public static void shutDown(){
        try {
            Class<?> clz = Class.forName("android.os.ServiceManager");
            Log.e("wmb", "--shutDown 111");
            Method getService = clz.getMethod("getService", String.class);
            Log.e("wmb", "--shutDown 222");
            Object powerService = getService.invoke(null, Context.POWER_SERVICE);
            Log.e("wmb", "--shutDown 333");
            Class<?> cStub =  Class.forName("android.os.IPowerManager$Stub");
            Log.e("wmb", "--shutDown 444");
            Method asInterface = cStub.getMethod("asInterface", IBinder.class);
            Log.e("wmb", "--shutDown 555");
            Object IPowerManager = asInterface.invoke(null, powerService);
            Log.e("wmb", "--shutDown 666");
            Method shutDown = IPowerManager.getClass().getMethod("shutdown", boolean.class, boolean.class);
            Log.e("wmb", "--shutDown 777");
            shutDown.invoke(IPowerManager, false,true);
            Log.e("wmb", "--shutDown 888");
        } catch (Exception e) {
            Log.e("wmb", "--shutDown has an exception");
            e.printStackTrace();
        }
    }

    public static void reboot(){
        try {
            Class<?> clz = Class.forName("android.os.ServiceManager");
            Log.e("wmb", "--shutDown 111");
            Method getService = clz.getMethod("getService", String.class);
            Log.e("wmb", "--shutDown 222");
            Object powerService = getService.invoke(null, Context.POWER_SERVICE);
            Log.e("wmb", "--shutDown 333");
            Class<?> cStub =  Class.forName("android.os.IPowerManager$Stub");
            Log.e("wmb", "--shutDown 444");
            Method asInterface = cStub.getMethod("asInterface", IBinder.class);
            Log.e("wmb", "--shutDown 555");
            Object IPowerManager = asInterface.invoke(null, powerService);
            Log.e("wmb", "--shutDown 666");
            Method reboot = IPowerManager.getClass().getMethod("reboot", boolean.class,String.class, boolean.class);
            Log.e("wmb", "--shutDown 777");
            reboot.invoke(IPowerManager, false,"wmb test",true);
            Log.e("wmb", "--shutDown 888");
        } catch (Exception e) {
            Log.e("wmb", "--shutDown has an exception");
            e.printStackTrace();
        }
    }

    public static String getVolumeState(StorageManager storageManager, String path){
        String result = "";
        if(null == storageManager || TextUtils.isEmpty(path)){
            return result;
        }
        try {
            Class clz = StorageManager.class;
            Method getVolumeList = clz.getMethod("getVolumeState", String.class);
            result = (String) getVolumeList.invoke(storageManager, path);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }
}

TimeUtils.java

package com.example.whiteboard.utils;

import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;

/**
 * 时间处理工具
 * Created by Nereo on 2015/4/8.
 */
public class TimeUtils {

    public static String timeFormat(long timeMillis, String pattern){
        SimpleDateFormat format = new SimpleDateFormat(pattern, Locale.CHINA);
        return format.format(new Date(timeMillis));
    }

    public static String formatPhotoDate(long time){
        return timeFormat(time, "yyyy-MM-dd");
    }

    public static String formatPhotoDate(String path){
        File file = new File(path);
        if(file.exists()){
            long time = file.lastModified();
            return formatPhotoDate(time);
        }
        return "1970-01-01";
    }

    public static String getNowTimeString() {
        SimpleDateFormat sDateFormat = new SimpleDateFormat("yyyy-MM-dd_hhmmss");
        return sDateFormat.format(new java.util.Date());
    }
}

ViewUtils.java

package com.example.whiteboard.utils;

import android.app.Activity;
import android.view.View;

public class ViewUtils {

    public static <T extends View> T findViewById(Activity activity, int id){
        return (T) activity.findViewById(id);
    }

    public static <T extends View> T findViewById(View view, int id){
        return (T) view.findViewById(id);
    }

}

布局文件如下:

fragment_white_board.xml

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


    <com.example.whiteboard.SketchView 
            android:id="@+id/id_sketch_view"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="@color/green"
            android:focusable="true"
            android:focusableInTouchMode="true"/>

    <LinearLayout 
        android:id="@+id/id_controller"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:layout_alignParentBottom="true"
        android:orientation="horizontal"
        android:gravity="center"
        android:background="@color/gray"
        android:paddingLeft="@dimen/control_layout_padding"
        android:paddingRight="@dimen/control_layout_padding"
        android:paddingTop="@dimen/control_btn_padding"
        android:paddingBottom="@dimen/control_btn_padding">

        <ImageView 
            android:id="@+id/id_pen"
            style="@style/control_btn"
            android:src="@drawable/stroke_type_rbtn_draw_checked"/>

        <ImageView 
            android:id="@+id/id_eraser"
            style="@style/control_btn"
            android:src="@drawable/ic_eraser"/>

        <ImageView 
            android:id="@+id/id_undo"
            style="@style/control_btn"
            android:src="@drawable/ic_undo"/>

        <ImageView 
            android:id="@+id/id_redo"
            style="@style/control_btn"
            android:src="@drawable/ic_redo"/>

        <ImageView 
            android:id="@+id/id_pic"
            style="@style/control_btn"
            android:src="@drawable/ic_photo"/>

        <ImageView 
            android:id="@+id/id_bacground"
            style="@style/control_btn"
            android:src="@drawable/ic_background"/>

        <ImageView 
            android:id="@+id/id_drag"
            style="@style/control_btn"
            android:src="@drawable/ic_drag"/>

        <ImageView 
            android:id="@+id/id_save"
            style="@style/control_btn"
            android:src="@drawable/ic_file"/>

        <ImageView 
            android:id="@+id/id_empty"
            style="@style/control_btn"
            android:src="@drawable/ic_empty"/>

        <ImageView 
            android:id="@+id/id_screenshot"
            style="@style/control_btn"
            android:src="@drawable/ic_screenshot"/>


    </LinearLayout>



</RelativeLayout>

popup_pen.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@drawable/bg_popup"
    android:orientation="vertical" 
    android:padding="16dp">

    <!-- 画笔类型: 画笔、直线、圆、矩形、文字 -->
    <TextView 
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textSize="20sp"
        android:text="画笔类型"/>
    <RadioGroup 
        android:id="@+id/pen_type_radio_group"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:gravity="center"
        android:orientation="horizontal">


        <RadioButton 
            android:id="@+id/type_pen"
            style="@style/control_btn.nobackground"
            android:checked="true"
            android:button="@drawable/stroke_type_rbtn_draw"/>

        <RadioButton 
            android:id="@+id/type_line"
            style="@style/control_btn.nobackground"
            android:button="@drawable/stroke_type_rbtn_line"/>

        <RadioButton 
            android:id="@+id/type_circle"
            style="@style/control_btn.nobackground"
            android:button="@drawable/stroke_type_rbtn_circle"/>

        <RadioButton 
            android:id="@+id/type_rectangle"
            style="@style/control_btn.nobackground"
            android:button="@drawable/stroke_type_rbtn_rectangle"/>

        <RadioButton 
            android:id="@+id/type_text"
            style="@style/control_btn.nobackground"
            android:button="@drawable/stroke_type_rbtn_text"/>

    </RadioGroup>

    <!-- 画笔大小 -->
    <TextView 
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="画笔大小"
        android:textSize="20sp"/>
    <LinearLayout 
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:gravity="center_vertical">

        <ImageView 
            android:id="@+id/pen_size_circle"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:src="@drawable/stroke_color_rbtn_black"/>
        <SeekBar 
            android:id="@+id/pen_size_seek_bar"
            android:layout_width="match_parent"
            android:layout_height="match_parent"/>

    </LinearLayout>

    <!-- 画笔颜色 -->
    <TextView 
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textSize="20sp"
        android:text="画笔颜色"/>

    <RadioGroup 
        android:id="@+id/pen_color_radio_group"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:gravity="center"
        android:orientation="horizontal">


        <RadioButton 
            android:id="@+id/color_black"
            style="@style/control_btn.nobackground"
            android:checked="true"
            android:button="@drawable/stroke_color_rbtn_black"/>

        <RadioButton 
            android:id="@+id/color_red"
            style="@style/control_btn.nobackground"
            android:button="@drawable/stroke_color_rbtn_red"/>

        <RadioButton 
            android:id="@+id/color_green"
            style="@style/control_btn.nobackground"
            android:button="@drawable/stroke_color_rbtn_green"/>

        <RadioButton 
            android:id="@+id/color_orange"
            style="@style/control_btn.nobackground"
            android:button="@drawable/stroke_color_rbtn_orange"/>

        <RadioButton 
            android:id="@+id/color_blue"
            style="@style/control_btn.nobackground"
            android:button="@drawable/stroke_color_rbtn_blue"/>

    </RadioGroup>

    <!-- 画笔透明度 -->
    <TextView 
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="画笔透明度"
        android:textSize="20sp"/>
    <LinearLayout 
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:gravity="center_vertical">

        <ImageView 
            android:id="@+id/pen_alpha_circle"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:src="@drawable/stroke_color_rbtn_black"/>
        <SeekBar 
            android:id="@+id/pen_alpha_seek_bar"
            android:layout_width="match_parent"
            android:layout_height="match_parent"/>

    </LinearLayout>

</LinearLayout>

popup_eraser.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@drawable/bg_popup"
    android:padding="16dp"
    android:minWidth="410dp"
    android:orientation="vertical" >

    <TextView 
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="橡皮擦大小"
        android:textSize="20sp"/>

    <LinearLayout 
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="5dp"
        android:layout_marginBottom="5dp"
        android:orientation="horizontal">

        <ImageView 
            android:id="@+id/eraser_size_circle"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@drawable/circle"/>

        <SeekBar 
            android:id="@+id/eraser_size_seek_bar"
            android:layout_width="match_parent"
            android:layout_height="match_parent"/>

    </LinearLayout>


</LinearLayout>

popup_text.xml

<?xml version="1.0" encoding="utf-8"?>
<EditText xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/text_pupwindow_et"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:hint="请输入文字"
    android:imeOptions="actionDone"
    android:maxWidth="200dp"
    android:minWidth="120dp"
    android:background="@color/gray"
    android:selectAllOnFocus="true"
    android:textSize="22sp" >


</EditText>

猜你喜欢

转载自blog.csdn.net/adayabetter/article/details/81035262