通常对于在修图或者图片打点的应用中,我们会有像在黑板上画画一样的需求,类似于下面的效果:
要实现这样一个功能,首先我们自定义一个画板,画板上操作,它频率很高并且实时性很高的,所以我们需要自定义一个 view继承surfaceview:
public class MyView extends SurfaceView implements Callback, OnTouchListener {
private Paint p = null;
private Path path = null;
public int textSize = 1;
private SurfaceHolder holder;
public int currentColor = Color.RED;
private Bitmap bitmap = null;
public void setTextSize(int textSize) {
this.textSize = textSize;
initPamars();
}
public void setCurrentColor(int currentColor) {
this.currentColor = currentColor;
initPamars();
}
public MyView(Context context) {
super(context);
if (holder == null)
holder = getHolder();
holder.addCallback(this);
initPamars();
}
public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
if (holder == null)
holder = getHolder();
holder.addCallback(this);
initPamars();
}
private void initPamars() {
p = new Paint();
path = new Path();
p.setColor(currentColor);
p.setStrokeWidth(textSize);
p.setTextSize(20);
p.setStyle(Style.STROKE);
//消除锯齿
p.setAntiAlias(true);
// 设置监听
setOnTouchListener(this);
}
public void setBackground(int id) {
BitmapDrawable bd = (BitmapDrawable) getResources().getDrawable(id);
Bitmap bm = bd.getBitmap();
Matrix matrix = new Matrix();
matrix.setScale(0.655f, 0.65f);
bitmap = Bitmap.createBitmap(bm, 0, 0, bm.getWidth(), bm.getHeight(), matrix, true);
}
/**
* 自定义画的方法canvas.drawPath
*/
public void draw(boolean isTogether) {
Canvas canvas = holder.lockCanvas();
canvas.drawColor(Color.GRAY);
canvas.drawBitmap(bitmap, 0, 0, new Paint());
if (list.size() > 0) {
for (int i = 0; i < list.size(); i++) {
Action a = list.get(i);
if (!isTogether) {
if (a.isDelete) {
list.remove(i);
i--;
}
}
canvas.drawPath(a.getPath(), a.getPaint());
}
}
holder.unlockCanvasAndPost(canvas);
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height) {
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
this.holder = holder;
draw(false);
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
}
/**
* 触摸事件
*/
@Override
public boolean onTouch(View v, MotionEvent event) {
float x = event.getX();
float y = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
initPamars();
path.moveTo(x, y);
break;
case MotionEvent.ACTION_MOVE:
path.lineTo(x, y);
Action action = new Action();
action.setPaint(p);
action.setDelete(true);
action.setPath(path);
list.addLast(action);
draw(true);
break;
case MotionEvent.ACTION_UP:
if (!list.isEmpty())
list.getLast().setDelete(false);
draw(false);
break;
default:
break;
}
return true;
}
LinkedList list = new LinkedList<>();
class Action {
Path path;
Paint paint;
boolean isDelete;
public void setDelete(boolean delete) {
isDelete = delete;
}
public void setPaint(Paint paint) {
this.paint = paint;
}
public void setPath(Path path) {
this.path = path;
}
public Paint getPaint() {
return paint;
}
public Path getPath() {
return path;
}
}
/**
* 回退撤销
*/
public void back() {
if (!list.isEmpty()) {
list.removeLast();
}
draw(false);
}
/**
* 清理画布
*/
public void clear() {
list = new LinkedList<>();
draw(false);
}
/**
* 获取图片和历史画笔保存至指定文件
*/
public boolean savePicToSdcard(String path) {
try {
File file = new File(path);
Bitmap bit = Bitmap.createBitmap(bitmap);
Canvas canvas = new Canvas(bit);
if (list.size() > 0) {
for (int i = 0; i < list.size(); i++) {
Action a = list.get(i);
canvas.drawPath(a.getPath(), a.getPaint());
}
}
OutputStream stream = new FileOutputStream(file);
bit.compress(Bitmap.CompressFormat.PNG, 100, stream);
stream.close();
return true;
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
}
然后在布局文件中加上我们基本的功能按钮:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="horizontal">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:orientation="vertical">
<com.widget.huaban.MyView
android:id="@+id/myview"
android:layout_width="300dp"
android:layout_height="300dp" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:layout_marginTop="20dp"
android:orientation="horizontal">
<View
android:id="@+id/red_view"
android:layout_width="30dp"
android:layout_height="30dp"
android:background="@android:color/holo_red_light" />
<View
android:id="@+id/blue_view"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_marginLeft="50dp"
android:background="@android:color/holo_blue_light" />
<View
android:id="@+id/black_view"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_marginLeft="50dp"
android:background="@android:color/black" />
<View
android:id="@+id/green_view"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_marginLeft="50dp" android:background="@android:color/holo_green_light" />
LinearLayout>
<TextView
android:id="@+id/sizeprogress_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="20dp"
android:text="1"
android:textSize="20sp" />
<SeekBar
android:id="@+id/seekbar"
android:layout_width="400dp"
android:layout_height="wrap_content"
android:max="50"
android:progress="1" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:layout_marginTop="10dp"
android:orientation="horizontal">
<Button
android:layout_width="120dp"
android:layout_height="50dp"
android:gravity="center"
android:onClick="back"
android:text="撤销" />
<Button
android:layout_width="120dp"
android:layout_height="50dp"
android:gravity="center"
android:onClick="clear"
android:text="清除" />
<Button
android:layout_width="120dp"
android:layout_height="50dp"
android:gravity="center"
android:onClick="save"
android:text="保存" />
LinearLayout>
LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="20dp"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="新涂鸦照片:" />
<ImageView
android:id="@+id/showview_iv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:scaleType="fitXY" />
LinearLayout>
LinearLayout>
最后在activity中调用:
private MyView myView;
private TextView sizeprogress_tv;
private SeekBar seekbar;
private View blue_view, red_view, black_view, green_view;
private ImageView showview_iv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_huaban_layout);
showview_iv = (ImageView) findViewById(R.id.showview_iv);
blue_view = findViewById(R.id.blue_view);
red_view = findViewById(R.id.red_view);
black_view = findViewById(R.id.black_view);
green_view = findViewById(R.id.green_view);
myView = (MyView) findViewById(R.id.myview);
myView.setBackground(R.drawable.img_3);
seekbar = (SeekBar) findViewById(R.id.seekbar);
sizeprogress_tv = (TextView) findViewById(R.id.sizeprogress_tv);
seekbar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int i, boolean b) {
sizeprogress_tv.setText("" + i);
myView.setTextSize(i);
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
});
blue_view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
myView.setCurrentColor(Color.BLUE);
}
});
black_view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
myView.setCurrentColor(Color.BLACK);
}
});
red_view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
myView.setCurrentColor(Color.RED);
}
});
green_view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
myView.setCurrentColor(Color.GREEN);
}
});
}
public void back(View view) {
myView.back();
}
public void clear(View view) {
myView.clear();
}
public void save(View view) {
String path = Configutils.GalleryPath + "test.png";
boolean b = myView.savePicToSdcard(path);
if (b) {
showview_iv.setImageBitmap(BitmapFactory.decodeFile(path));
}
}
这样我们就完成了截图中的所有功能!
**小结:
1.在surfaceCreated回调方法中得到SurfaceHolder;
2.重写onTouch,并且在ACTION_DOWN时初始化path和paint,将path移动到点击的位置;在ACTION_MOVE中将path所走的线路利用SurfaceHolder得到的canvas绘制出来,由于在手指移动过程中每移动一个坐标都会绘制一次,都会在集合中添加一个action对象,所以要将移动时的绘制对象打上标记,我这里用的是‘isDelete’,以便手指抬起时将移动过程中的对象删除,只保留抬起手指时最后一次完整路径的action;在ACTION_UP中将最后一次的action对象的标记改为保留,也就是false;
3.在修改画笔颜色和粗细时,重新初始化path和paint;
4.提供画板背景的修改方法,传入一张图片在绘制线路时将背景图画上;
5.回退撤销操作,则将线路历史集合中的最后一个移除并且刷新;
6.保存涂鸦时,则将传入的背景图对象bitmap传给canvas对象,然后将线路历史全部绘制上写入指定的文件中。**