package com.netease.nim.demo.rts.doodle;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.os.Handler;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import com.netease.nim.demo.rts.doodle.action.Action;
import com.netease.nim.demo.rts.doodle.action.MyPath;
import java.util.ArrayList;
import java.util.List;
/**
* 涂鸦板控件(基类)
* <p/>
* Created by huangjun on 2015/6/24.
*/
public class DoodleView extends SurfaceView implements SurfaceHolder.Callback, TransactionObserver {
public enum Mode {
PAINT,
PLAYBACK,
BOTH
}
private final String TAG = "DoodleView";
private SurfaceHolder surfaceHolder;
private DoodleChannel paintChannel; // 绘图通道
private DoodleChannel playbackChannel; // 回放通道
private TransactionManager transactionManager; // 数据发送管理器
private int bgColor = Color.WHITE; // 背景颜色
private float zoom = 1.0f; // 收发数据时缩放倍数(归一化)
private float paintOffsetY = 0.0f; // 绘制时的Y偏移(去掉ActionBar,StatusBar,marginTop等高度)
private float paintOffsetX = 0.0f; // 绘制事的X偏移(去掉marginLeft的宽度)
private float lastX = 0.0f;
private float lastY = 0.0f;
public DoodleView(Context context) {
super(context);
init();
}
public DoodleView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
public DoodleView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
private void init() {
surfaceHolder = this.getHolder();
surfaceHolder.addCallback(this);
this.setFocusable(true);
}
/**
* 初始化(必须调用)
*
* @param mode 设置板书模式
* @param bgColor 设置板书的背景颜色
*/
public void init(String sessionId, String toAccount, Mode mode, int bgColor, Context context) {
this.transactionManager = new TransactionManager(sessionId, toAccount, context);
if (mode == Mode.PAINT || mode == Mode.BOTH) {
this.paintChannel = new DoodleChannel();
}
if (mode == Mode.PLAYBACK || mode == Mode.BOTH) {
this.playbackChannel = new DoodleChannel();
this.transactionManager.registerTransactionObserver(this);
}
this.bgColor = bgColor;
}
public void onResume() {
new Handler(getContext().getMainLooper()).postDelayed(new Runnable() {
@Override
public void run() {
Canvas canvas = surfaceHolder.lockCanvas();
if (canvas == null) {
return;
}
drawHistoryActions(canvas);
surfaceHolder.unlockCanvasAndPost(canvas);
}
}, 50);
}
/**
* 退出涂鸦板时调用
*/
public void end() {
if (transactionManager != null) {
transactionManager.end();
}
}
/**
* 设置绘制时画笔的偏移
*
* @param x DoodleView的MarginLeft的宽度
* @param y ActionBar与StatusBar及DoodleView的MarginTop的高度的和
*/
public void setPaintOffset(float x, float y) {
this.paintOffsetX = x;
this.paintOffsetY = y;
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
onPaintBackground();
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
Log.i(TAG, "surfaceView created, width = " + width + ", height = " + height);
zoom = width;
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
}
/**
* ******************************* 绘图板 ****************************
*/
/**
* 设置绘制时的画笔颜色
*
* @param color
*/
public void setPaintColor(String color) {
this.paintChannel.setColor(color);
}
/**
* 设置回放时的画笔颜色
*
* @param color
*/
public void setPlaybackColor(String color) {
this.playbackChannel.setColor(color);
}
/**
* 设置画笔的粗细
*
* @param size
*/
public void setPaintSize(int size) {
if (size > 0) {
this.paintChannel.paintSize = size;
this.playbackChannel.paintSize = size;
}
}
/**
* 设置当前画笔的形状
*
* @param type
*/
public void setPaintType(int type) {
this.paintChannel.setType(type);
this.playbackChannel.setType(type);
}
/**
* 设置当前画笔为橡皮擦
*
* @param size 橡皮擦的大小(画笔的粗细)
*/
public void setEraseType(int size) {
this.paintChannel.setEraseType(this.bgColor, size);
}
/**
* 撤销一步
*
* @return 撤销是否成功
*/
public synchronized boolean paintBack() {
if (paintChannel == null) {
return false;
}
boolean res = back(true);
transactionManager.sendRevokeTransaction();
return res;
}
/**
*
*/
public synchronized void clear() {
clearAll();
transactionManager.sendClearSelfTransaction();
}
/**
* 触摸绘图
*
* @param event
* @return
*/
@Override
public synchronized boolean onTouchEvent(MotionEvent event) {
int action = event.getAction();
if (action == MotionEvent.ACTION_CANCEL) {
return false;
}
float touchX = event.getRawX();
float touchY = event.getRawY();
touchX -= paintOffsetX;
touchY -= paintOffsetY;
Log.i(TAG, "x=" + touchX + ", y=" + touchY);
switch (action) {
case MotionEvent.ACTION_DOWN:
onPaintActionStart(touchX, touchY);
break;
case MotionEvent.ACTION_MOVE:
onPaintActionMove(touchX, touchY);
break;
case MotionEvent.ACTION_UP:
onPaintActionEnd();
break;
default:
break;
}
return true;
}
private void onPaintActionStart(float x, float y) {
if (paintChannel == null) {
return;
}
onActionStart(true, x, y);
transactionManager.sendStartTransaction(x / zoom, y / zoom);
}
private void onPaintActionMove(float x, float y) {
if (paintChannel == null) {
return;
}
if (!isNewPoint(x, y)) {
return;
}
onActionMove(true, x, y);
transactionManager.sendMoveTransaction(x / zoom, y / zoom);
}
private void onPaintActionEnd() {
if (paintChannel == null) {
return;
}
onActionEnd(true);
transactionManager.sendEndTransaction(lastX / zoom, lastY / zoom);
}
/**
* ******************************* 回放板 ****************************
*/
@Override
public synchronized void onTransaction(List<Transaction> transactions) {
Log.i(TAG, "onTransaction, size =" + transactions.size());
if (playbackChannel == null) {
return;
}
List<Transaction> cache = new ArrayList<>(transactions.size());
for (Transaction t : transactions) {
if (t == null) {
continue;
}
if (t.isPaint()) {
// 正常画笔
cache.add(t);
} else {
onMultiTransactionsDraw(cache);
cache.clear();
if (t.isRevoke()) {
back(false);
} else if (t.isClearSelf()) {
clearAll();
transactionManager.sendClearAckTransaction();
} else if (t.isClearAck()) {
clearAll();
}
}
}
if (cache.size() > 0) {
onMultiTransactionsDraw(cache);
cache.clear();
}
}
private void setPlaybackEraseType(int size) {
this.playbackChannel.setEraseType(this.bgColor, size);
}
/**
* ******************************* 基础绘图封装 ****************************
*/
private void onPaintBackground() {
Canvas canvas = surfaceHolder.lockCanvas();
if (canvas == null) {
return;
}
canvas.drawColor(bgColor); // 涂鸦板背景颜色
surfaceHolder.unlockCanvasAndPost(canvas);
}
private void onActionStart(boolean isPaintView, float x, float y) {
DoodleChannel channel = isPaintView ? paintChannel : playbackChannel;
if (channel == null) {
return;
}
channel.action = new MyPath(x, y, channel.paintColor, channel.paintSize);
}
private void onActionMove(boolean isPaintView, float x, float y) {
DoodleChannel channel = isPaintView ? paintChannel : playbackChannel;
if (channel == null) {
return;
}
if (channel.action == null) {
// 有可能action被清空,此时收到move,重新补个start
onPaintActionStart(x, y);
}
Canvas canvas = surfaceHolder.lockCanvas();
drawHistoryActions(canvas);
// 绘制当前Action
channel.action.onMove(x, y);
channel.action.onDraw(canvas);
if (canvas != null) {
surfaceHolder.unlockCanvasAndPost(canvas);
}
}
private void onActionEnd(boolean isPaintView) {
DoodleChannel channel = isPaintView ? paintChannel : playbackChannel;
if (channel == null || channel.action == null) {
return;
}
channel.actions.add(channel.action);
channel.action = null;
}
private void onMultiTransactionsDraw(List<Transaction> transactions) {
if (transactions == null || transactions.size() == 0) {
return;
}
Canvas canvas = surfaceHolder.lockCanvas();
drawHistoryActions(canvas);
// 绘制新的数据
for (Transaction t : transactions) {
switch (t.getStep()) {
case Transaction.ActionStep.START:
if (playbackChannel.action != null) {
// 如果没有收到end包,在这里补提交
playbackChannel.actions.add(playbackChannel.action);
}
playbackChannel.action = new MyPath(t.getX() * zoom, t.getY() * zoom, playbackChannel
.paintColor, playbackChannel.paintSize);
playbackChannel.action.onStart(canvas);
break;
case Transaction.ActionStep.MOVE:
if (playbackChannel.action != null) {
playbackChannel.action.onMove(t.getX() * zoom, t.getY() * zoom);
playbackChannel.action.onDraw(canvas);
}
break;
case Transaction.ActionStep.END:
if (playbackChannel.action != null) {
playbackChannel.actions.add(playbackChannel.action);
playbackChannel.action = null;
}
break;
default:
break;
}
}
if (canvas != null) {
surfaceHolder.unlockCanvasAndPost(canvas);
}
}
private void drawHistoryActions(Canvas canvas) {
if (canvas == null) {
return;
}
// 绘制背景
canvas.drawColor(bgColor);
if (playbackChannel != null && playbackChannel.actions != null) {
for (Action a : playbackChannel.actions) {
a.onDraw(canvas);
}
// 绘制当前
if (playbackChannel.action != null) {
playbackChannel.action.onDraw(canvas);
}
}
// 绘制所有历史Action
if (paintChannel != null && paintChannel.actions != null) {
for (Action a : paintChannel.actions) {
a.onDraw(canvas);
}
// 绘制当前
if (paintChannel.action != null) {
paintChannel.action.onDraw(canvas);
}
}
}
private boolean back(boolean isPaintView) {
DoodleChannel channel = isPaintView ? paintChannel : playbackChannel;
if (channel == null) {
return false;
}
if (channel.actions != null && channel.actions.size() > 0) {
channel.actions.remove(channel.actions.size() - 1);
Canvas canvas = surfaceHolder.lockCanvas();
if (canvas == null) {
return false;
}
drawHistoryActions(canvas);
surfaceHolder.unlockCanvasAndPost(canvas);
return true;
}
return false;
}
private void clearAll() {
clear(false);
clear(true);
}
private void clear(boolean isPaintView) {
DoodleChannel channel = isPaintView ? paintChannel : playbackChannel;
if (channel == null) {
return;
}
if (channel.actions != null) {
channel.actions.clear();
}
channel.action = null;
Canvas canvas = surfaceHolder.lockCanvas();
if (canvas == null) {
return;
}
drawHistoryActions(canvas);
surfaceHolder.unlockCanvasAndPost(canvas);
}
private boolean isNewPoint(float x, float y) {
if (Math.abs(x - lastX) <= 0.1f && Math.abs(y - lastY) <= 0.1f) {
return false;
}
lastX = x;
lastY = y;
return true;
}
}