最近无聊,一时心血来潮,倒腾倒腾Android编程。自己略有写java基础,就不想拿起教程从头学起,直接以应用为导向,在实际应用中巩固和拓展java编程。搞个什么应用了,Hello World!没意思,突发奇想,倒腾一个翻牌的小应用。涉及的知识点比较多,不能自己闭门造车,从零开始,一句代码一句代码敲,要博览群码。各种搜索,找了好些源码,但只钟意一款(文后附上链接),拜读学习领会,下面开始我的再创作。
一、程序逻辑
总共9张不同内容的卡片,扩展成18张卡片(两两相同),排列成6行3列队形。用户点击卡片,实现翻牌效果,由卡片背面翻转至内容面(可爱小动物),翻转两张卡片之后,进行卡牌内容匹配,如果卡片内容一致,匹配成功,卡片消失;如果卡片内容不一致,匹配失败,卡片翻转至背面。
二、界面布局
这边没什么难点,无非是编写布局xml文件,MainActivity加载文件。我想说下布局的选择和GridView全屏自适应的设置。
1、布局的选择
我选择是FrameLayout,因为这种布局上的view都是一层一层叠加,这样方便在最上层加一个开始按钮。
2、GridView全屏自适应
在xml布局文件中设置GridViewd的android:layout_height为match_parent,无法达到效果。百度了一番,基本都是采用在适配器的getView方法中设置GridView中每个View的高度来实现GridView的屏幕自适应。代码如下:
先计算出每个View的高度
Resources res = getResources();
int statusBarHeight = 0;
int resourceId = res.getIdentifier("status_bar_height", "dimen", "android");
if (resourceId > 0) {
statusBarHeight = res.getDimensionPixelSize(resourceId);
}
int actionBarHeight = 0;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
TypedValue tv = new TypedValue();
getTheme().resolveAttribute(android.R.attr.actionBarSize, tv, true);
actionBarHeight = TypedValue.complexToDimensionPixelSize(tv.data, getResources().getDisplayMetrics());
}
int contentWidth=0,contentHeight=0;
//此方法获取的屏幕高度包括了状态栏和标题栏的高度,但不包括底部导航栏
DisplayMetrics metrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(metrics);
contentWidth = metrics.widthPixels;
contentHeight = metrics.heightPixels -actionBarHeight-statusBarHeight;
// 计算view的宽高
mCardWidth = (contentWidth - dp2px(16) * 2 - dp2px(12) * 2) / 3;
mCardHeight = (contentHeight - dp2px(16) * 2 - dp2px(12) * 5) / 6;
在Adapter的getView方法中设置View大小
public View getView(int i, View view, ViewGroup viewGroup) {
ViewHolder holder = null;
if(view == null){
//加载布局
view =layoutInflater.inflate(R.layout.card, viewGroup, false);
ViewGroup.LayoutParams layoutParams = view.getLayoutParams();
// 设置View大小
layoutParams.width = cardWidth;
layoutParams.height = cardHeight;
view.setLayoutParams(layoutParams);
holder = new ViewHolder();
holder.viewBack = (ImageView)view.findViewById(R.id.card_img_font);
holder.viewContent = (ImageView)view.findViewById(R.id.card_img_back);
view.setTag(holder);
}else{
holder = (ViewHolder)view.getTag();
}
viewHolderList[i] = holder;
//设置图标和文字
Card card = cardList.get(i);
holder.viewContent.setImageResource(card.getImage());
return view;
}
三、动画设置
程序只用到三种动画,一是点击背面卡片的翻转动作,二是两张卡片内容匹配正确时的缩放动作,三是匹配失败时的抖动动作。没什么特别的,百度一大堆,这里需要注意的就是动画监听过程中的开始和结束两个事件。在卡片翻转的结束事件中检查两张卡片内容是否匹配,在匹配正确的缩放的开始事件中播放匹配正确音效和在结束事件中检查游戏是否结束(所有卡片是否都匹配完成),在匹配错误的抖动动画事件中播放匹配错误音效,并在结束事件中将选择的两张卡片翻转至背面。具体源码附后。
四、CardGame类介绍
游戏初始化
public void InitGame(){
cardList = new ArrayList<>();
int [] imgs = new int[]{R.drawable.bear,R.drawable.bird,R.drawable.bull,R.drawable.chicken, R.drawable.dog,R.drawable.elephant,R.drawable.monkey,R.drawable.mouse,R.drawable.pig};
for(int i=0; i<CARD_NUM; i++)
{
int index = i%imgs.length;
cardList.add(new Card(imgs[index],"img_"+imgs[index]));
}
// 打乱卡片顺序
Collections.shuffle(cardList);
mainActivity.mGridView.setAdapter(new CardAdapter(cardList,mainActivity));
}
CardList装载了18张卡片,除了用作初始化CardAdapter,在后期还用作两张卡片的匹配,Card的name的值是唯一的。
检查两次选择的卡片的内容是否一致
public boolean isMatch(int lastIndex,int currentIndex){
if (-1 == lastIndex || -1 == currentIndex) return false;
return cardList.get(lastIndex).getName().equals(cardList.get(currentIndex).getName());
}
检查游戏是否结束
public boolean isEnd(){
for(int i=0; i<CARD_NUM; i++){
if(mainActivity.mGridView.getChildAt(i).getVisibility() == View.VISIBLE) return false;
}
return true;
}
这个方法比较偷懒,因为在两张卡片匹配成功时,程序会隐藏卡片所在的View,所以检查到所有View都隐藏了,游戏就结束。其实可以定义一个匹配成功次数计数器,如果成功次数=卡片总数的一半,那么游戏结束。
游戏再次开始前的复位
public void restart(){
for(int i=0; i<CARD_NUM; i++){
View item = mainActivity.mGridView.getChildAt(i);
item.setVisibility(View.VISIBLE);
ViewHolder card = (ViewHolder)item.getTag();
card.viewBack.clearAnimation();
card.viewContent.clearAnimation();
card.isShowing = false;
}
isStart = false;
}
顺带说一句Card类中的isShowing成员,它指示着卡片是否翻转至内容面,如果已翻转,那么用户再点击这张卡片就无效。
就说这么多吧,第一次接触Android程序,无论是类库使用,还是代码质量,多多少少都还存在这一定问题。如果你是先行人,请多提宝贵意见,如果你是后来者,希望能带给你些许帮助。