在公司系统学习RN框架+android 公众号推文学习了一段时间后,终于等到你-公司布置的一个任务 开发纯原生的白板功能,包含画笔的粗细程度,橡皮擦,清空画板功能,裁剪,包含手动裁剪,自动裁剪需求,base64导入,导出功能。翻页功能。html代码导入canvas,啥也不多说,上源码分析和遇到的坑。
由于还在开发中 目前功能只缺少翻页功能 ,其他功能需求基本完善,已写成demo,可供大家学习和优化,如有写不妥之处,欢迎指点一二,在此谢谢各位。
废话什么的不多说,上源码才是硬道理。
canvasDemo的架构很简单 里面就两个activity 一个base64转bitmap的工具类
工具类代码如下:(这个我就不分析了,自行度娘,很多,就是一个base64和bitmap互转的工具类)
package com.example.test.canvas;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.util.Base64;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
public class Base64BitmapUtil {
/**
* bitmap转为base64
*
* @param bitmap
* @return
*/
public static String bitmapToBase64(Bitmap bitmap) {
String result = null;
ByteArrayOutputStream baos = null;
try {
if (bitmap != null) {
baos = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos);
baos.flush();
baos.close();
byte[] bitmapBytes = baos.toByteArray();
result = Base64.encodeToString(bitmapBytes, Base64.DEFAULT);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (baos != null) {
baos.flush();
baos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return result;
}
/**
* base64转为bitmap
*
* @param base64Data
* @return
*/
public static Bitmap base64ToBitmap(String base64Data) {
byte[] bytes = Base64.decode(base64Data, Base64.DEFAULT);
return BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
}
}
MainActivity 就是白板 photoAty 这个是保存图片 裁剪后展示的activity,如需更改其他需求,欢迎各位大大自行修改,页面很丑 其他的什么效果可以自行修改 。
该为MainActivity的界面布局,很简单,一个线性布局里面放了一些功能按键,然后就是一整个白板 在imageview呈现
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#ffffff"
android:orientation="vertical"
tools:context=".MainActivity">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom">
<Button
android:id="@+id/btn_resume"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="清空"
android:textSize="14sp" />
<Button
android:id="@+id/btn_xi"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toRightOf="@+id/btn_resume"
android:text="细铅笔"
android:textSize="14sp" />
<Button
android:id="@+id/btn_cu"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toRightOf="@+id/btn_xi"
android:text="粗铅笔"
android:textSize="14sp" />
<Button
android:id="@+id/btn_color"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/btn_cu"
android:text="切换颜色"
android:textSize="14sp" />
<Button
android:id="@+id/btn_clear"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/btn_cu"
android:layout_toRightOf="@+id/btn_color"
android:text="橡皮擦"
android:textSize="14sp" />
<Button
android:id="@+id/btn_autoclip"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/btn_xi"
android:layout_toRightOf="@+id/btn_clear"
android:text="自动裁剪"
android:textSize="14sp" />
<Button
android:id="@+id/btn_clip"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/btn_cu"
android:layout_toRightOf="@+id/btn_autoclip"
android:text="手动裁剪"
android:textSize="14sp" />
<Button
android:id="@+id/btn_extend"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/btn_color"
android:text="延长"
android:textSize="14sp" />
<Button
android:id="@+id/btn_import"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/btn_clear"
android:layout_toRightOf="@+id/btn_extend"
android:text="Base64导入"
android:textSize="14sp" />
<Button
android:id="@+id/btn_output"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/btn_autoclip"
android:layout_toRightOf="@+id/btn_import"
android:text="base64导出"
android:textSize="14sp"
android:visibility="gone" />
<Button
android:id="@+id/btn_html"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/btn_clip"
android:layout_toRightOf="@+id/btn_import"
android:text="html"
android:textSize="14sp" />
</RelativeLayout>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<WebView
android:id="@+id/btn_webview"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="visible"></WebView>
<ImageView
android:id="@+id/iv_canvas"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>
</LinearLayout>
界面长这洋,原谅像素不清晰哈
package com.example.test.canvas;
import android.Manifest;
import android.app.Activity;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.net.Uri;
import android.os.Build;
import android.os.CpuUsageInfo;
import android.os.Environment;
import android.provider.MediaStore;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v4.content.FileProvider;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.webkit.WebView;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.Toast;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
public class MainActivity extends Activity {
private Button btn_resume, btn_xi, btn_cu, btn_color, btn_clear, btn_autoclip, btn_clip, btn_extend, btn_import, btn_output, btn_html; //清空 保存 细 粗 颜色 橡皮擦 自动裁剪 裁剪 延长 base64 导入 base64导出 html->canvas
private ImageView iv_canvas;
private Bitmap baseBitmap;
private Canvas canvas;
private Paint paint;
private int color[] =
{
Color.RED, Color.GREEN, Color.BLUE, Color.BLACK
};
private int i = 1;
private int Mode = 0; // 0 铅笔 1 橡皮擦
private Paint eraserPaint = new Paint();
private static final int CROP_CODE = 3;//剪切裁剪
private final static int MY_PERMISSIONS_REQUEST_RECORD_STORAGE = 1;
private final static int MY_PERMISSIONS_REQUEST_RECORD_STORAGE1 = 2;
private List<Float> xlist = new ArrayList<>(); // x 坐标集合
private List<Float> ylist = new ArrayList<>(); // y 坐标集合
private Bitmap bmp;
private int screenWidth;
private int screenHeight;
private WebView btn_webview;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
DisplayMetrics dm = getResources().getDisplayMetrics();
screenWidth = dm.widthPixels;
screenHeight = dm.heightPixels;
// 初始化一个画笔,笔触宽度为3,铅笔,颜色为红色
paint = new Paint();
paint.setStrokeWidth(3);
paint.setColor(Color.RED);
paint.setAntiAlias(true);
paint.setDither(true);
// 初始化控件
iv_canvas = (ImageView) findViewById(R.id.iv_canvas);
btn_webview = (WebView) findViewById(R.id.btn_webview);
btn_resume = (Button) findViewById(R.id.btn_resume);
btn_xi = (Button) findViewById(R.id.btn_xi);
btn_cu = (Button) findViewById(R.id.btn_cu);
btn_color = (Button) findViewById(R.id.btn_color);
btn_clear = (Button) findViewById(R.id.btn_clear);
btn_autoclip = (Button) findViewById(R.id.btn_autoclip);
btn_clip = (Button) findViewById(R.id.btn_clip);
btn_extend = (Button) findViewById(R.id.btn_extend);
btn_import = (Button) findViewById(R.id.btn_import);
btn_output = (Button) findViewById(R.id.btn_output);
btn_html = (Button) findViewById(R.id.btn_html);
//添加点击监听
btn_resume.setOnClickListener(click);
btn_clear.setOnClickListener(click);
btn_color.setOnClickListener(click);
btn_cu.setOnClickListener(click);
btn_xi.setOnClickListener(click);
btn_html.setOnClickListener(click);
btn_output.setOnClickListener(click);
btn_import.setOnClickListener(click);
btn_extend.setOnClickListener(click);
btn_clip.setOnClickListener(click);
btn_autoclip.setOnClickListener(click);
iv_canvas.setOnTouchListener(touch);
}
private View.OnTouchListener touch = new View.OnTouchListener() {
// 定义手指开始触摸的坐标
float startX;
float startY;
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
// 用户按下动作
case MotionEvent.ACTION_DOWN:
// 第一次绘图初始化内存图片,指定背景为白色
if (baseBitmap == null) {
baseBitmap = Bitmap.createBitmap(iv_canvas.getWidth(),
iv_canvas.getHeight(), Bitmap.Config.ARGB_8888);
canvas = new Canvas(baseBitmap);
// canvas.drawColor(Color.WHITE);
}
// 记录开始触摸的点的坐标
startX = event.getX();
startY = event.getY();
xlist.add((startX));
ylist.add((startY));
break;
// 用户手指在屏幕上移动的动作
case MotionEvent.ACTION_MOVE:
// 记录移动位置的点的坐标
float stopX = event.getX();
float stopY = event.getY();
xlist.add((stopX));
ylist.add((stopY));
//根据两点坐标,绘制连线
if (Mode == 0) {
canvas.drawLine(startX, startY, stopX, stopY, paint);
canvas.save(Canvas.ALL_SAVE_FLAG);
canvas.restore();
} else if (Mode == 1) {
canvas.drawLine(startX, startY, stopX, stopY, eraserPaint);
}
// 更新开始点的位置
startX = event.getX();
startY = event.getY();
xlist.add((startX));
ylist.add((startY));
// 把图片展示到ImageView中
iv_canvas.setImageBitmap(baseBitmap);
break;
case MotionEvent.ACTION_UP:
break;
default:
break;
}
return true;
}
};
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode == Activity.RESULT_OK && data != null) {
switch (requestCode) {
case CROP_CODE:
//获取到裁剪后的图片的Uri进行处理
if (imageUri != null) {
Intent intent = new Intent(MainActivity.this, PhotoAty.class);
intent.setDataAndType(imageUri, "url");
startActivity(intent);
}
break;
}
}
}
private View.OnClickListener click = new View.OnClickListener() {
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_resume:
resumeCanvas();
break;
case R.id.btn_cu:
paint.setStrokeWidth(10);
Mode = 0;
break;
case R.id.btn_xi:
paint.setStrokeWidth(3);
Mode = 0;
break;
case R.id.btn_color:
if (Mode == 0) {
Log.i("i的值", String.valueOf(i));
if (i == 4) {
i = 0;
}
paint.setColor(color[i]);
++i;
}
break;
case R.id.btn_clear:
Mode = 1;
eraserPaint.setAlpha(0);
eraserPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN)); //设置图像的混合模式
eraserPaint.setAntiAlias(true); //抗锯齿
eraserPaint.setDither(true); //设置防抖动
eraserPaint.setStrokeCap(Paint.Cap.ROUND); //设置线帽 圆形
eraserPaint.setStyle(Paint.Style.STROKE); //设置橡皮擦样式 空心
eraserPaint.setStrokeJoin(Paint.Join.ROUND);
eraserPaint.setStrokeWidth(15); //设置橡皮擦宽度
// eraserPaint.setColor(0xFF00FF00);
// eraserPaint.setColor(Color.WHITE);
break;
case R.id.btn_autoclip:
/**
* 第 1 步: 检查是否有相应的权限
*/
boolean isAllGranted1 = checkPermissionAllGranted(
new String[]{
Manifest.permission.RECORD_AUDIO,
Manifest.permission.CAMERA,
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE
}
);
if (!isAllGranted1) {
ActivityCompat.requestPermissions(MainActivity.this,
new String[]{Manifest.permission.RECORD_AUDIO,
Manifest.permission.CAMERA,
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE},
MY_PERMISSIONS_REQUEST_RECORD_STORAGE1);
} else {
if (xlist.size() != 0 && ylist.size() != 0) {
// removeDuplicate(xlist);
// removeDuplicate(ylist);
//x 坐标最小值 最大值
float max_x = Float.valueOf(Collections.max(xlist));
float min_x = Float.valueOf(Collections.min(xlist));
//y 坐标最小值 最大值
float max_y = Float.valueOf(Collections.max(ylist));
float min_y = Float.valueOf(Collections.min(ylist));
// canvas.clipRect(min_x, min_y, max_x, max_y);
float tip_x = max_x - min_x;
float tip_y = max_y - min_y;
bmp = Bitmap.createBitmap((int) (tip_x + 10),
(int) (tip_y + 10), Bitmap.Config.ARGB_8888);
Canvas canvas1 = new Canvas(bmp);
canvas1.drawColor(Color.WHITE);
Rect mSrcRect = new Rect((int) min_x - 5, (int) min_y - 5, (int) max_x + 5, (int) max_y + 5);
Rect mDestRect = new Rect(0, 0, (int) (tip_x + 10), (int) (tip_y + 10));
canvas1.drawBitmap(baseBitmap, mSrcRect, mDestRect, null); //把baseBitmap中需要的绘制区域mSrcRect 截取出来 放入 新创建的(宽高为需要截取的矩形区域)bmp中的mDestRect位置 在利用bmp的canvas绘制出来
canvas1.save(Canvas.ALL_SAVE_FLAG);
canvas1.restore();
saveBitmap1();
}
// saveBitmap();
}
break;
case R.id.btn_clip:
/**
* 第 1 步: 检查是否有相应的权限
*/
boolean isAllGranted = checkPermissionAllGranted(
new String[]{
Manifest.permission.RECORD_AUDIO,
Manifest.permission.CAMERA,
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE
}
);
if (!isAllGranted) {
ActivityCompat.requestPermissions(MainActivity.this,
new String[]{Manifest.permission.RECORD_AUDIO,
Manifest.permission.CAMERA,
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE},
MY_PERMISSIONS_REQUEST_RECORD_STORAGE);
} else {
saveBitmap();
}
break;
case R.id.btn_extend:
break;
case R.id.btn_import:
break;
case R.id.btn_output:
break;
case R.id.btn_html:
btn_webview.setVisibility(View.VISIBLE);
StringBuilder sb = new StringBuilder();
// 拼接一段HTML代码
sb.append("<html>");
sb.append("<head>");
sb.append("<title> 欢迎您 </title>");
sb.append("</head>");
sb.append("<body>");
sb.append("<h2> 欢迎您访问<a href=\"http://www.cctv.com\">"
+ "Java联盟</a></h2>");
sb.append("</body>");
sb.append("</html>");
// 加载、并显示HTML代码
btn_webview.loadDataWithBaseURL(null, sb.toString(), "text/html", "utf-8", null);
break;
default:
break;
}
}
};
/**
* 检查是否拥有指定的所有权限
*/
private boolean checkPermissionAllGranted(String[] permissions) {
for (String permission : permissions) {
if (ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) {
// 只要有一个权限没有被授予, 则直接返回 false
return false;
}
}
return true;
}
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
if (requestCode == MY_PERMISSIONS_REQUEST_RECORD_STORAGE) {
boolean isAllGranted = true;
// 判断是否所有的权限都已经授予了
for (int grant : grantResults) {
if (grant != PackageManager.PERMISSION_GRANTED) {
isAllGranted = false;
break;
}
}
if (isAllGranted) {
// 如果所有的权限都授予了, 则执行备份代码
saveBitmap();
} else {
// 弹出对话框告诉用户需要权限的原因, 并引导用户去应用权限管理中手动打开权限按钮
android.widget.Toast.makeText(MainActivity.this, "Permission Denied", android.widget.Toast.LENGTH_SHORT).show();
}
return;
}
if (requestCode == MY_PERMISSIONS_REQUEST_RECORD_STORAGE1) {
boolean isAllGranted = true;
// 判断是否所有的权限都已经授予了
for (int grant : grantResults) {
if (grant != PackageManager.PERMISSION_GRANTED) {
isAllGranted = false;
break;
}
}
if (isAllGranted) {
// 如果所有的权限都授予了, 则执行备份代码
if (xlist.size() != 0 && ylist.size() != 0) {
removeDuplicate(xlist);
removeDuplicate(ylist);
//x 坐标最小值 最大值
float max_x = Float.valueOf(Collections.max(xlist));
float min_x = Float.valueOf(Collections.min(xlist));
//y 坐标最小值 最大值
float max_y = Float.valueOf(Collections.max(ylist));
float min_y = Float.valueOf(Collections.min(ylist));
// canvas.clipRect(min_x, min_y, max_x, max_y);
float tip_x = (max_x + Float.valueOf(5)) - min_x;
float tip_y = (max_y + Float.valueOf(5)) - min_y;
bmp = Bitmap.createBitmap((int) (max_x + 5),
(int) (max_y + 5), Bitmap.Config.ARGB_8888);
Canvas canvas1 = new Canvas(bmp);
canvas1.drawColor(Color.WHITE);
canvas1.drawBitmap(baseBitmap, 0, 0, null);
canvas1.save(Canvas.ALL_SAVE_FLAG);
canvas1.restore();
saveBitmap1();
}
} else {
// 弹出对话框告诉用户需要权限的原因, 并引导用户去应用权限管理中手动打开权限按钮
android.widget.Toast.makeText(MainActivity.this, "Permission Denied", android.widget.Toast.LENGTH_SHORT).show();
}
return;
}
}
private Uri imageUri;
/**
* 保存图片到SD卡上
*/
protected void saveBitmap() {
try {
File imageStorageDir = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), "Canvas"); // Create the storage directory if it does not exist
if (!imageStorageDir.exists()) {
imageStorageDir.mkdirs();
}
File file = new File(imageStorageDir + File.separator + "IMG_" + String.valueOf(System.currentTimeMillis()) + ".png");
FileOutputStream fos = null;
try {
fos = new FileOutputStream(file);
baseBitmap.compress(Bitmap.CompressFormat.PNG, 50, fos); //压缩 写入
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
try {
fos.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
if (Build.VERSION.SDK_INT >= 24) {
imageUri = FileProvider.getUriForFile(MainActivity.this, "com.example.test.canvas.fileprovider", file);
} else {
imageUri = Uri.fromFile(file);
}
// File file = new File(Environment.getExternalStorageDirectory(),
// System.currentTimeMillis() + ".png");
// imageUri = Uri.fromFile(file);
Intent intent = new Intent("com.android.camera.action.CROP");
//添加这一句表示对目标应用临时授权该Uri所代表的文件
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
//可以选择图片类型,如果是*表明所有类型的图片
intent.setDataAndType(imageUri, "image/*");
// 下面这个crop = true是设置在开启的Intent中设置显示的VIEW可裁剪
intent.putExtra("crop", "true");
// aspectX aspectY 是宽高的比例,这里设置的是正方形(长宽比为1:1)
intent.putExtra("aspectX", 1);
intent.putExtra("aspectY", 1);
// outputX outputY 是裁剪图片宽高
// intent.putExtra("outputX", 500);
// intent.putExtra("outputY", 500);
intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);
intent.putExtra("outputFormat", Bitmap.CompressFormat.JPEG.toString());
//是否将数据保留在Bitmap中返回,true返回bitmap,false返回uri
intent.putExtra("return-data", false);
//裁剪后的图片Uri路径,uritempFile为Uri类变量
// uritempFile = Uri.parse("file://" + "/" + Environment.getExternalStorageDirectory().getPath() + "/" + "small.jpg");
// intent.putExtra(MediaStore.EXTRA_OUTPUT, uritempFile);
startActivityForResult(intent, CROP_CODE);
// Toast.makeText(MainActivity.this, "保存图片成功", Toast.LENGTH_SHORT).show();
} catch (
Exception e)
{
Toast.makeText(MainActivity.this, "保存图片失败", Toast.LENGTH_SHORT).show();
e.printStackTrace();
}
}
private Uri imageUri1;
/**
* 保存图片到SD卡上
*/
protected void saveBitmap1() {
try {
File imageStorageDir = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), "Canvas"); // Create the storage directory if it does not exist
if (!imageStorageDir.exists()) {
imageStorageDir.mkdirs();
}
File file = new File(imageStorageDir + File.separator + "IMG_" + String.valueOf(System.currentTimeMillis()) + ".png");
FileOutputStream fos = null;
try {
fos = new FileOutputStream(file);
bmp.compress(Bitmap.CompressFormat.PNG, 50, fos); //压缩 写入
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
try {
fos.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
if (Build.VERSION.SDK_INT >= 24) {
imageUri1 = FileProvider.getUriForFile(MainActivity.this, "com.example.test.canvas.fileprovider", file);
} else {
imageUri1 = Uri.fromFile(file);
}
if (imageUri1 != null) {
Intent intent = new Intent(MainActivity.this, PhotoAty.class);
intent.setDataAndType(imageUri1, "url");
startActivity(intent);
}
} catch (Exception e)
{
Toast.makeText(MainActivity.this, "保存图片失败", Toast.LENGTH_SHORT).show();
e.printStackTrace();
}
}
/**
* 清除画板
*/
protected void resumeCanvas() {
// 手动清除画板的绘图,重新创建一个画板
if (baseBitmap != null) {
baseBitmap = Bitmap.createBitmap(iv_canvas.getWidth(),
iv_canvas.getHeight(), Bitmap.Config.ARGB_8888);
canvas = new Canvas(baseBitmap);
// canvas.drawColor(Color.WHITE);
iv_canvas.setImageBitmap(baseBitmap);
xlist.clear();
ylist.clear();
btn_webview.setVisibility(View.GONE);
Toast.makeText(MainActivity.this, "清除画板成功,可以重新开始绘图", Toast.LENGTH_SHORT).show();
}
}
public static List removeDuplicate(List list) {
for (int i = 0; i < list.size() - 1; i++) {
for (int j = list.size() - 1; j > i; j--) {
if (list.get(j).equals(list.get(i))) {
list.remove(j);
}
}
}
return list;
}
}
上面就是Mainactivity主界面的代码了
这是photoaty的布局加代码 里面逻辑很简单 就是接收MainActivity传过来的url路径进行bitmap转换然后显示出来 还有个base64导出的功能 不需要可以自行去除
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#D3D3D3"
tools:context=".PhotoAty">
<Button
android:id="@+id/photo_output"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:background="#ffffff"
android:text="base64导出"
android:textColor="#000000"
android:textSize="14sp" />
<ImageView
android:id="@+id/photo_img"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:scaleType="centerCrop" />
</RelativeLayout>
package com.example.test.canvas;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import java.io.FileNotFoundException;
public class PhotoAty extends AppCompatActivity {
private ImageView photo_img;
private Uri uri;
private Button photo_output;
private Bitmap bitmap;
private String base64 = "";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_photo_aty);
initView();
initListener();
}
private void initView() {
bitmap = null;
base64 = "";
photo_img = (ImageView) findViewById(R.id.photo_img);
Intent intent = getIntent();
uri = intent.getData();
bitmap = decodeUriAsBitmap(uri);
// 把解析到的位图显示出来
photo_img.setImageBitmap(bitmap);
photo_output = (Button) findViewById(R.id.photo_output);
}
private void initListener() {
photo_output.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (bitmap != null) {
base64 = Base64BitmapUtil.bitmapToBase64(bitmap);
Log.i("base4------", base64);
}
}
});
}
private Bitmap decodeUriAsBitmap(Uri uri) {
Bitmap bitmap = null;
try {
// 先通过getContentResolver方法获得一个ContentResolver实例,
// 调用openInputStream(Uri)方法获得uri关联的数据流stream
// 把上一步获得的数据流解析成为bitmap
bitmap = BitmapFactory.decodeStream(getContentResolver().openInputStream(uri));
} catch (FileNotFoundException e) {
e.printStackTrace();
return null;
}
return bitmap;
}
}
下面来分析下代码
首先 初始化画笔
// 初始化一个画笔,笔触宽度为3,铅笔,颜色为红色
paint = new Paint();
paint.setStrokeWidth(3);
paint.setColor(Color.RED);
paint.setAntiAlias(true);
创建画笔 设置画笔宽度,颜色,抗锯齿(使线条看起来很平滑)。
然后就是手指绘图的时候了!!!
我们知道,所有的图案都是有一条一条非常小的线段组成,当线段组成多了就成了各种各样的形状,所以我们应该去获取每次手指移动的起点坐标x,y和终点坐标x,y 然后绘制成直线,在组合起来就是我们绘图的样子.
所以我这边就监听手指的触摸动作事件 触摸动作事件分为3种,ACTION_DOWN(手指按住)->ACTION_MOVE(手指一动)->ACTION_UP(手指抬起) 3种动作 ,我们可以先初始化内存图片baseBitmap, 去需要绘制的区域作为宽高,然后配置Bitmap.Config.ARGB_8888 64位图(android 废除了ARGB_4444位图,采用质量更好的ARGB_8888来绘制),在创建两个参数startx,starty,分别记录手指按下去那个动作记入开始的坐标,然后在手指移动的时候记录移动的坐标,也可以看作移动的时候的终点坐标stopx,stopy,在利用canvas.drawline 绘制直线,更新新的起始坐标保存下来。然后就一直重复,就可以利用canvas绘制成我们想要的形状了,然后在显示在bitmap上 这样你手指在画板上画什么就能显示什么。
下面贴代码
在OnTouch回调方法里面做相应操作
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
// 用户按下动作
case MotionEvent.ACTION_DOWN:
// 第一次绘图初始化内存图片,指定背景为白色
if (baseBitmap == null) {
baseBitmap = Bitmap.createBitmap(iv_canvas.getWidth(),
iv_canvas.getHeight(), Bitmap.Config.ARGB_8888);
canvas = new Canvas(baseBitmap);
canvas.drawColor(Color.WHITE);
}
// 记录开始触摸的点的坐标
startX = event.getX();
startY = event.getY();
xlist.add((startX));
ylist.add((startY));
break;
// 用户手指在屏幕上移动的动作
case MotionEvent.ACTION_MOVE:
// 记录移动位置的点的坐标
float stopX = event.getX();
float stopY = event.getY();
xlist.add((stopX));
ylist.add((stopY));
//根据两点坐标,绘制连线
if (Mode == 0) {
canvas.drawLine(startX, startY, stopX, stopY, paint);
canvas.save(Canvas.ALL_SAVE_FLAG);
canvas.restore();
} else if (Mode == 1) {
canvas.drawLine(startX, startY, stopX, stopY, eraserPaint);
}
// 更新开始点的位置
startX = event.getX();
startY = event.getY();
xlist.add((startX));
ylist.add((startY));
// 把图片展示到ImageView中
iv_canvas.setImageBitmap(baseBitmap);
break;
case MotionEvent.ACTION_UP:
break;
default:
break;
}
return true;
}
};
然后这就是绘图白板的基本思路了,切换颜色和更换粗细想必不用我说大家应该也会了吧,利用
paint.setStrokeWidth(variable);
variable 填写你需要改变的画笔粗细大小 值越大越粗了,值越小当然越细了,variable等于0的时候不能再细了 画笔的细度 记住!
切换颜色
paint.setColor(color);
color 填写你需要改变的颜色就好了,就可以呈现不一样的颜色,
现在讲下清空画板功能,也很简单。
/**
* 清除画板
*/
protected void resumeCanvas() {
// 手动清除画板的绘图,重新创建一个画板
if (baseBitmap != null) {
baseBitmap = Bitmap.createBitmap(iv_canvas.getWidth(),
iv_canvas.getHeight(), Bitmap.Config.ARGB_8888);
canvas = new Canvas(baseBitmap);
canvas.drawColor(Color.WHITE);
iv_canvas.setImageBitmap(baseBitmap);
xlist.clear();
ylist.clear();
Toast.makeText(MainActivity.this, "清除画板成功,可以重新开始绘图", Toast.LENGTH_SHORT).show();
}
}
只要重新创建一个新的画板就好了
现在讲解下橡皮擦功能,橡皮擦功能其实说起来也很简单,google都给我们提供好了方法
case R.id.btn_clear:
Mode = 1;
eraserPaint.setAlpha(0);
eraserPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN)); //设置图像的混合模式
eraserPaint.setAntiAlias(true); //抗锯齿
eraserPaint.setDither(true); //设置防抖动
eraserPaint.setStrokeCap(Paint.Cap.ROUND); //设置线帽 圆形
eraserPaint.setStyle(Paint.Style.STROKE); //设置橡皮擦样式 空心
eraserPaint.setStrokeJoin(Paint.Join.ROUND);
eraserPaint.setStrokeWidth(15); //设置橡皮擦宽度
// eraserPaint.setColor(0xFF00FF00);
// eraserPaint.setColor(Color.WHITE);
break;
只要设置下透明度 ,然后设置图像的混合模式,
eraserPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN)); //设置图像的混合模式
上面这句话必须设置,不然就不是橡皮擦了,不懂没关系。
然后就可以形成这个效果
原谅我像素不好,没拍好。
下面讲重点了,自动裁剪功能,这个功能是因为这个白本初衷是给学生用的,学生写完后,点击提交,需要系统自动裁剪出他绘制的区域,没绘制的区域就不需要,节省文件大小。也方便查看。
原理是这样的,我们知道画布的坐标系是从左往右 X轴,从上往下Y轴。最左上角为(0,0)坐标原点。然后先创建两个对象集合。
private List<Float> xlist = new ArrayList<>(); // x 坐标集合
private List<Float> ylist = new ArrayList<>(); // y 坐标集合
分别存放绘制的所有的startx,starty,stopx,stopy。
这样当学生绘制完毕后这两个对象集合将收集了所有的坐标点,然后取出最大的x_max,y_max.最小的x_min,y_min。然后遵循canvas.drawRect 绘制矩形区域,那么这个矩形区域就是我们裁剪后所需要的学生绘制的区域,在将这个区域放在一个新的bitmap上,这个bitmap宽高跟这个矩形区域一样大小,这样就是截取了我们需要的区域。
if (xlist.size() != 0 && ylist.size() != 0) {
// removeDuplicate(xlist);
// removeDuplicate(ylist);
//x 坐标最小值 最大值
float max_x = Float.valueOf(Collections.max(xlist));
float min_x = Float.valueOf(Collections.min(xlist));
//y 坐标最小值 最大值
float max_y = Float.valueOf(Collections.max(ylist));
float min_y = Float.valueOf(Collections.min(ylist));
// canvas.clipRect(min_x, min_y, max_x, max_y);
float tip_x = max_x - min_x;
float tip_y = max_y - min_y;
bmp = Bitmap.createBitmap((int) (tip_x + 10),
(int) (tip_y + 10), Bitmap.Config.ARGB_8888);
Canvas canvas1 = new Canvas(bmp);
canvas1.drawColor(Color.WHITE);
Rect mSrcRect = new Rect((int) min_x - 5, (int) min_y - 5, (int) max_x + 5, (int) max_y + 5);
Rect mDestRect = new Rect(0, 0, (int) (tip_x+10), (int) (tip_y+10));
canvas1.drawBitmap(baseBitmap, mSrcRect, mDestRect, null); //把baseBitmap中需要的绘制区域mSrcRect 截取出来 放入 新创建的(宽高为需要截取的矩形区域)bmp中的mDestRect位置 在利用bmp的canvas绘制出来
canvas1.save(Canvas.ALL_SAVE_FLAG);
canvas1.restore();
saveBitmap1();
}
//把baseBitmap中需要的绘制区域mSrcRect 截取出来 放入 新创建的(宽高为需要截取的矩形区域)bmp中的mDestRect位置 在利用bmp的canvas绘制出来 记得留点空白,这样用户看起来很舒服,不会紧贴着截取
然后在执行savebitmap方法进行保存就行了
protected void saveBitmap1() {
try {
File imageStorageDir = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), "Canvas"); // Create the storage directory if it does not exist
if (!imageStorageDir.exists()) {
imageStorageDir.mkdirs();
}
File file = new File(imageStorageDir + File.separator + "IMG_" + String.valueOf(System.currentTimeMillis()) + ".png");
FileOutputStream fos = null;
try {
fos = new FileOutputStream(file);
bmp.compress(Bitmap.CompressFormat.PNG, 50, fos); //压缩 写入
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
try {
fos.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
if (Build.VERSION.SDK_INT >= 24) {
imageUri1 = FileProvider.getUriForFile(MainActivity.this, "com.example.test.canvas.fileprovider", file);
} else {
imageUri1 = Uri.fromFile(file);
}
if (imageUri1 != null) {
Intent intent = new Intent(MainActivity.this, PhotoAty.class);
intent.setDataAndType(imageUri1, "url");
startActivity(intent);
}
} catch (Exception e)
{
Toast.makeText(MainActivity.this, "保存图片失败", Toast.LENGTH_SHORT).show();
e.printStackTrace();
}
}
找到图片的路径地址,将bmp压缩写入进去就行了。
这样imageUrl1就是我们自动裁剪后的图片的地址。
大家只要拿到这个然后根据自己所需要的需求进行操作就行了。
对了,这里要进行android系统适配和检查权限。
<!-- 在SDCard中创建与删除文件权限 -->
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />
<!-- 往SDCard写入数据权限 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
android7.0 适配需要使用fileprovider,android6.0进行动态权限管理,这个我在代码里都写好了,自己去看,很简单。
手动裁剪我动用系统自带的裁剪功能
Intent intent = new Intent("com.android.camera.action.CROP");
//添加这一句表示对目标应用临时授权该Uri所代表的文件
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
//可以选择图片类型,如果是*表明所有类型的图片
intent.setDataAndType(imageUri, "image/*");
// 下面这个crop = true是设置在开启的Intent中设置显示的VIEW可裁剪
intent.putExtra("crop", "true");
// aspectX aspectY 是宽高的比例,这里设置的是正方形(长宽比为1:1)
intent.putExtra("aspectX", 1);
intent.putExtra("aspectY", 1);
// outputX outputY 是裁剪图片宽高
// intent.putExtra("outputX", 500);
// intent.putExtra("outputY", 500);
intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);
intent.putExtra("outputFormat", Bitmap.CompressFormat.JPEG.toString());
//是否将数据保留在Bitmap中返回,true返回bitmap,false返回uri
intent.putExtra("return-data", false);
//裁剪后的图片Uri路径,uritempFile为Uri类变量
// uritempFile = Uri.parse("file://" + "/" + Environment.getExternalStorageDirectory().getPath() + "/" + "small.jpg");
// intent.putExtra(MediaStore.EXTRA_OUTPUT, uritempFile);
startActivityForResult(intent, CROP_CODE);
代码里写的很清楚了,拿到图片的url地址 imageUri,传入自动自带的裁剪功能进行裁剪就好了,然后根据回调方法
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode == Activity.RESULT_OK && data != null) {
switch (requestCode) {
case CROP_CODE:
//获取到裁剪后的图片的Uri进行处理
if (imageUri != null) {
Intent intent = new Intent(MainActivity.this, PhotoAty.class);
intent.setDataAndType(imageUri, "url");
startActivity(intent);
}
break;
}
}
}
做相应的操作就行了。
这就是我的画板的功能,如有不足欢迎大家指教。非常感谢各位大大能观看到最后,写的不好见谅。
贴上我写好的demo地址,需要的可以自行下载。
这个demo 包含了我上面所说的所有功能,包括了android动态权限管理,android7.0系统适配,大家拿了可以直接应用在项目中。
CSDN地址:https://download.csdn.net/download/qq_33266474/10596433