一. 前言

封装了画板的一些基本操作。

Github地址:XLDrawBoard

二. 自定义View

1. 自定义用到的属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//背景颜色
private int bg_color = Color.WHITE;

//画板的状态
private int board_state = BOARD_STATE_PEN;
public static final int BOARD_STATE_PEN = 0;
public static final int BOARD_STATE_ERASER = 1;

//线条颜色
private int lineColor = Color.MAGENTA;

//线条的粗细
private int lineWidth = 20;

//临时的路径
private Path temp_path;

//存储绘制的线条
private List<Graph> graphs;

//用于反撤销功能而存储的线条
private List<Graph> last_graphs;

2. 在values文件夹下自定义属性

1
2
3
4
5
6
7
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="XLDrawBoard">
<!--背景颜色-->
<attr name="bg_color" format="color"/>
</declare-styleable>
</resources>

3. 创建构造方法

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
/**
* 构造方法:Java代码初始化
* @param context
*/
public XLDrawBoard(Context context, int bg_color) {
super(context);

this.bg_color = bg_color;

//初始化操作
init();
}

/**
* 构造方法:Xml代码初始化
* @param context
* @param attrs
*/
public XLDrawBoard(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);

//解析属性
if (attrs != null){
//1.获得所欲属性值的集合
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.XLDrawBoard);
//2.解析属性
bg_color = typedArray.getColor(R.styleable.XLDrawBoard_bg_color,bg_color);
//3.释放资源
typedArray.recycle();
}

//初始化操作
init();
}

/**
* 初始化操作
*/
private void init() {
//初始化集合
graphs = new ArrayList<>();
last_graphs = new ArrayList<>();

//设置背景颜色
setBackgroundColor(bg_color);
}

4. 绘制过程

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
/**
* 重新绘制的过程
* @param canvas
*/
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);

//遍历Graphs数组
for (Graph graph : graphs) {
//绘制图形
canvas.drawPath(graph.path, graph.paint);
}
}

/**
* 内部类:管理每一个线条的路径以及画笔
*/
private static class Graph {
//路径
Path path;
//画笔
Paint paint;

//构造方法
Graph(Path path, Paint paint) {
this.path = path;
this.paint = paint;
}

}

5. 触摸事件

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
/**
* 触摸事件
* @param event
* @return
*/
@SuppressLint("ClickableViewAccessibility")
@Override
public boolean onTouchEvent(MotionEvent event) {
//获取触摸点
float x = event.getX();
float y = event.getY();

//触摸的过程
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
//初始化路径
temp_path = new Path();
//移动到起点
temp_path.moveTo(x,y);

//初始化画笔
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setColor(lineColor);
paint.setStrokeWidth(lineWidth);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeCap(Paint.Cap.ROUND);
paint.setStrokeJoin(Paint.Join.ROUND);

//确定Graph
Graph graph = new Graph(temp_path, paint);
graphs.add(graph);
last_graphs.add(graph);

break;
case MotionEvent.ACTION_MOVE:
//完善路径
temp_path.lineTo(x,y);

break;
case MotionEvent.ACTION_UP:
break;
}

//刷新
invalidate();

return true;
}

6. 事件的管理

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
73
74
/**
* 管理的方法-撤销功能的实现核心
* @return
*/
public void removeLast(){
//防止抛出异常,判断线条数量
if (graphs.size() > 0){
//移除最后一个线条
remove_graphs.add(graphs.get(graphs.size()-1));
graphs.remove(graphs.size()-1);

//刷新
invalidate();
}
}

/**
* 管理的方法-反撤销功能的实现
*/
public void resumeLast(){
//防止抛出异常,判断线条数量
if (remove_graphs.size() > 0){
//添加到绘制的线条中
graphs.add(remove_graphs.get(remove_graphs.size()-1));
//移除最后一个线条
remove_graphs.remove(remove_graphs.size()-1);

//刷新
invalidate();
return;
}

//小提示
Toast.makeText(getContext(), "不可还原", Toast.LENGTH_SHORT).show();
}

/**
* 管理的方法-清空功能的实现核心
* @return
*/
public void removeAll(){
//防止抛出异常,判断线条数量
if (graphs.size() > 0){
//移除所有的线条
graphs.clear();
remove_graphs.clear();

//刷新
invalidate();
}
}

/**
* 管理的方法-保存图片
* @return
*/
public Bitmap save(){
//创建一个bitmap
Bitmap bitmap = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888);
//以这个bitmap创建一个画板
Canvas canvas = new Canvas(bitmap);
//绘制背景
canvas.drawColor(bg_color);
//绘制线条
if (graphs != null){
//遍历Graphs数组
for (Graph graph : graphs) {
//绘制图形
canvas.drawPath(graph.path, graph.paint);
}
}

return bitmap;
}

三. 具体的使用

首先需要添加依赖,添加依赖之后,在布局文件中这样调用。

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
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
<?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"
tools:context=".MainActivity"
android:background="#696969">

<swu.xl.drawboard.XLDrawBoard
android:id="@+id/board"
android:layout_width="match_parent"
android:layout_height="500dp"
app:bg_color="#ffffff" />

<LinearLayout
android:layout_above="@id/operation"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center"
>

<Button
android:id="@+id/size_20"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="20"
/>

<Button
android:id="@+id/color_red"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#ff0000"
/>

<Button
android:id="@+id/color_green"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#00ff00"
/>

<Button
android:id="@+id/size_40"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="40"
/>

</LinearLayout>

<LinearLayout
android:id="@+id/operation"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:orientation="horizontal"
android:gravity="center"
>

<Button
android:id="@+id/pen"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="笔"
/>

<Button
android:id="@+id/undo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="撤销"
/>

<Button
android:id="@+id/resume"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="重做"
/>

<Button
android:id="@+id/clear"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="清空"
/>

<Button
android:id="@+id/eraser"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="橡皮"
/>

</LinearLayout>

</RelativeLayout>
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
public class MainActivity extends AppCompatActivity implements View.OnClickListener {

XLDrawBoard board;

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

board = findViewById(R.id.board);

findViewById(R.id.size_20).setOnClickListener(this);
findViewById(R.id.size_40).setOnClickListener(this);
findViewById(R.id.color_red).setOnClickListener(this);
findViewById(R.id.color_green).setOnClickListener(this);
findViewById(R.id.pen).setOnClickListener(this);
findViewById(R.id.undo).setOnClickListener(this);
findViewById(R.id.resume).setOnClickListener(this);
findViewById(R.id.clear).setOnClickListener(this);
findViewById(R.id.eraser).setOnClickListener(this);
}

@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.size_20:
board.setLineWidth(20);
break;

case R.id.size_40:
board.setLineWidth(40);

break;

case R.id.color_red:
board.setLineColor(Color.RED);
break;

case R.id.color_green:
board.setLineColor(Color.GREEN);

break;

case R.id.pen:
board.setBoard_state(XLDrawBoard.BOARD_STATE_PEN);
break;

case R.id.eraser:
board.setBoard_state(XLDrawBoard.BOARD_STATE_ERASER);

break;

case R.id.undo:
board.removeLast();
break;

case R.id.resume:
board.resumeLast();

break;

case R.id.clear:
board.removeAll();
break;

}
}
}