吹毛求疵画图板
因为之前java的画图板只是实现了基础功能,所以我想在android上做一个功能完善的画图板。
我们首先要设计界面,现在主流的android 的界面主要是relative layout 和line layout 相互嵌套使用。
另外在android:onClick="cancel" 可以轻松绑定在主界面MainActivity写的方法,轻松快捷,超好用的有木有。组件一般都是先拖上去再在xml中修改代码。另外在布局中,熟练使用android:layout_weight="1" 我一般先再xml中先写好,然后再在graphical_layout 拉界面调整比例。
然后今天我还知道了如何更改主界面的theme,在AndroidManifest中的application中选择system resours
这是我的界面图:(4寸一般手机是刚好可以铺开的)
布局格式:先是整体用相对布局然后中间嵌套竖直流布局(充满前面一个组件,既充满整个屏幕),然后再在中间加一个imageView横向充满屏幕,下面再加上一个横向线型布局,中间加上各种按钮组件(全部的权重都设为android:layout_weight="1")。下面是代码:
<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"
android:background="@drawable/background"
tools:context=".MainActivity" >
<LinearLayout
android:id="@+id/linearLayout1"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<ImageView
android:id="@+id/imageView1"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_weight="32.15"
android:src="@drawable/backgrand" />
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="horizontal" >
<!--onClick绑定点击方法 -->
<Button
android:id="@+id/line"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="setLine"
android:layout_weight="1"
android:text="line" />
<Button
android:id="@+id/rectangle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="setRect"
android:layout_weight="1"
android:text="rect" />
<Button
android:id="@+id/pen"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="setPen"
android:layout_weight="1"
android:text="pen" />
<Button
android:id="@+id/cancel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:onClick="cancel"
android:text="cancel" />
<Button
android:id="@+id/clear"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="clear"
android:layout_weight="1"
android:text="clear" />
</LinearLayout>
</LinearLayout>
</RelativeLayout>
具体画直线的歩奏这里就先省略。这里为了实现一般画图板的拖动效果,我们首先采用了双缓冲画图技术。就是采用两张Bitmap 用一张bitmap存本来的图,在MotionEvent.ACTION_MOVE:用另一张显示现在正在拖动的的图片。代码如下:
// 如果没有位图,就新建一个
if (bitmap == null) {
bitmap = Bitmap.createBitmap(imageView.getWidth(),
imageView.getHeight(), Config.ARGB_8888);
// 宽,高,第一个alpha 是透明度 后面是3原色
// 实例化一个canvas对象,通过bitmap
canvas = new Canvas(bitmap);
paint.setStyle(Style.STROKE);//画一个空心的
paint.setColor(Color.RED);
paint.setStrokeWidth(5.0f);
canvas.drawRect(0, 0, imageView.getWidth(), imageView.getHeight(), paint);//画入新线
//将缓冲图输出
imageView.setImageBitmap(bitmap);
}
// 获取触控方式
int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
x1=event.getX();
y1=event.getY();
preX=x1;
preY=y1;
break;
case MotionEvent.ACTION_UP:
x2=event.getX();
y2=event.getY();
//设置画笔的属性
paint.setColor(Color.BLACK);
paint.setStrokeWidth(5.0f);
// paint.setStyle(Style.STROKE);//画一个空心的
// paint.setStyle(Style.FILL);
//画线
canvas.drawLine(x1, y1, x2, y2, paint);
//输出到位图,再镶嵌到线框
line line=new line();
line.setAttribute(1);
line.setX1(x1);
line.setX2(x2);
line.setY1(y1);
line.setY2(y2);
list.add(line);
imageView.setImageBitmap(bitmap);
break;
case MotionEvent.ACTION_MOVE:
x2=event.getX();
y2=event.getY();
//设置一个缓冲位图
Bitmap buffer = Bitmap.createBitmap(imageView.getWidth(), imageView.getHeight(), Config.ARGB_8888);
Canvas canvas2 = new Canvas(buffer);
//新化一个缓冲图
canvas2.drawBitmap(bitmap, 0, 0, new Paint());//将以前画的全部写入缓冲区
paint.setColor(Color.BLACK);
paint.setStrokeWidth(5.0f);
canvas2.drawLine(x1, y1, x2, y2, paint);//画入新线
//将缓冲图输出
imageView.setImageBitmap(buffer);
break;
}
但是运用这种技术,我们发现手机上可能会有一些不流畅的感觉。但是我们只用一个bitmap将之前存在队列中的图形对象,在move行为结束,新的直线即将即将画出来的时候,先将队列中的图形全部画出。这样就不用每move一下就新建一个对象,减少了内存开销。仅提供思路,有时间再去尝试下。
有了画图板,我们还要做到撤销,要做到撤销为了实现撤销的效果我们每一次画的图都要保存在队列里撤销时,我们删除掉最近一个图形,然后再调用子类中的draw方法,将之前的图形画出来。为了实现这个效果,我们首先要有一个父类(用来做画图存储列表),各个图形对象继承这个父类,当每画一个图形,就把这个图形丢到这个队列中去,子类转换为父类。当点击撤销时,先将父类对象取出(不能直接取出子类),,这时因为在存储时子类转换为父类,所以在JAVA中,子类的方法被隐藏了。再强制转换为子类,就可以调用draw的方法,代码如下:
package com.example.drawboard;
import android.graphics.Canvas;
import android.graphics.Paint;
/**
* 所有图形的父类
* @author
*
*/
public abstract class paintList {
public int getAttribute() {
return attribute;
}
public void setAttribute(int attribute) {
this.attribute = attribute;
}
public float getX1() {
return x1;
}
public void setX1(float x1) {
this.x1 = x1;
}
public float getY1() {
return y1;
}
public void setY1(float y1) {
this.y1 = y1;
}
public float getX2() {
return x2;
}
public void setX2(float x2) {
this.x2 = x2;
}
public float getY2() {
return y2;
}
public void setY2(float y2) {
this.y2 = y2;
}
private int attribute;//属性,1代表1按钮
private float x1,y1,x2,y2;
/**
* 绘制的方法
* @param g
*/
public abstract void draw(Canvas canvas,Paint paint);
package com.example.drawboard;
import android.graphics.Canvas;
import android.graphics.Paint;
/**
* 直线类
* @author
*
*/
public class line extends paintList{
public line(){
//构造时设置标志位
this.setAttribute(1);
}
public void draw(Canvas canvas,Paint paint){
canvas.drawLine(this.getX1(), this.getY1(), this.getX2(), this.getY2(), paint);
}
}
bitmap = Bitmap.createBitmap(imageView.getWidth(),
imageView.getHeight(), Config.ARGB_8888);
// 宽,高,第一个alpha 是透明度 后面是3原色
// 实例化一个canvas对象,通过bitmap
canvas = new Canvas(bitmap);
paint.setStyle(Style.STROKE);//画一个空心的
paint.setColor(Color.RED);
paint.setStrokeWidth(5.0f);
canvas.drawRect(0, 0, imageView.getWidth(), imageView.getHeight(), paint);//画入新线
//将缓冲图输出
if(list.size()!=0){
paint.setColor(Color.BLACK);
list.remove(list.size()-1);
int total=list.size();
//还原图片
for(int i=0;i<total;i++){
paintList T=list.get(i);
//如果是直线的话
if(T.getAttribute()==1){
line line =(line)T;
line.draw(canvas, paint);
}
if(T.getAttribute()==2){
rect rect =(rect)T;
rect.draw(canvas, paint);
}
if(T.getAttribute()==3){
pen pen =(pen)T;
System.out.println("hi");
pen.draw(canvas, paint);
}
}
}
imageView.setImageBitmap(bitmap);
最后我来讲一下,如何做到做到铅笔的效果,正所谓所见接虚,我们看到的一条曲线实际上也是由许多直线所构成。我们每画一个pen对象我们就在pen对象中加上直线队列。撤销时再一个个重画出来,就有我们想要的效果了。
package com.example.drawboard;
import java.util.ArrayList;
import android.graphics.Canvas;
import android.graphics.Paint;
/**
* 铅笔类
* @author
*/
public class pen extends paintList{
public pen(){
//构造时设置标志位
this.setAttribute(3);
}
//应该有一个队列存储铅笔的轨迹
private ArrayList<line> list=new ArrayList<line>();
public ArrayList<line> getList() {
return list;
}
public void setList(ArrayList<line> list) {
this.list = list;
}
@Override
public void draw(Canvas canvas, Paint paint) {
// TODO Auto-generated method stub
//如果是直线的话
int total=list.size();
for(int i=0;i<total;i++){
line line =list.get(i);
line.draw(canvas, paint);
}
}
}
感悟:
命名规则
类名首字母要大写,否则容易与对象名混淆。
另外在android工程中,我们要看到system.out 我们就要在logcat 中saved filters 旁边的绿色加号按钮,选择要查看的内容的关键字。这点与java不同。