Android 白板代码实现
16lz
2021-01-23
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 { @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 { @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 photoRecordList; public List strokeRecordList; public List 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 mPathList = new ArrayList(); 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 findViewById(Activity activity, int id){ return (T) activity.findViewById(id); } public static T findViewById(View view, int id){ return (T) view.findViewById(id); }}
布局文件如下:
fragment_white_board.xml
<?xml version="1.0" encoding="utf-8"?>"http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#ffffff" > "@+id/id_sketch_view" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@color/green" android:focusable="true" android:focusableInTouchMode="true"/> "@+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"> "@+id/id_pen" style="@style/control_btn" android:src="@drawable/stroke_type_rbtn_draw_checked"/> "@+id/id_eraser" style="@style/control_btn" android:src="@drawable/ic_eraser"/> "@+id/id_undo" style="@style/control_btn" android:src="@drawable/ic_undo"/> "@+id/id_redo" style="@style/control_btn" android:src="@drawable/ic_redo"/> "@+id/id_pic" style="@style/control_btn" android:src="@drawable/ic_photo"/> "@+id/id_bacground" style="@style/control_btn" android:src="@drawable/ic_background"/> "@+id/id_drag" style="@style/control_btn" android:src="@drawable/ic_drag"/> "@+id/id_save" style="@style/control_btn" android:src="@drawable/ic_file"/> "@+id/id_empty" style="@style/control_btn" android:src="@drawable/ic_empty"/> "@+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>
更多相关文章
- Android实现图片缩放与旋转
- Android imageView图片按比例缩放
- Android控制水平方向字体缩放android:textScaleX
- 第三章C++:字符串string、向量vector和数组
- 超级炫酷!HTML5 Canvas火焰画笔动画
- 使用sklearn轻松实现数据缩放
- Html css缩放会将大小改变几个像素
- 关于大背景图片随浏览器百分比缩放的问题
- Google Maps API v3:如何设置缩放级别和地图中心到用户提交的位置