演示效果:
需求描述: 在发送礼物按钮上进行连击,界面上展示礼物且礼物数字随着连击事件增加。需求扩展:数字变动的间隔时间可通过参数设置。
思路简析: 这可以看作是一个生产-消费模型,用队列来存储连击事件。什么是状态机思想呢?简单地说,就是n个状态在不同条件下互相转化的过程。那么如何通过状态机思想来分析这个需求呢?
首先,罗列出所有状态:START、WORKING、WAITING、STOP
然后画草图,画出状态之间的转换条件:
按照状态机思想画上草图后,状态之间的关系链就会变得很清晰。
源码:
public class ComboUtil<T extends ComboUtil.TimeEvt> {
//点击事件队列
private LinkedList<T> queue;
private ComboHandler<T> mHandler;
/**
* @param noRspTime 无响应时间 两次点击间隔noRspTime时默认为新一轮连击
* @param takeEvtInterval 取队列数据的间隔时间,同一轮连击里点击事件的间隔时间
*/
public ComboUtil(Callback<T> callback, long noRspTime, long takeEvtInterval) {
queue = new LinkedList<>();
mHandler = new ComboHandler<T>(callback, noRspTime, takeEvtInterval) {
@Override
public T takeEvt() {
return queue.isEmpty() ? null : queue.removeFirst();
}
@Override
public T getEvt() {
return queue.isEmpty() ? null : queue.getFirst();
}
@Override
public boolean isEmptyQueue() {
return queue.isEmpty();
}
};
}
public void onClick(T evt) {
queue.add(evt);
Log.d("连击测试", "enqueue");
mHandler.onModelEnqueue();
}
public void onDestroy() {
mHandler.onDestroy();
queue.clear();
}
public static class TimeEvt {
private long mills;
public TimeEvt() {
this.mills = System.currentTimeMillis();
}
public long getMills() {
return mills;
}
}
public interface Callback<T> {
//连击准备
void onStart();
//连击回调
void onCombo(T evt);
//等待点击
void onWaiting();
//连击结束
void onStop();
}
public static abstract class ComboHandler<T> extends Handler {
public static final int START = 1;
public static final int WORKING = 2;
public static final int STOP = 4;
public static final int WAITING = 3;
private ComboUtil.Callback<T> callback;
private long takeEvtInterval;
private long noRspTime;//单位:毫秒
public ComboHandler(ComboUtil.Callback<T> callback, long noRspTime, long takeEvtInterval) {
this.callback = callback;
this.takeEvtInterval = takeEvtInterval;
this.noRspTime = noRspTime;
}
@Override
public void handleMessage(Message msg) {
//用msg.arg1表示上一个状态
int from = msg.arg1;
Message newMsg = Message.obtain();
switch (msg.what) {
case START:
if (from < STOP && from >= START) return;//不符合DFA状态机算法图
Log.d("连击测试", "receive START msg");
newMsg.arg1 = START;
if (callback != null)
callback.onStart();
if (isEmptyQueue()) {
newMsg.what = WAITING;//队列为空进入等待状态
} else {
newMsg.what = WORKING;//进入工作状态
}
sendMessage(newMsg);
break;
case WORKING:
if (from == WORKING || from == WAITING || from == START) {
//符合DFA状态机算法图
Log.d("连击测试", "receive WORKING msg");
newMsg.arg1 = WORKING;
//从队尾取出一个点击事件
T t = takeEvt();
ComboUtil.TimeEvt evt = (TimeEvt) t;
if (evt == null) {
//当前队列已空
if (from == WAITING) {
//上一个状态已经是等待状态了
newMsg.what = STOP;
sendMessage(newMsg);//直接结束本次连击
} else {
//进入等待状态
newMsg.what = WAITING;
sendMessage(newMsg);
}
} else {
if (callback != null)
callback.onCombo(t);
//判断下一条数据时间
ComboUtil.TimeEvt nxtEvt = (TimeEvt) getEvt();
if (nxtEvt != null && nxtEvt.getMills() - evt.getMills() >= noRspTime) {
//认为本次连击结束,并重新开始新一轮连击
newMsg.what = STOP;
} else newMsg.what = WORKING;
sendMessageDelayed(newMsg, takeEvtInterval);
}
}
break;
case WAITING:
if (from == START || from == WORKING || from == WAITING) {
Log.d("连击测试", "receive WAITING msg");
newMsg.arg1 = WAITING;
boolean isEmpty = isEmptyQueue();
//符合DFA状态机算法图
if (callback != null)
callback.onWaiting();
switch (from) {
case START:
case WORKING:
//取数据时发现队列已空,进入等待状态,noRspTime时间后再次判断队列是否为空
newMsg.what = WAITING;
sendMessageDelayed(newMsg, noRspTime);
break;
case WAITING:
if (isEmpty) {
//经过noRspTime后 数据队列仍然为空,直接发送stop msg 结束这次连击
newMsg.what = STOP;
} else {
//经过noRspTime后 数据队列已入队新的数据,发送take msg 继续取数据
newMsg.what = WORKING;
}
sendMessage(newMsg);
break;
}
}
break;
case STOP:
if (from == WORKING || from == WAITING) {
Log.d("连击测试", "receive STOP msg");
newMsg.arg1 = STOP;
if (callback != null)
callback.onStop();
//发送stop msg到接收stop msg之间可能还有入队操作,需要再检查一次队列
if (!isEmptyQueue()) {
newMsg.what = START;
sendMessage(newMsg);
}
}
break;
}
}
//读写操作
public abstract T takeEvt();
//只读操作
public abstract T getEvt();
public abstract boolean isEmptyQueue();
//是否是空闲状态
public boolean isIDLE() {
boolean hasMsg = hasMessages(START) || hasMessages(WORKING) || hasMessages(WAITING) || hasMessages(STOP);
return !hasMsg;
}
/**
* 当前空闲 则发送start msg 开始工作
* 开始工作后会自动按照逻辑从数据model队列取数据
*/
public void onModelEnqueue() {
if (isIDLE())
sendEmptyMessage(START);
else if (hasMessages(WAITING)) {
//如果是等待状态-唤醒
removeMessages(WAITING);
Message newMsg = Message.obtain();
newMsg.arg1 = WAITING;
newMsg.what = WORKING;
sendMessage(newMsg);
}
}
public void onDestroy() {
removeCallbacksAndMessages(null);
if (callback != null)
callback.onStop();
}
}
}
大家有什么好用的制图软件推荐吗?
有任何疑问或建议欢迎留言讨论!