0%

Android自定义画板

参考Android 自定义View (一)这篇博客,总结出自定义View的步骤:

1、自定义View的属性
2、在View的构造方法中获得自定义属性
[3、重写onMesure]
4、重写onDraw

再参照构建一个 Android 的简单绘图应用这篇翻译文章,以一个简单的画板例子走走整个流程。画板实现简单的随手涂鸦,双击清屏和按音量键分享的功能。

自义定View

自定义View的属性

先在res/values/ 下建立一个属性文件(attrs.xml), 在里面声明自定义属性。

1
2
3
4
5
6
7
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="MyView">
<attr name="paintColor" format="color"/>
<attr name="paintSize" format="dimension"/>
</declare-styleable>
</resources>

定义了画笔颜色和粗细。

获得自定义属性

在自定义View的构造方法中获得自定义属性。

1
2
3
4
5
6
7
8
9
10
11
12
public MyView(Context context, AttributeSet attrs) {
super(context, attrs);

TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.MyView);
int paintColor = typedArray.getColor(R.styleable.MyView_paintColor, Color.BLACK);
//第二个参数为默认值
int paintSize = typedArray.getDimensionPixelSize(R.styleable.MyView_paintSize, 12);
typedArray.recycle();

//进行画笔的相关设置
...
}

重写onDraw

1
2
3
protected void onDraw(Canvas canvas) {
canvas.drawPath(path, paint);
}

重写onTouchEvent事件

触碰屏幕和拖动手指的路径需要用onTouchEvent事件,并用Path对象封装路径,思路如下:

  • 首先,在我们第一次触碰屏幕(MotionEvent.ACTION_DOWN)时创建一个路径。
    • 这里需要一个双击判断,我们的思路是通过比较两次按下的时间间隔来判断
  • 然后,当手指拖动时,我们不断增加点到路径里(MotionEvent.ACTION_MOVE)
  • 最后,当手指停止时,我们也停止增加点并且invalidate视图强行重绘。

实现代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
private long firstClick;
private int countClick;// 点击次数
@Override
public boolean onTouchEvent(MotionEvent event) {
float pointX = event.getX();
float pointY = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// 如果第二次点击距离第一次点击时间过长 那么将第二次点击看为第一次点击
if (firstClick != 0 && System.currentTimeMillis() - firstClick > 200) {
countClick = 0;
}
countClick ++;
if (countClick == 1) {
firstClick = System.currentTimeMillis();
path.moveTo(pointX, pointY);

} else if (countClick == 2) {
long lastClick = System.currentTimeMillis();
// 两次点击小于200ms,进行双击清屏操作
if (lastClick - firstClick < 200) {
path.reset();
break;
}
}
return true;
case MotionEvent.ACTION_MOVE:
path.lineTo(pointX, pointY);
break;
default:
break;
}
postInvalidate();
return false;
}

至此完成了自定义View的过程,接下来就是在程序中使用自定义View了。

使用View

在XML中布局

先在布局文件(res/layout/activity_main.xml)中声明我们的自定义View。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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"
tools:context="com.leon.myappdraw2.MainActivity"
xmlns:custom="http://schemas.android.com/apk/res-auto">
<com.leon.myappdraw2.ui.MyView
android:id="@+id/myView"
android:layout_width="match_parent"
android:layout_height="match_parent"
custom:paintColor="#00ffff"
custom:paintSize="12dp"/>
</RelativeLayout>

对于自定义的paintColor和paintSize属性,需要引入 xmlns:custom=”http://schemas.android.com/apk/res-auto" 命名空间。

在Acitvity中使用

在Acitvity中使用,主要实现两个功能:

  • 引入自定义View来画图
  • 按音量键保存图片并分享

实现代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
public class MainActivity extends AppCompatActivity {

private MyView myView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
myView= (MyView) findViewById(R.id.myView);
}

@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
switch (keyCode) {
case KeyEvent.KEYCODE_VOLUME_DOWN:
case KeyEvent.KEYCODE_VOLUME_UP:
Bitmap bitmap = Bitmap.createBitmap(myView.getWidth(), myView.getHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
myView.draw(canvas);
String image = saveBitmap(bitmap);
if (image!=null) {
shareImage(image);
} else {
Toast.makeText(MainActivity.this, "图片保存失败!", Toast.LENGTH_SHORT).show();
}
return true;
default:
break;
}
return super.onKeyDown(keyCode, event);
}

/**
* 系统的分享图片
* @param filename 文件名
*/
private void shareImage(String filename) {
Uri imageUri = Uri.fromFile(new File(filename));
Intent shareIntent = new Intent();
shareIntent.setAction(Intent.ACTION_SEND);
shareIntent.putExtra(Intent.EXTRA_STREAM, imageUri);
shareIntent.setType("image/*");
startActivity(Intent.createChooser(shareIntent, "分享到"));
}

/** 保存图片到本地
* @param bitmap 要保存的bitmap
*/
public String saveBitmap(Bitmap bitmap){

String albumPath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES)
+ File.separator + getString(R.string.app_name);
String imagePath = albumPath+File.separator+System.currentTimeMillis() + ".png";

File album = new File(albumPath);
if (!album.isDirectory()&&!album.mkdirs()) {
return null;
}

FileOutputStream out;
try {
out = new FileOutputStream(imagePath);
bitmap.compress(Bitmap.CompressFormat.PNG, 90, out);
out.flush();
out.close();
return imagePath;

} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}

在AndroidManifest.xml中设置权限

1
2
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>

运行结果

手写        分享

注意点备忘

AppCompat设置全屏

在res/values/styles.xml中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<resources>

<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
</style>

<style name="AppTheme.NoActionBar">
<item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item>
</style>

<style name="AppTheme.NoActionBar.Fullscreen">
<item name="android:windowFullscreen">true</item>
<item name="android:windowContentOverlay">@null</item>
</style>

</resources>

Markdown中插入图片怎么定义图片的大小或比例

1
2
3
<div  align="center">    
<img src="url" width = "270" height = "480" alt="图片名称" />
</div>

Paint的效果研究

1
2
3
4
5
6
7
8
9
10
11
12
// 画笔颜色
paint.setColor(paintColor);
// 画笔风格
paint.setStyle(Paint.Style.STROKE);
// 画笔粗细
paint.setStrokeWidth(paintSize);
//设置是否使用抗锯齿功能,会消耗较大资源,绘制图形速度会变慢
paint.setAntiAlias(true);
//设定是否使用图像抖动处理,会使绘制出来的图片颜色更加平滑和饱满,图像更加清晰
paint.setDither(true);
// 圆角效果
paint.setPathEffect(new CornerPathEffect(50));

Android的Environment类

getExternalStoragePublicDirectory(String type)的type类型
类型 说明
DIRECTORY_PICTURES 图片存放的标准目录
DIRECTORY_DCIM 相机拍摄照片和视频的标准目录
DIRECTORY_ALARMS 系统提醒铃声存放的标准目录
DIRECTORY_DOWNLOADS 下载的标准目录
DIRECTORY_MOVIES 电影存放的标准目录
DIRECTORY_MUSIC 音乐存放的标准目录
DIRECTORY_NOTIFICATIONS 系统通知铃声存放的标准目录
DIRECTORY_PICTURES 图片存放的标准目录
DIRECTORY_PODCASTS 系统广播存放的标准目录
DIRECTORY_RINGTONES 系统铃声存放的标准目录