最近在学安卓UI方面,就是看到自定义View那块,突然想写个2048玩下,就先看网上极客学院的视频然后知道大概的思路就开始自己写了,然后再根据网上各种人的博客,自己慢慢理解发现还是蛮好写的,,,,but网上博客和视频的代码都大多是一样的,我遇到了问题,,,发现自己写的自定义View游戏界面根本加载不出来,,,log了好久发现是一个获取屏幕宽度的方法运行顺序问题,,,,发现网上有的的博客代码压根都没运行过,遇到问题还是自己解决
先看下效果图
功能:
1.2048游戏逻辑实现
2.记录得分和保存最高分
3.界面的优化
4.最后游戏结束和获胜的判定
实现的逻辑顺序:
1.设计主布局页面
2.写GameView类,并放到主页面里面
3.监听上,下,左,右四个方向的滑动判定
4.写卡片类
5.把卡片类放进GameView里面,完成基本界面
6.卡片随机出现设计和实现
7.滑动时候的逻辑实现,滑动时候的得分和界面变化
8.最后游戏失败和胜利的逻辑判断
9.记录最高分的逻辑
10.界面的优化,颜色配色,图标
//如果想看具体的实现过程,建议看极客学院的2048教学视频(可能有有点老了,,有的地方有问题),,但是你如果全按他教的就游戏布局完全加载不出来。。反正我是这样,天生bug体质,,,但我的代码解决这个问题了,就是在获取屏幕界面宽度那块的问题,我重新写了那块,可以仔细看下
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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:orientation="vertical"
tools:context="com.example.hasee.a2048dome.MainActivity"
android:background="#FFFFCC">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/scrore"
android:textSize="20sp" />
<TextView
android:id="@+id/tvSorce"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:layout_width="145dp"
android:layout_height="wrap_content" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/maxscore"
android:textSize="20sp" />
<TextView
android:id="@+id/maxSorce"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
<TextView
android:layout_width="match_parent"
android:layout_height="80dp"
android:background="#FFCC99"
android:text="笑笑的2048"
android:textSize="30sp"
android:gravity="center"/>
<com.example.hasee.a2048dome.GameView
android:layout_width="fill_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:id="@+id/gameView" />
<Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:id="@+id/bt_cx"
android:text="重来"
android:background="#FFFFCC"/>
</LinearLayout>
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.graphics.Point;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.widget.GridLayout;
import java.util.ArrayList;
import java.util.List;
/**
* Created by hasee on 2018/3/13.
* 游戏界面类
*/
public class GameView extends GridLayout {
//我们需要定义一个二维数组来记录GameView初始化时生成的16个卡片类
private Card[][] cardsMap = new Card[4][4];
private static GameView gameView = null;
public static GameView getGameView() {
return gameView;
}
private List<Point> points = new ArrayList<Point>();
public GameView(Context context) {
super(context);
gameView = this;
initGameView();
}
public GameView(Context context, AttributeSet attrs) {
super(context, attrs);
gameView = this;
initGameView();
}
public GameView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
gameView = this;
initGameView();
}
/**
* 初始化界面
*/
private void initGameView(){
Log.d("233","0");
setColumnCount(4); //指名是4列的
setBackgroundColor(0xffbbada0);
addCards(getCardWitch(),getCardWitch());
startGame();
setOnTouchListener(new OnTouchListener() {
private float startX,startY;//初始的位置
private float offsetX,offsetY; //偏移的值
@Override
public boolean onTouch(View view, MotionEvent motionEvent) {
switch (motionEvent.getAction()){
case MotionEvent.ACTION_DOWN:
startX = motionEvent.getX();
startY = motionEvent.getY();
break;
case MotionEvent.ACTION_UP:
offsetX = motionEvent.getX()-startX;
offsetY = motionEvent.getY()-startY;
if(Math.abs(offsetX)>Math.abs(offsetY)) { //这个是防止斜着化
if (offsetX < -5) {
Log.d("233","left");
swipeLeft();
} else if (offsetX > 5) {
Log.d("233","right");
swipeRight();
}
}else {
if (offsetY < -5){
Log.d("233","up");
swipeUp();
}else if (offsetY>5){
Log.d("233", "down ");
swipeDown();
}
}
break;
}
return true;
}
});
}
/**
* 布局里面加入卡片
* @param cardWidth
* @param cardHeight
*/
private void addCards(int cardWidth,int cardHeight){
Card c;
for(int y = 0;y< 4;y++){
for(int x = 0;x < 4;x++){
c = new Card(getContext());
c.setNum(0);
Log.d("233","3");
addView(c,cardWidth,cardHeight);
Log.d("233","4");
cardsMap[x][y] = c;
}
}
}
/**
* 获取屏幕的宽度
* @return
*/
private int getCardWitch(){
Log.d("233","5");
DisplayMetrics displayMetrics;
displayMetrics = getResources().getDisplayMetrics();
int carWitch;
carWitch = displayMetrics.widthPixels;
return (carWitch-10)/4;
}
public void startGame(){
for (int y = 0;y<4;y++){
for (int x = 0;x < 4;x++) {
cardsMap[x][y].setNum(0);
}
}
MainActivity.getMainActivity().score = 0;
addRondomNum();
addRondomNum();
}
private void addRondomNum(){
points.clear();
for (int y = 0;y < 4;y++){
for (int x = 0;x <4;x++){
if (cardsMap[x][y].getNum()<=0){
points.add(new Point(x,y));
}
}
}
Point p = points.remove((int)(Math.random()*points.size()));
cardsMap[p.x][p.y].setNum(Math.random() > 0.1?2:4);
}
//设置随机数的方法
private void addRandomNum(){
//把这个point清空,每次调用添加随机数时就清空之前所控制的指针
points.clear();
//对所有的位置进行遍历:即为每个卡片加上了可以控制的指针
for(int y = 0;y<4;y++){
for (int x = 0; x < 4;x++) {
if(cardsMap[x][y].getNum()<=0){
points.add(new Point(x,y));//给List存放控制卡片用的指针(通过坐标轴来控制)
}
}
}
//一个for循环走完我们就从List里取出一个控制指针的point对象
Point p = points.remove((int)(Math.random()*points.size()));
//
cardsMap[p.x][p.y].setNum(Math.random()>0.1?2:4);//通过point对象来充当下标的角色来控制存放card的二维数组cardsMap,然后随机给定位到的card对象赋值
}
private void swipeLeft(){
boolean merge = false;//控制每滑动一次画面就加一个随机数到画面,也就是在下面所有for循环之后
// Toast.makeText(getContext(), "向左滑动了", 0).show();
//以下两行for循环实现了一行一行的遍历,在向左滑动的时候
for (int y = 0; y < 4; y++) {
for (int x = 0; x < 4; x++) {
for (int x1 = x+1; x1 < 4; x1++) {
//这是在水平上固定了一个格子之后再继续在水平上遍历别的格子,且当格子有数的时候进行以下的操作
if (cardsMap[x1][y].getNum()>0) {
//现在判断被固定的格子有没有数字,如果没有数字就进行以下的操作
if (cardsMap[x][y].getNum()<=0) {
//把与被固定的格子的同一水平上的格子的数字赋给被固定的格子
cardsMap[x][y].setNum(cardsMap[x1][y].getNum());
//把值赋给被固定的格子后自己归零
cardsMap[x1][y].setNum(0);
//第二层循环,即同一层的不同列退一格继续循环,这样做的原因是为了继续固定这个格子而去检查与它同一水平上的别的格子的内容,因为其他格子是什么个情况还需要继续在第二层进行判断
x--;
//只要有移动就要加随机数
merge = true;
}else if (cardsMap[x][y].equals(cardsMap[x1][y])) {//这层判断是判断相邻两个数相同的情况
cardsMap[x][y].setNum(cardsMap[x][y].getNum()*2);
cardsMap[x1][y].setNum(0);
MainActivity.getMainActivity().addScore(cardsMap[x][y].getNum());
//只要有移动就要加随机数
merge = true;
}
break;
}
}
}
}
if (merge) {
addRandomNum();
check();
}
}
private void swipeRight(){
boolean merge = false;//控制每滑动一次画面就加一个随机数到画面,也就是在下面所有for循环之后
// Toast.makeText(getContext(), "向右滑动了", 0).show();
for (int y = 0; y < 4; y++) {
for (int x = 4-1; x >=0; x--) {
for (int x1 = x-1; x1 >=0; x1--) {
if (cardsMap[x1][y].getNum()>0) {
if (cardsMap[x][y].getNum()<=0) {
cardsMap[x][y].setNum(cardsMap[x1][y].getNum());
cardsMap[x1][y].setNum(0);
x++;
//只要有移动就要加随机数
merge = true;
}else if (cardsMap[x][y].equals(cardsMap[x1][y])) {
cardsMap[x][y].setNum(cardsMap[x][y].getNum()*2);
cardsMap[x1][y].setNum(0);
MainActivity.getMainActivity().addScore(cardsMap[x][y].getNum());
//只要有移动就要加随机数
merge = true;
}
break;
}
}
}
}
if (merge) {
addRandomNum();
check();
}
}
private void swipeUp(){
boolean merge = false;//控制每滑动一次画面就加一个随机数到画面,也就是在下面所有for循环之后
// Toast.makeText(getContext(), "向上滑动了", 0).show();
for (int x = 0; x < 4; x++) {
for (int y = 0; y < 4; y++) {
for (int y1 = y+1; y1 < 4; y1++) {
if (cardsMap[x][y1].getNum()>0) {
if (cardsMap[x][y].getNum()<=0) {
cardsMap[x][y].setNum(cardsMap[x][y1].getNum());
cardsMap[x][y1].setNum(0);
y--;
//只要有移动就要加随机数
merge = true;
}else if (cardsMap[x][y].equals(cardsMap[x][y1])) {
cardsMap[x][y].setNum(cardsMap[x][y].getNum()*2);
cardsMap[x][y1].setNum(0);
MainActivity.getMainActivity().addScore(cardsMap[x][y].getNum());
//只要有移动就要加随机数
merge = true;
}
break;
}
}
}
}
if (merge) {
addRandomNum();
check();
}
}
private void swipeDown(){
boolean merge = false;//控制每滑动一次画面就加一个随机数到画面,也就是在下面所有for循环之后
// Toast.makeText(getContext(), "向下滑动了", 0).show();
for (int x = 0; x < 4; x++) {
for (int y = 4-1; y >=0; y--) {
for (int y1 = y-1; y1 >=0; y1--) {
if (cardsMap[x][y1].getNum()>0) {
if (cardsMap[x][y].getNum()<=0) {
cardsMap[x][y].setNum(cardsMap[x][y1].getNum());
cardsMap[x][y1].setNum(0);
y++;
//只要有移动就要加随机数
merge = true;
}else if (cardsMap[x][y].equals(cardsMap[x][y1])) {
cardsMap[x][y].setNum(cardsMap[x][y].getNum()*2);
cardsMap[x][y1].setNum(0);
MainActivity.getMainActivity().addScore(cardsMap[x][y].getNum());
//只要有移动就要加随机数
merge = true;
}
break;
}
}
}
}
if (merge) {
addRandomNum();
check();
}
}
/**
* 判断游戏结束的
* 界面格子全满了且相邻的格子没有相同的数字
*/
private void check(){
boolean complete = true;
ALL: for(int y = 0;y <4;y++){
for(int x = 0;x<4;x++){
if (cardsMap[x][y].getNum()==0
||(x>0&&cardsMap[x][y].equals(cardsMap[x-1][y]))||
(x<3&&cardsMap[x][y].equals(cardsMap[x+1][y]))||
(y>0&&cardsMap[x][y].equals(cardsMap[x][y-1]))||
(y<3&&cardsMap[x][y].equals(cardsMap[x][y+1]))) {
complete = false;
break ALL;
}
}
}
if (complete){
new AlertDialog.Builder(getContext()).setTitle("啦啦啦").setMessage("游戏结束了哈").setPositiveButton("重来哈", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
startGame();
}
}).show();
}
/**
* 这个是判定赢了的逻辑
* 只要有一个格子的数字是2048就赢了
*/
for (int x = 0;x < 4;x++){
for(int y = 0;y < 4;y++){
if (cardsMap[x][y].getNum()==2048){
new AlertDialog.Builder(getContext()).setTitle("耶耶耶").setMessage("你赢了").setPositiveButton("重来了吗", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
startGame();
}
}).show();
}
}
}
}
}
import android.content.Context;
import android.view.Gravity;
import android.widget.FrameLayout;
import android.widget.TextView;
/**
* Created by hasee on 2018/3/13.
*/
public class Card extends FrameLayout{
private TextView label; //呈现的文字
private int num = 0; //绑定的编号
// 设置背景色
private int defaultBackColor = 0x338B8B00;
public Card(Context context) {
super(context);
label = new TextView(getContext()); //显示下
label.setTextSize(32);
label.setGravity(Gravity.CENTER);
label.setBackgroundColor(0x33ffffff);
LayoutParams lp = new LayoutParams(-1,-1); //创建个布局,填充满整个父局容器
lp.setMargins(15,15,0,0);
addView(label,lp); //然后扔进去
setNum(0);
}
public int getNum() {
return num;
}
public void setNum(int num) {
this.num = num;
label.setBackgroundColor(getBackground(num));
if (this.num<= 0)
{
label.setText("");
}else {
label.setText(num + "");
}
}
private int getBackground(int num) {
int bgcolor = defaultBackColor;
switch (num) {
case 0:
bgcolor = 0xffCCC0B3;
break;
case 2:
bgcolor = 0xffEEE4DA;
break;
case 4:
bgcolor = 0xffEDE0C8;
break;
case 8:
bgcolor = 0xffF2B179;// #F2B179
break;
case 16:
bgcolor = 0xffF49563;
break;
case 32:
bgcolor = 0xffF5794D;
break;
case 64:
bgcolor = 0xffF55D37;
break;
case 128:
bgcolor = 0xffEEE863;
break;
case 256:
bgcolor = 0xffEDB04D;
break;
case 512:
bgcolor = 0xffECB04D;
break;
case 1024:
bgcolor = 0xffEB9437;
break;
case 2048:
bgcolor = 0xffEA7821;
break;
default:
bgcolor = 0xffEA7821;
break;
}
return bgcolor;
}
/**
* 判断卡片的数字是否相同
* @param
* @return
*/
public boolean equals(Card o) {
return getNum()==o.getNum();
}
}
import android.annotation.SuppressLint;
import android.app.Activity;
import android.os.Bundle;
import android.view.KeyEvent;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
public class MainActivity extends Activity implements View.OnClickListener{
public TextView tvScrore;//计分的
public TextView tvBestScore;//最高分
public int score = 0;
private int bestScores;//历史最高成绩
private Button bt;
private static MainActivity mainActivity = null;
public MainActivity(){
mainActivity = this;
}
public static MainActivity getMainActivity() {
return mainActivity;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
inital();
}
@SuppressLint("SetTextI18n")
public void inital() {
tvBestScore = (TextView) findViewById(R.id.maxSorce);
tvScrore = (TextView) findViewById(R.id.tvSorce);
bt = (Button)findViewById(R.id.bt_cx);
bt.setOnClickListener(this);
BastScode bastScode = new BastScode(this);
bestScores = bastScode.getBestScode();
tvBestScore.setText(bestScores+"");
}
@Override
public void onClick(View v) {
GameView.getGameView().startGame();
}
public void clearScore(){
score = 0;
showScore();
}
public void showScore(){
tvScrore.setText(score+"");
}
public void addScore(int s){
score+=s;
showScore();
if (score > bestScores){
bestScores = score;
BastScode bs = new BastScode(this);
bs.setBestScode(bestScores);
tvBestScore.setText(bestScores+"");
}
}
/**
* 菜单、返回键响应
*/
private long exitTime = 0;
@SuppressLint("WrongConstant")
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK
&& event.getAction() == KeyEvent.ACTION_DOWN) {
if ((System.currentTimeMillis() - exitTime) > 2000) {
Toast.makeText(this, "再按一次退出哈",1000).show();
exitTime = System.currentTimeMillis();
} else {
finish();
System.exit(0);
}
return true;
}
return super.onKeyDown(keyCode, event);
}
}
import android.content.Context;
import android.content.SharedPreferences;
/**
* Created by hasee on 2018/3/18.
*/
public class BastScode {
private SharedPreferences s;
BastScode(Context context){
s = context.getSharedPreferences("bestscode",context.MODE_PRIVATE);
}
public int getBestScode(){
int bestscode = s.getInt("bestscode",0);
return bestscode;
}
public void setBestScode(int bestScode){
SharedPreferences.Editor editor = s.edit();
editor.putInt("bestscode",bestScode);
editor.commit();
}
}
这个是要设置的,,,不然人家手机横着放了就尴尬了
在AndroidManifest.xml里面修改
这个就2048的全部代码,,,有兴趣的看下就行,,,然后发现什么事情要相信自己,,网上其他人的博客真的我是重来没正确运行过,,,可能我是天生bug体质吧emmmmm
//2048真好玩啦啦啦,现在还没打到2048赢尴尬,最近互联网+比赛开始写项目了,,加油我是最胖胖的,耶耶耶