前言
大家都知道Android的设计架构是基于MVC的。对于MVC大家并没有”陌生”,而且一般来说,这个是一个广泛使用的框架。用游戏来讲述MVC是最好的,因为对于Control层的理解比较直观:control就是游戏的控制,上下左右,技能ABC,游戏的时间,事件;View是对绘制UI,场景的Face,人物Body(不知道Model是什么);Model是实体,具有动作。
MVC模型
大致的流向,用户的输入改变Model,并且改变View,View显示动作的Action给Control,Model的改变Notify控制。
重要的是:
- 模型和视图要严格的分离(不能有交互)
贪吃蛇游戏的简易mvc代码
在结构上安装model,view,control来分类,代码整体的包结构如下:
编写View
注意的是view是独立的模块,不能关联control和mode,l那么就有了以下代码。说明,view是游戏的UI,它只关心绘制的点,和场景,不关心蛇,游戏。所以,我们可以独立这个view来测试,譬如测试绘制蛇,绘制食物;
package com.owant.mvcsnakegame.view;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.View;
import com.owant.mvcsnakegame.model.Location;
import java.util.ArrayList;
/**
* Created by owant on 16/11/2016.
*/
public class SnakeGameView extends View {
public static final int screenX = 30;
public static final int screenY = 30;
private int dx;
private int dy;
public ArrayList<Location> body;
public Location food;
private Paint mPaint;
public SnakeGameView(Context context) {
this(context, null, 0);
}
public SnakeGameView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public SnakeGameView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mPaint = new Paint();
mPaint.setColor(Color.RED);
mPaint.setAntiAlias(true);
mPaint.setStyle(Paint.Style.FILL);
mPaint.setStrokeWidth(2);
// testDrawSnakeBody();
// testFood();
}
private void testFood() {
food = new Location();
food.rawY = 3;
food.rawX = 5;
}
private void testDrawSnakeBody() {
body = new ArrayList<>();
Location location = new Location();
location.rawX = 0;
location.rawY = 0;
body.add(location);
Location location1 = new Location();
location1.rawX = 0;
location1.rawY = 1;
body.add(location1);
Location location2 = new Location();
location2.rawX = 0;
location2.rawY = 2;
body.add(location2);
Location location3 = new Location();
location3.rawX = 0;
location3.rawY = 3;
body.add(location3);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
drawScreen(canvas);
drawSnake(canvas);
drawFood(canvas);
}
/**
* draw the game screen
*
* @param canvas
*/
private void drawScreen(Canvas canvas) {
mPaint.setColor(Color.RED);
//x
for (int i = 1; i < screenX; i++) {
canvas.drawLine(dx * i, 0, dx * i, getHeight(), mPaint);
}
//y
for (int i = 1; i < screenY; i++) {
canvas.drawLine(0, dy * i, getWidth(), dy * i, mPaint);
}
}
/**
* draw the snake body
*
* @param canvas
*/
private void drawSnake(Canvas canvas) {
mPaint.setStyle(Paint.Style.FILL);
mPaint.setColor(Color.RED);
if (body != null) {
for (Location b : body) {
canvas.drawRect(b.rawX * dx, b.rawY * dy, b.rawX * dx + dx, b.rawY * dy + dy, mPaint);
}
}
}
/**
* draw the food
*
* @param canvas
*/
private void drawFood(Canvas canvas) {
if (food != null) {
mPaint.setColor(Color.GREEN);
canvas.drawRect(food.rawX*dx, food.rawY*dx, food.rawX*dx + dx, food.rawY*dx + dy, mPaint);
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
dx = getWidth() / screenX;
dy = getHeight() / screenY;
}
public ArrayList<Location> getBody() {
return body;
}
public void setBody(ArrayList<Location> bodys) {
this.body = bodys;
}
@Override
public void invalidate() {
super.invalidate();
}
public Location getFood() {
return food;
}
public void setFood(Location food) {
this.food = food;
}
public void clearFood() {
this.food = null;
}
}
Model的编写
Snake。说明,蛇对自己的特性负责,不理会control和view;
package com.owant.mvcsnakegame.model;
import java.util.ArrayList;
/**
* Created by owant on 16/11/2016.
*/
public class Snake {
public boolean live = true;
public ArrayList<Location> body;
public int direction = Direction.down;
//30ms
public int speek = 300;
public Snake() {
body = new ArrayList<>();
init();
}
private void init() {
Location location = new Location();
location.rawX = 0;
location.rawY = 0;
body.add(location);
Location location1 = new Location();
location1.rawX = 0;
location1.rawY = 1;
body.add(location1);
Location location2 = new Location();
location2.rawX = 0;
location2.rawY = 2;
body.add(location2);
Location location3 = new Location();
location3.rawX = 0;
location3.rawY = 3;
}
public int getDirection() {
return direction;
}
public void setDirection(int direction) {
this.direction = direction;
}
public void growUp() {
}
public Location getHeard() {
return body.get(body.size() - 1);
}
public void move() {
Location heard = getHeard();
Location next = new Location();
if (direction == Direction.up) {//向上
next.rawX = heard.rawX;
next.rawY = heard.rawY - 1;
} else if (direction == Direction.down) {//向下
next.rawX = heard.rawX;
next.rawY = heard.rawY + 1;
} else if (direction == Direction.left) {//左边
next.rawX = heard.rawX - 1;
next.rawY = heard.rawY;
} else if (direction == Direction.right) {//右
next.rawX = heard.rawX + 1;
next.rawY = heard.rawY;
}
body.remove(0);
body.add(next);
}
}
Location 坐标点,代码省略。
Control
控制层,需要管理View和Model,它应该包括游戏的时间控制,游戏的输入。代码如下:
package com.owant.mvcsnakegame.control;
import com.owant.mvcsnakegame.model.Direction;
import com.owant.mvcsnakegame.model.Snake;
import com.owant.mvcsnakegame.view.SnakeGameView;
/**
* Created by owant on 16/11/2016.
*/
public class GameControl implements Runnable {
//control view
SnakeGameView view;
Snake snake;
public GameControl(SnakeGameView view, Snake snake) {
this.view = view;
this.snake = snake;
}
public void right() {
if (snake.getDirection() == Direction.down) {
snake.setDirection(Direction.left);
} else if (snake.getDirection() == Direction.up) {
snake.setDirection(Direction.right);
} else if (snake.getDirection() == Direction.left) {
snake.setDirection(Direction.up);
} else if (snake.getDirection() == Direction.right) {
snake.setDirection(Direction.down);
}
}
public void left() {
if (snake.getDirection() == Direction.left) {
snake.setDirection(Direction.down);
} else if (snake.getDirection() == Direction.right) {
snake.setDirection(Direction.up);
} else if (snake.getDirection() == Direction.up) {
snake.setDirection(Direction.left);
} else if (snake.getDirection() == Direction.down) {
snake.setDirection(Direction.right);
}
}
@Override
public void run() {
do {
try {
Thread.sleep(snake.speek);
} catch (InterruptedException e) {
e.printStackTrace();
}
snake.move();
view.post(new Runnable() {
@Override
public void run() {
view.invalidate();
}
});
} while (snake.live);
}
}
Activity
至于为何有Activity内,因为Android的MVC设计,Control落在了Activity上,也可以独立为一层特殊的Control,因为GameControl已经实现了所有的控制功能。Activity只是程序的入口,初始化实体,视图,接受输入传递给GameControl就行。
package com.owant.mvcsnakegame;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.KeyEvent;
import com.owant.mvcsnakegame.control.GameControl;
import com.owant.mvcsnakegame.model.Snake;
import com.owant.mvcsnakegame.view.SnakeGameView;
public class MainActivity extends AppCompatActivity {
GameControl control;
Snake snake;
SnakeGameView snakeGameView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
snake = new Snake();
snakeGameView = (SnakeGameView) findViewById(R.id.main_snake_view);
snakeGameView.setBody(snake.body);
control = new GameControl(snakeGameView, snake);
new Thread(control).start();
}
@Override
protected void onResume() {
super.onResume();
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
System.out.println(keyCode);
if (keyCode == KeyEvent.KEYCODE_A) {
control.left();
} else if (keyCode == KeyEvent.KEYCODE_D) {
control.right();
}
return super.onKeyDown(keyCode, event);
}
}
大致对于MVC的框架有了一定了解了吧。
总结
mvc是一种框架,不是设计模式。框架是很灵活的,可以变动,主要是对model,view,control的分类,而且如何关联到Android的代码中去。注意的是:
model和view是独立分开的。不要在view里面进行model和control的操作。
大多数的APP的control大多是loadData
一些错误和有异议的问题
我在网上看了一些人的mvc,发现他们的view层居然出现model和control,这是很错误的。譬如:
- 在ListView的Adapter中写点击事件。
- View出现Control和刷新,这个不能说错误,由于View自己刷新是Android自身提供的,而且代码写起来简易,可以考虑View自己刷新。