Android开发自定义View实现数字与图片无缝切换的2048
来源:互联网 发布:太上老君化胡为佛 知乎 编辑:程序博客网 时间:2024/05/15 01:37
本博客地址:http://blog.csdn.net/talentclass_ctt/article/details/51952378
最近在学自定义View,无意中看到鸿洋大神以前写过的2048(附上他的博客地址http://blog.csdn.net/lmj623565791/article/details/40020137),看起来很不错,所以自己在他的基础上做一个加强版的2048。先看图:
功能除了正常的2048外,还支持数字与图片无缝切换而没有任何影响,此外,图片不是嵌在自定义View里面的,而是开发者自己在调用时再自己添加的,如:在MainActivity里面添加图片,缺点是Activity被销毁后再进入是重新开始的,不过这只是做一个demo而已,就不讲究这么多了。其实想要开发者改变更多的样式而不用改自定义View内部的关键在于对外暴露的方法的多少,如你可以在自定义View里面写4行4列,也可以暴露一个改变行列数的方法,结果其实没差,只是说这样会减少对自定义View内部的直接操作。
下面这两张图是对应的,切换只需按一下按钮。
下面开始挑战2048:
写自定义View的第一步:分析有什么属性。
一、容器GameLayout,很明显,必须要知道有多少行多少列,小方格的间距,这是靠上下左右滑动的当然就有检测用户滑动的手势,玩的过程肯定要计分啦...
接着开始实现
1、可以用一个数组来存放小方格,数组的大小由行数决定,之后数字变化了都会对这个数组进行操作,保证每时每刻位置和数字都是对的;
/** * 测量Layout的宽和高,以及设置Item的宽和高,这里忽略wrap_content 以宽、高之中的最小值绘制正方形 */ @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); // 获得正方形的边长 int length = Math.min(getMeasuredHeight(), getMeasuredWidth()); // 获得Item的宽度 int childWidth = (length - mPadding * 2 - mMargin * (mColumn - 1)) / mColumn; if (!once) { if (mItems == null) { mItems = new GameItem[mColumn * mColumn]; } // 放置Item for (int i = 0; i < mItems.length; i++) { GameItem item = new GameItem(getContext()); mItems[i] = item; item.setId(i + 1); RelativeLayout.LayoutParams lp = new LayoutParams(childWidth, childWidth); // 设置横向边距,不是最后一列 if ((i + 1) % mColumn != 0) { lp.rightMargin = mMargin; } // 如果不是第一列 if (i % mColumn != 0) { lp.addRule(RelativeLayout.RIGHT_OF, mItems[i - 1].getId()); } // 如果不是第一行,设置纵向边距,非最后一行 if ((i + 1) > mColumn) { lp.topMargin = mMargin; lp.addRule(RelativeLayout.BELOW, mItems[i - mColumn].getId()); } addView(item, lp); } //生成数字 generateNum(); } once = true; setMeasuredDimension(length, length); }2、对于手势,为了简单方便,我们枚举四个方向,自己写一个类继承GestureDetector.SimpleOnGestureListener,在里面判断向那边滑动,注释写的很清楚就不多说了,对于里面的action方法,它会根据你向哪边滑动做出响应的处理,如对小方格移动、数字的合并等等;
/** * 运动方向的枚举 */ private enum ACTION { LEFT, RIGHT, UP, DOWM } /** * 根据坐标变化判断手势 */ class MyGestureDetector extends GestureDetector.SimpleOnGestureListener { // 设置最小滑动距离 final int FLING_MIN_DISTANCE = 50; @Override public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { // 得到在X轴移动的距离 float x = e2.getX() - e1.getX(); // 得到在Y轴移动的距离 float y = e2.getY() - e1.getY(); if (x > FLING_MIN_DISTANCE && Math.abs(velocityX) > Math.abs(velocityY)) { // 向右滑 action(ACTION.RIGHT); } else if (x < -FLING_MIN_DISTANCE && Math.abs(velocityX) > Math.abs(velocityY)) { // 向左滑 action(ACTION.LEFT); } else if (y > FLING_MIN_DISTANCE && Math.abs(velocityX) < Math.abs(velocityY)) { // 向下滑 action(ACTION.DOWM); } else if (y < -FLING_MIN_DISTANCE && Math.abs(velocityX) < Math.abs(velocityY)) { // 向上滑 action(ACTION.UP); } return true; } }3、不从界面,单纯从逻辑考虑,当用户向某一方向移动时,其实就是不断遍历再判断,表的遍历需要两重for循环,根据方向从方向的最前面开始,一个一个判断是不是0(0表示空白),从而判断能不能移动,然后判断是否能合并以及设置合并后的值,之后在值为0的空白小方格中随机选一块产生2或4,当然,到最后无法产生随机数就说明游戏结束了,逻辑差不多就这样吧。
/** * 根据用户运动,整体进行移动合并值等 */ private void action(ACTION action) { // 行|列 for (int i = 0; i < mColumn; i++) { List<GameItem> row = new ArrayList<>(); // 行|列 //记录不为0的数字 for (int j = 0; j < mColumn; j++) { // 得到下标 int index = getIndexByAction(action, i, j); GameItem item = mItems[index]; // 记录不为0的数字 if (item.getNumber() != 0) { row.add(item); } } //判断是否发生移动 for (int j = 0; j < mColumn && j < row.size(); j++) { int index = getIndexByAction(action, i, j); GameItem item = mItems[index]; if (item.getNumber() != row.get(j).getNumber()) { isMoveHappen = true; } } // 合并相同的 mergeItem(row); // 设置合并后的值 for (int j = 0; j < mColumn; j++) { int index = getIndexByAction(action, i, j); if (row.size() > j) { mItems[index].setNumber(row.get(j).getNumber()); } else { mItems[index].setNumber(0); } } } //生成数字 generateNum(); }二、接下来轮到小方格了,他应该设什么属性呢?你可能会想到边长吧,其实边长是可以不用考虑的,因为容器的边长确定了,行数确定了,内边距也确定了,小方格的边长也就确定了,这也符合自定义View的原则之一,能又其他属性算出来的就直接算出来而不重复设。它的属性应该有类型(是图片还是数字)、数字、图片、背景色。
1、默认类型是数字,可以用setType方法改变模式;
/** * 设置类型 * @param type 0为数字, 1为图片 */public void setType(int type) { this.type = type; invalidate(); }2、通过setNumber方法改变内容,改变时又会根据不同的数字选取不同的颜色(这些颜色是我自己一个一个试的,感觉还可以,还有就是我比较喜欢蓝色的,所以你会看到demo运行后基本上界面都是蓝色的),同理,图片也是根据这个来变化的。
/** * 得到图片id数组,并转换成Bitmap类型 * * @param iamges */ public void setImages(int[] Images) { this.mImages = Images; if (mBitmaps == null) { mBitmaps = new Bitmap[mImages.length]; for (int i = 0; i < mImages.length; i++) { // 将图片id转化成Bitmap mBitmaps[i] = BitmapFactory.decodeResource(getResources(), mImages[i]); } } invalidate(); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); if (type == TYPE_NUMBER) { String bgColor = null; switch (mNumber) { case 0: bgColor = "#616ba1"; break; case 2: bgColor = "#bfc8f7"; break; case 4: bgColor = "#b0bbf7"; break; case 8: bgColor = "#9facf5"; break; case 16: bgColor = "#909ff4"; break; case 32: bgColor = "#8394f2"; break; case 64: bgColor = "#788bf4"; break; case 128: bgColor = "#6f83f2"; break; case 256: bgColor = "#6379f2"; break; case 512: bgColor = "#5971f4"; break; case 1024: bgColor = "#4f69f2"; break; case 2048: bgColor = "#3F51B5"; break; default: bgColor = "#8899f5"; break; } // 用对应的颜色充满整个小方格 mPaint.setColor(Color.parseColor(bgColor)); mPaint.setStyle(Paint.Style.FILL); canvas.drawRect(0, 0, getWidth(), getHeight(), mPaint); // 如果有数字就画出来 if (mNumber != 0) { mPaint.setColor(Color.BLACK); float x = (getWidth() - mBound.width()) / 2; float y = getHeight() / 2 + mBound.height() / 2; canvas.drawText(mNumber + "", x, y, mPaint); } } else { int index = -1; // 将数字转换成图片下标 switch (mNumber) { case 2: index = 0; break; case 4: index = 1; break; case 8: index = 2; break; case 16: index = 3; break; case 32: index = 4; break; case 64: index = 5; break; case 128: index = 6; break; case 256: index = 7; break; case 512: index = 8; break; case 1024: index = 9; break; case 2048: index = 10; break; } // 如果没有图片,则直接用颜色充满整个小方格 if (mNumber == 0) { mPaint.setColor(Color.parseColor("#616ba1")); mPaint.setStyle(Paint.Style.FILL); canvas.drawRect(0, 0, getWidth(), getHeight(), mPaint); } // 如果有图片就画出来 if (mNumber != 0) canvas.drawBitmap(mBitmaps[index], null, new Rect(0, 0, getWidth(), getHeight()), null); } }
三、接下来就是使用了,其实很简单,加入xml后,在Activity 中找到控件,设置各种监听和处理
<?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" android:paddingBottom="8dp" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="8dp" tools:context=".MainActivity"> <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:gravity="center_vertical"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:textColor="@color/colorPrimary" android:textSize="18sp" android:text="当前得分:" /> <TextView android:id="@+id/id_score" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="32sp" android:text="0" android:textColor="@color/colorAccent" android:textStyle="bold"/> </LinearLayout> <View android:layout_width="match_parent" android:layout_height="2dp" android:background="@color/colorPrimary" /> <com.talentclass.numberimage2048.GameLayout android:id="@+id/id_game2048" android:layout_width="match_parent" android:layout_height="match_parent" android:padding="10dp" /> <LinearLayout android:layout_width="wrap_content" android:layout_height="42dp" android:layout_marginBottom="12dp"> <Button android:id="@+id/id_type" android:layout_width="wrap_content" android:layout_height="42dp" android:background="@drawable/shape" android:textColor="@color/white" android:text="图片模式"/> <View android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="1"/> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="18sp" android:textColor="@color/colorPrimary" android:text="最高分:"/> <TextView android:id="@+id/id_max_score" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="24sp" android:textColor="@color/red" android:textStyle="bold" android:text="0" /> </LinearLayout> <Button android:id="@+id/id_restart" android:layout_width="match_parent" android:layout_height="42dp" android:background="@drawable/shape" android:textColor="@color/white" android:text="不服重来" android:layout_gravity="bottom" /></LinearLayout>Activity也只是简答的判断逻辑
package com.talentclass.numberimage2048;import android.app.AlertDialog;import android.content.DialogInterface;import android.content.SharedPreferences;import android.preference.Preference;import android.support.v7.app.AppCompatActivity;import android.os.Bundle;import android.view.View;import android.widget.Button;import android.widget.TextView;/** * 程序入口 * * @author talentClass */public class MainActivity extends AppCompatActivity implements GameLayout.Game2048Listener { public static final String SCORE = "score"; /** * 模式:false为数字,true为图片 */ private boolean bType; private TextView tvScore, tvMaxScore; // 当前分数、最高分 private Button btnType, btnRestart; // 设置类型、重新开始 private GameLayout mGameLayout; // 自定义View容器// 放置图片的数组 private int[] mImages = {R.mipmap.image1, R.mipmap.image2, R.mipmap.image3, R.mipmap.image4, R.mipmap.image5, R.mipmap.image6, R.mipmap.image7, R.mipmap.image8, R.mipmap.image9, R.mipmap.image10, R.mipmap.image11}; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main);// 初始化界面 init(); }/** * 初始化界面 */ private void init() { tvScore = (TextView) findViewById(R.id.id_score); tvMaxScore = (TextView) findViewById(R.id.id_max_score); btnType = (Button) findViewById(R.id.id_type); btnRestart = (Button) findViewById(R.id.id_restart); mGameLayout = (GameLayout) findViewById(R.id.id_game2048); mGameLayout.setOnGame2048Listener(this); btnType.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if(bType){// 如果当前是图片模式,则此时按钮显示数字模式,所以点下去后,按钮显示图片模式 bType = false; btnType.setText("图片模式");// 设置类型为数字模式 mGameLayout.setType(GameItem.TYPE_NUMBER); }else {// 如果当前是数字模式,则按钮显示图片模式,所以点下去后,按钮显示数字模式 bType = true; btnType.setText("数字模式");// 先把图片放进去,然后再设置类型为图片模式 mGameLayout.setImage(mImages); mGameLayout.setType(GameItem.TYPE_IMAGE); } } }); btnRestart.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { saveScore(tvScore.getText().toString());// 重新开始 mGameLayout.restart(); } }); tvMaxScore.setText(getScore()); } /** * 获取最高分 * * @return */ private String getScore() { return getSharedPreferences(SCORE, MODE_PRIVATE).getString(SCORE, "0"); } /** * 根据得分判断是否保存到最高分 * * @param score */ private void saveScore(String score) { // 先转换成int类型比较大小 int now = Integer.parseInt(tvScore.getText().toString()); int max = Integer.parseInt(tvMaxScore.getText().toString()); // 如果超过最高分 if (now > max) { tvMaxScore.setText(score); // 保存起来,下次启动再拿出来 SharedPreferences.Editor editor = getSharedPreferences(SCORE, MODE_PRIVATE).edit(); editor.putString(SCORE, score); editor.commit(); } } @Override public void onBackPressed() {// 推出前先保存分数 saveScore(tvMaxScore.getText().toString()); super.onBackPressed(); } @Override public void onScoreChange(int score) { tvScore.setText(score + ""); } @Override public void onGameOver() { new AlertDialog.Builder(this).setTitle("游戏结束") .setMessage("你的得分是:" + tvScore.getText()) .setPositiveButton("再来一次", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { saveScore(tvScore.getText().toString()); mGameLayout.restart(); } }) .setNegativeButton("不玩了", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) {// 保存分数后直接退出应用 saveScore(tvScore.getText().toString()); finish(); } }).show(); }}
其实源代码我注释也写的很详细,大家可以下载,相信一看就懂的。
最后附上完整源代码:源代码
0 0
- Android开发自定义View实现数字与图片无缝切换的2048
- Android 实现双Launcher的无缝切换
- 【android开发】自定义数字软键盘的设计与实现
- 【android开发】自定义数字软键盘的设计与实现
- Android自定义View实现图片显示,并实现缩放、拖拽、切换功能
- android 自定义view实现数字进度条
- android自定义View实现图片的绘制、旋转、缩放
- Android自定义View实现不断旋转的圆形图片
- Android开发旋转圆形图片自定义View
- Android实现自定义view---绘制图片
- android自定义view实现"偷窥"图片
- 自定义View实现Android圆形图片
- 【android开发】自定义数字软键盘的设计与实现(1)
- 【android开发】自定义数字软键盘的设计与实现(2)
- 【android开发】自定义数字软键盘的设计与实现(2)
- 【android开发】自定义数字软键盘的设计与实现(1)
- 【Android】Android开发之自定义View的功能实现详解。教你一步一步学会自定义View
- 实现图片的无缝滚动
- 大菲波数
- python学习——数据库
- toj 3515堆的应用
- pat L1-025. 正整数A+B
- [JQ权威指南]$.ajaxSetup()方法全局设置Ajax
- Android开发自定义View实现数字与图片无缝切换的2048
- [Cloud Computing]Mechanisms: Load Balancer
- Acm 素数距离问题
- JAVA中HashMap和Hashtable区别(转)
- python学习——使用SQLite
- 1sting
- BZOJ 1026 [SCOI2009]windy数【数位DP】
- MaterialProgressDrawable
- STL容器学习总结