Android项目实战--【谁是歌手-逻辑实现篇】

本文详细介绍了基于Android的‘谁是歌手’项目实战,包括歌曲数据模型、初始化数据、UI初始化、时间进度控制、音乐音效控制、提示对话框设计及功能实现流程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


   上篇Android项目实战--【谁是歌手-布局篇】,对项目做了整体介绍,并实现了项目的界面布局,本篇开始实现所有的功能逻辑。

歌曲数据模型对象

       根据项目需求,我们要用到歌曲来播放,要用到歌手图片,以方便用户选择,后期如果我们要扩展功能,可能还会用户到歌曲名,歌手名等等。总之这些数据都是与歌曲相关的数据,按照面向对象思想和MVC框架思想,我们可以构造一个存放歌曲相关信息的数据模型对象(M),我给它起名为Song。这个类很简单,就是一些歌曲相关信息和它们的getter和setter方法。具体代码如下:

com.kedi.songs.model

|--Song.java

 

packagecom.kedi.songs.model;
/**
 * 歌曲实体类
 *
 * @author 张科勇
 */
publicclass Song {
        //歌手名(这个非必须)
        private String singerName;
        // 歌手图片名
        private String singerPicName;
        // 歌曲文件名
        private String musicFileName;
        // 歌曲名
        private String musicName;
 
        public String getSingerName() {
               return singerName;
        }
 
        public void setSingerName(StringsingerName) {
               this.singerName = singerName;
        }
 
        public String getSingerPicName() {
               return singerPicName;
        }
 
        public void setSingerPicName(StringsingerPicName) {
               this.singerPicName =singerPicName;
        }
 
        public String getMusicFileName() {
               return musicFileName;
        }
 
        public void setMusicFileName(StringmusicFileName) {
               this.musicFileName =musicFileName;
        }
 
        public String getMusicName() {
               return musicName;
        }
 
        public void setMusicName(StringmusicName) {
               this.musicName = musicName;
        }
 
        public Song() {
        }
 
        public Song(String singerName, StringsingerPicName, String musicFileName, String musicName) {
               this.singerName = singerName;
               this.singerPicName =singerPicName;
               this.musicFileName =musicFileName;
               this.musicName = musicName;
        }
 
}

初始化数据

       在上一篇中我们把音乐文件,歌手图片,以及与它们对应的字符串数组都准备好了,而上一步我们也创建了歌曲数据模型,接下来需要我们在MainActivity中把这些数据封装到Song数据模型对象中,因为有多首歌曲,所以我们把这些歌曲对象统一再放到一个List集合中,以方便之后的逻辑使用。

 

(1)定义数据相关的数组和集合变量

 

/**
 * 主界面Activity类
 *
 * @author 张科勇
 *
 */
publicclass MainActivity extends Activity {
      // 歌手名数组
        private String[] singerNames;
        // 歌曲名数组
        private String[] musicNames;
        // 歌曲文件名数组
        private String[] musicFileNames;
        // 歌手图片名数组
        private String[] singerPicNames;
        // 保存歌曲对象的集合
        private List<Song> mSongLists =new ArrayList<Song>();
        @Override
         protected void onCreate(Bundle savedInstanceState){
        super.onCreate(savedInstanceState);
         setContentView(R.layout.activity_main);
  }
}

  (2)初始化数据

         这块其实分为两部分,第一部分是把数据对应的字符串数组获取到,第二部分就是遍历数组,把相关数据封装到Song对象中,同时把Song再缓存到List集合中。

为了使程序有结构有层次,我写了一个初始化的方法名叫init(),这个方法的作用是用来初始化与MainActivity类中逻辑相关的所有的事情,而初始化数据就是其中之一,我把初始化数据的逻辑又放到一个专门的方法initData()中,这样做的目的是不至于所有的初始化代码都放在init()方法中,从而提高代码的阅读性。好了,接下来看看代码:

 

/**
 * 主界面Activity类
 *
 * @author 张科勇
 *
 */
publicclass MainActivity extends Activity {
       
        // 歌手名数组
        private String[] singerNames;
        // 歌曲名数组
        private String[] musicNames;
        // 歌曲文件名数组
        private String[] musicFileNames;
        // 歌手图片名数组
        private String[] singerPicNames;
        // 保存歌曲对象的集合
        private List<Song> mSongLists =new ArrayList<Song>();
        @Override
        protected void onCreate(BundlesavedInstanceState) {
               super.onCreate(savedInstanceState);
               setContentView(R.layout.activity_main);
               init();
        }
        /** * 初始化方法 */
        private void init() {
                initData();
        }
        /** * 初始化数据的方法 */
        private void initData() {
        // 获取歌曲相关数据,封装成Song对象,并保存到List集合中
        //获取歌手名字符串数组
        singerNames =getResources().getStringArray(R.array.singer_names);
        //获取歌手图片名字符串数组
        singerPicNames =getResources().getStringArray(R.array.singer_pic_names);
        //获取歌曲字符串数组
        musicNames =getResources().getStringArray(R.array.music_names);
        //获取歌曲文件名字符串数组
        musicFileNames =getResources().getStringArray(R.array.music_file_names);
        for (int i = 0; i <musicNames.length; i++) {
         //创建Song对象,并将对应的数组以构造方法的方式封装到Song对象中
         Song song = new Song(singerNames[i], singerPicNames[i],musicFileNames[i], musicNames[i]);
         //将Song对象缓冲到mSongLists集合中
         mSongLists.add(song);
        }
    }
}


初始化UI

   初始化数据完成后,我们可以初始化UI,设计一个initView()方法,专门来存放初始化UI相关的代码。

/**
 * 初始化View的方法
 */
privatevoid initView() {
        //标题文本控件,mTitleTv是成员变量,定义在属性区域,这里就不给出它们的定义了,下面情况一样。
        mTitleTv = (TextView) findViewById(R.id.tv_title);
        //初始化标题文本,显示总歌曲数,这里为了方便没有把汉字写到string.xml资源文件中。
        mTitleTv.setText("谁是歌手(" + mSongLists.size() + ")");
        //存放那6张歌手图片的控件--GridView
        mSingerPicGv = (GridView)findViewById(R.id.gv_singers);
        // 时间进度条相关
        mTimePb = (ProgressBar)findViewById(R.id.pb_time);
        //播放按钮
        mPlayIb = (ImageButton)findViewById(R.id.ib_start);
        //显示金币数量的文本控件
        mCoinNumTv = (TextView)findViewById(R.id.tv_coin_num);
};

 然后在之前的总初始化方法init()方法调用它。

/**
 * 初始化方法
*/
privatevoid init() {
     initData();
     initView();
}

初始化时间进度条

        这块的逻辑是一旦进入MainActivity界面(项目主界面),时间进度条开始倒计时,倒计时逻辑使用了Handler,通过Handler的postDelayed()方法,让其在1000钟后执行一个Runnable任务,如果时间不小于0,则时间减1,并且修改进行条的进度值,然后延迟1秒钟,执行当前的Runnable任务,直到时间为0,弹出时间到的对话框。

 

/**
* 控制时间进度
 */
 
privatevoid controlTimeBar() {
        //判断游戏是否结束的boolean变量,因为有可能60秒倒计时还没有结束,用户已经通关了,需要控制这种
        //情况,所以设计一个变量记录游戏结束的状态
        isGameOver = false;
        //初始化时间数为60秒,为int类型变量,在成员属性区有定义,
        time = 60;
        //初始化当前进度条的进度值
        mTimePb.setProgress(time);
        mHandler.postDelayed(new Runnable() {
 
               @Override
               public void run() {
                       if (!isGameOver) {//判断游戏如果没有结束
                               if (--time >0) {
                                      mTimePb.setProgress(time);
                                       //延时1秒继续执行run方法
                                      mHandler.postDelayed(this,1000);
                               } else {
                                      // 时间耗尽,设置时间进度为0
                                      mTimePb.setProgress(0);
                                       //如果此时音乐还在播放,则停止播放
                                      // 弹出对话框,提示用户重玩或退出
                                             
                               }
                       }
 
               }
               }, 1000);
        }

我们希望一进入MainActivity界面就开计时,所以controlTimeBar()方法可以在init()方法中调用。

 

/**
 * 初始化方法
 */
privatevoid init() {
        initData();
        initView();
        controlTimeBar();
}

        调用上面的方法之后,时间进度控制就没有问题了,但和整体的逻辑控制还没有关联上,比如时间耗尽需要停止音乐并弹出提示对话框的控制,而这部分需要我们先去设计好控制音乐的类和控制对话框的类,为了方便使用我把这些功能都封装工具类。音乐音效控制工具类设计音乐音效控制主要用是的MediaPlayer这个类,要了解更多的话可以了解一下Android多媒体框架,这里直接设计播放,暂停,停止的方法,来控制音乐和音效。需要注意的是因为音乐音效文件存放在assets目录中,所以在为MediaPlayer设计数据源的时候与普通作法不一样。还有就是因为音乐在播放的时候可能音效同时也播放,比如金币加减时有音效,同时可以音乐正在播放。为了能达到同时播放音乐音效,所以需要两个MediaPlayer,这样它们在播放的时候就不会干扰,而是各播各的,否则我们会发现当音乐在播放的时候由于MediaPlayer此时被音乐占用,此时所有的音效都不会播放。具体设计代码如下:

  
 package com.kedi.songs.utils;
 
importjava.io.IOException;
 
importandroid.content.Context;
importandroid.content.res.AssetFileDescriptor;
importandroid.content.res.AssetManager;
/**
 * 音乐,音效播放工具类
 * @author 张科勇
 *
 */
importandroid.media.MediaPlayer;
/**
 * 音乐与音效控制工具类
 * @author 张科勇
 *
 */
publicclass MediaPlayerUtil {
        // 控制音乐类
        public static MediaPlayer mMediaPlayer;
        //控制音效类
        public static MediaPlayer mTonePlayer;
        // 确定按钮音效标志
        public final static int ENTER_TONE = 0;
        // 取消按钮音效标志
        public final static int CANCEL_TONE = 1;
        // 金币增加或减少音效标志
        public final static int COIN_TONE = 2;
 
        /**
         *播放音乐的方法
         *
         *@param context
         *@param fileName
         */
        public static void playSong(Contextcontext, String fileName) {
               if (mMediaPlayer == null) {
                       mMediaPlayer = newMediaPlayer();
               }
               // 强制重置
               mMediaPlayer.reset();
               //获得资产管理类
               AssetManager am =context.getAssets();
               try {
               //根据音乐文件名,打开某音乐文件,得到的是一个资产文件操作符类,可以获得文件相关信息
                       AssetFileDescriptor fd =am.openFd(fileName);
                     //为MediaPlayer设置数据源,播放资产音频文件的固定写法,(资产就是放在assets目录下的所有文件)
                       mMediaPlayer.setDataSource(fd.getFileDescriptor(),fd.getStartOffset(), fd.getLength());
                       mMediaPlayer.prepare();
                       //开始播放
                       mMediaPlayer.start();
               } catch (IOException e) {
                       e.printStackTrace();
               }
        }
/**
 *播放音效的方法
 * @param context
 * @param fileName
 */
        public static void playTone(Contextcontext, String fileName){
               if (mTonePlayer == null) {
                       mTonePlayer = newMediaPlayer();
                }
               // 强制重置
               mTonePlayer.reset();
               AssetManager am =context.getAssets();
               try {
                       AssetFileDescriptor fd =am.openFd(fileName);
                       mTonePlayer.setDataSource(fd.getFileDescriptor(),fd.getStartOffset(), fd.getLength());
                       mTonePlayer.prepare();
                       mTonePlayer.start();
               } catch (IOException e) {
                       e.printStackTrace();
               }
        }
        /**
         *暂停播放
         *
         *@param context
         */
        public static void pauseSong(Contextcontext) {
               if (mMediaPlayer != null) {
                       if(mMediaPlayer.isPlaying()) {
                               mMediaPlayer.pause();
                       }
               }
        }
 
        /**
         *停止播放
         *
         */
        public static void stopSong(Contextcontext) {
               if (mMediaPlayer != null) {
                       mMediaPlayer.stop();
               }
        }
}

提示对话框设计

        对话框的创建一般用AlertDialog.Buidler,如果需要自定义界面则需要提供一个布局文件,然后通过Builder的setView()方法将自定义界面转化成View类型,以实参形式传给setView()方法即可,但是需要注意的是如果这个布局上有需要交互的按钮,比如确定按钮,取消按钮或重玩按钮,退出按钮,那么这些按钮的点击事件处理需要我们自己处理,本项目要用到两个对话框,一个是倒计时进度为0时,弹出提示对话框,另一个是游戏结束时弹出提示对话框。且界面是自定义的,所以需要布局,并且需要自行处理交互事件。并且为了使对话框相对通用,我设计了一个工具类DialogUtil,专门显示和项目用到的那两个对话框类似对话框。

(1)自定义布局

 

 

<?xmlversion="1.0" encoding="utf-8"?>
<RelativeLayoutxmlns:android="http://schemas.android.com/apk/res/android"
   android:layout_width="match_parent"
   android:layout_height="match_parent" >
 
    <RelativeLayout
       android:layout_width="match_parent"
        android:layout_height="200dp"
       android:layout_centerInParent="true"
       android:background="@drawable/dialog_bg" >
 
        <TextView
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
            android:layout_marginLeft="10dp"
           android:layout_marginTop="10dp"
            android:text="提示"
           android:textColor="#FF8080"
            android:textSize="18sp"/>
 
        <TextView
           android:id="@+id/tv_message"
            android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:layout_centerInParent="true"
            android:text="时间已到!"
            android:gravity="center"
           android:textColor="#FF8080"
            android:textSize="16sp"/>
 
        <LinearLayout
           android:layout_width="match_parent"
           android:layout_height="wrap_content"
           android:layout_alignParentBottom="true"
           android:layout_margin="10dp"
            android:orientation="horizontal">
 
            <Button
               android:id="@+id/btn_ok"
               android:layout_width="0dp"
               android:layout_height="wrap_content"
               android:layout_weight="1"
                android:background="@drawable/btn_bg"
                android:text="重玩游戏"
               android:textColor="#FF8080" />
 
            <Button
               android:id="@+id/btn_cancel"
               android:layout_width="0dp"
               android:layout_height="wrap_content"
               android:layout_marginLeft="10dp"
               android:layout_weight="1"
               android:background="@drawable/btn_bg"
                android:text="退出游戏"
               android:textColor="#FF8080" />
        </LinearLayout>
    </RelativeLayout>
 
</RelativeLayout>


(2)确定按钮、取消按钮事件处理回调接口设计

        在DialogUtils工具类当中有自定义的布局,其中的按钮的点击事件的回调有时候我们不能写死了,最好是通过回调接口将事件回调给调用DialogUtils工具类显示对话框的地方。因此我们需要设计这么一个回调接口。

 com.kedi.songs.callback

 |--DialogCallBack.java

packagecom.kedi.songs.callback;
/**
 * 对话框确定、取消确定的事件回调接口
 * @author 张科勇
 *
 */
publicinterface DialogCallBack {
        void ok();
        void cancel();
 
}
 

  (3)对话框工具类设计

  com.kedi.songs.utils

 |--DialogUtil.java

 

/**
 * 对话框工具类
 *
 * @author 张科勇
 *
 */
publicclass DialogUtil {
        //对话框类
        private static AlertDialog mDialog;
 
       
        public static void showDialog(finalContext context, String message, final String[] toneFileNames,
                       final DialogCallBackcallBack) {
               //创建对话框构造器Builder
               AlertDialog.Builder builder = newAlertDialog.Builder(context, R.style.Dialog_Theme);
               //获取对话框自定义布局View
               View view = View.inflate(context,R.layout.dialog, null);
               //提示信息控件
               TextView mMessageTv = (TextView)view.findViewById(R.id.tv_message);
               mMessageTv.setText(message);
               //确定按钮()
               Button okBtn = (Button)view.findViewById(R.id.btn_ok);
               //取消按钮
               Button cancelBtn = (Button)view.findViewById(R.id.btn_cancel);
               okBtn.setOnClickListener(newOnClickListener() {
 
                       @Override
                       public void onClick(Viewv) {
                               // 取消对话框
                               if (mDialog !=null) {
                                      mDialog.cancel();
                               }
                               // 回调点击确定按钮或重玩游戏按钮的事件
                               if (callBack !=null) {
                                      callBack.ok();
                               }
                               // 播放点击确定按钮或重玩游戏按钮的音效
                               if (toneFileNames!= null && toneFileNames[MediaPlayerUtil.ENTER_TONE] != null) {
                               MediaPlayerUtil.playTone(context,toneFileNames[MediaPlayerUtil.ENTER_TONE]);
                               }
 
                       }
               });
               cancelBtn.setOnClickListener(newOnClickListener() {
 
                       @Override
                       public void onClick(Viewv) {
                               // 取消对话框
                               if (mDialog !=null) {
                                      mDialog.cancel();
                               }
                               // 回调点击退出按钮的事件
                               if (callBack !=null) {
                                      callBack.cancel();
                               }
                               // 播放点击退出按钮的音效
                               if (toneFileNames!= null && toneFileNames[MediaPlayerUtil.CANCEL_TONE] != null) {
                               MediaPlayerUtil.playTone(context,toneFileNames[MediaPlayerUtil.CANCEL_TONE]);
                               }
 
                       }
               });
               //为对话框添加自定义的布局View
               builder.setView(view);
               // 创建对话框
               mDialog = builder.create();
               // 显示对话框
               mDialog.show();
 
        }
 
}
 


 (4)设计显示对话框的方法

         在MainActivity中会使用到对话框,为此设计了一个方法showAlertDialog(int flag), 在MainActivity中定义了两个常量:

// 60秒时间耗完,显示对话框标志
private final static intTIME_FINISH_DIALOG = 0;
// 游戏结束对话框标志
private final static intGAME_OVER_DIALOG = 1;

 

这样在调用showAlertDialog()方法的时候传递对应的常量值,就会弹出对应的对话框。

     

/**
 * 显示对话框的方法
 *
 * @param flag
 */
privatevoid showAlertDialog(int flag) {
               switch (flag) {
               case TIME_FINISH_DIALOG:
               // 时间耗完,显示对话框
               DialogUtil.showDialog(MainActivity.this,"60秒时间到!", toneFileNames, new DialogCallBack() {
 
                       @Override
                       public void ok() {
                               // 重新开始游戏
                               reStartGame();
                       }
 
                       @Override
                       public void cancel() {
                               // 退出界面
                               finish();
 
                       }
                       });
                       break;
 
               case GAME_OVER_DIALOG:
               // 游戏结束,显示对话框
               String tipMessage = null;
               if (okNum == mSongLists.size()&& failNum == 0) {
                       tipMessage = "恭喜你通关了!";
               } else {
               tipMessage = "一共" + mSongLists.size() + "首歌\n"+ "答对:" + okNum + "首,答错:" + failNum + "首";
               }
               DialogUtil.showDialog(MainActivity.this,tipMessage, toneFileNames, new DialogCallBack() {
 
                       @Override
                       public void ok() {
                               // 重新开始游戏
                               reStartGame();
 
                       }
 
                       @Override
                       public void cancel() {
                               // 退出界面
                               finish();
                       }
                       });
                       break;
               default:
                       break;
               }
        }


重新开始游戏:先设计一个方法,等回过头来再完善这个方法 ,因为现在条件还不成熟。

 

/**
 * 重玩游戏的方法 ,主要是清零
*/
privatevoid reStartGame() {
 
 
}


退出游戏:由于现在只有一个界面,所以直接调用Activity的finish()方法,就可以退出MainActivity主界面了。

到此需要用到的工具类基本设计完成,回过头来完善时间进度控制方法的逻辑了。

/**
 * 控制时间进度
 */
 
privatevoid controlTimeBar() {
        isGameOver = false;
        time = 60;
        mTimePb.setProgress(time);
        mHandler.postDelayed(new Runnable() {
 
               @Override
               public void run() {
                       if (!isGameOver) {
                               if (--time >0) {
                                      mTimePb.setProgress(time);
                                      mHandler.postDelayed(this,1000);
                               } else {
                                      // 时间耗尽,设置时间进度为0
                                      mTimePb.setProgress(0);
                                      // 弹出对话框,提示用户重玩或退出
                                      MediaPlayerUtil.stopSong(MainActivity.this);
                                      showAlertDialog(TIME_FINISH_DIALOG);
                               }
                       }
 
                       }
               }, 1000);
        }

初始化每首歌对话的歌手图片和播放的歌曲

  (1)在MainActivity中定义一个整型成员变量:

       // 当前播放歌曲在集合中的索引值,初始为0,代表第一首要播放的歌的索引值
private int mCurrentSongIndex;

  (2)在MainActivity中定义一个Song类型的成员变量:

        // 当前播放的歌的对象,是根据mCurrentSongIndex索引值,从集合中获取到的歌曲对象
private Song mCurrentSong;

  (3)设计一个方法专门初始化每首歌对话的歌手图片和播放的歌曲

首先判断当前歌曲索引值是否小于所以歌集合的大小,用来作为游戏结束的条件。并且在游戏结束时

   设置isGameOver = true,与此同时停止音乐的播放,并弹出游戏结束的对话框。

 

if(mCurrentSongIndex < mSongLists.size()) {
      }else{
   // 停止音乐
 MediaPlayerUtil.stopSong(this);
// 代表游戏结束
 isGameOver = true;
//弹出游戏结束对话框
showAlertDialog(GAME_OVER_DIALOG);
 }


如果游戏没有结束,则获取当前要播放的对话,然后让mCurrentSongIndex++,以方便下次调用方法时能自动获取下一首歌对象。

 

if(mCurrentSongIndex < mSongLists.size()) {
 mCurrentSong =mSongLists.get(mCurrentSongIndex++);
}else{
// 停止音乐
  MediaPlayerUtil.stopSong(this);
// 代表游戏结束
isGameOver= true;
  //弹出游戏结束对话框
showAlertDialog(GAME_OVER_DIALOG);
}
 
接下来是创建一个List集合,存放6个歌手对应的Song对话,其中有一个是当前播放的歌曲对象,其余五个是随机的从所有歌曲中获得五个,然后加入到新创建的List集合中,然后将List集合中的对象顺序打乱。
// 获取并适配当前6个歌手图片(其中有一张是正确的,其它五个是随机的)
        tempList = new ArrayList<Song>();
        tempList.add(mCurrentSong);
        Random random = new Random();
        for (;;) {
               int index =random.nextInt(mSongLists.size());
               Song tempSong =mSongLists.get(index);
        //判断随机获取的Song如果不是当前播放的歌曲,则放入tempList集合中,以控制随机获取的五个Song不能和
        //当前播放的歌重复。
               if(!tempSong.getMusicName().equals(mCurrentSong.getMusicName())) {
        //判断随机获取的Song是否已经被添加到tempList集合中了,如果没有才添加,有就不添加
        //这么做是为了防止重复添加歌曲对象到tempList集合中
                       if(!tempList.contains(tempSong)) {
                               tempList.add(tempSong);
                       } else {
                               continue;
                       }
 
               } else {
                               continue;
               }
        //我们一共只需要6个歌手信息,所以需要判断tempList集合大小如果已经等于6了,则达到我们的目的
               if (tempList.size() == 6) {
 
               // 打乱顺序,但包含正确答案
                       Collections.shuffle(tempList);
                       break;
               }
 
        }


有了6个乱了序的歌手信息(其实得到的是歌曲对象集合,而歌曲对象中包含歌手图片信息),接下来就是创建GridView的适配器,为GridView适配歌手图片。为此创建了一个SingerPicAdapter适配器

 

条目布局: layout/singer_pic.xml

<?xmlversion="1.0" encoding="utf-8"?>
<RelativeLayoutxmlns:android="http://schemas.android.com/apk/res/android"
   android:layout_width="wrap_content"
    android:layout_height="wrap_content">
 
    <ImageView
       android:id="@+id/iv_singer_pic"
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:scaleType="fitXY"
       android:layout_centerInParent="true"
        android:src="@drawable/ic_launcher"/>
 
</RelativeLayout>

 GridView歌手图片适配器:


 com.kedi.songs.adapter

 |--SingerPicAdapter

packagecom.kedi.songs.adapter;
 
importjava.util.List;
 
importcom.kedi.songs.model.Song;
importcom.kedi.songs.R;
 
importandroid.content.Context;
importandroid.view.View;
importandroid.view.ViewGroup;
importandroid.widget.BaseAdapter;
importandroid.widget.ImageView;
/**
 * 歌手图片GridView的适配器
 * @author 张科勇
 *
 */
publicclass SingerPicAdapter extends BaseAdapter {
 
        private List<Song> mDatas;
        private Context mContext;
 
        public SingerPicAdapter(List<Song>mDatas, Context mContext) {
               super();
               this.mDatas = mDatas;
               this.mContext = mContext;
        }
 
        @Override
        public int getCount() {
               return mDatas.size();
        }
 
        @Override
        public Object getItem(int position) {
               return mDatas.get(position);
        }
 
        @Override
        public long getItemId(int position) {
               return position;
        }
 
        @Override
        public View getView(int position, ViewconvertView, ViewGroup parent) {
               if (convertView == null) {
                       convertView =View.inflate(mContext, R.layout.singer_pic, null);
               }
               ImageView singerPicIv =(ImageView) convertView.findViewById(R.id.iv_singer_pic);
               Song song = mDatas.get(position);
               // 获得对应歌手图片的ID,为ImageView显示图片
               int picId =mContext.getResources().getIdentifier(song.getSingerPicName(),"drawable", "com.kedi.songs");
               singerPicIv.setImageResource(picId);
               return convertView;
        }
 
}


 有了适配器,接下来就可以使用它为GridView适配数据了。

 

 // 创建歌手图片适配器,将图片显示到GridView上
    mAdapter = new SingerPicAdapter(tempList,this);
    mSingerPicGv.setAdapter(mAdapter);


接下来可以播放当前歌曲音乐。

 // 播放当前关音乐
    MediaPlayerUtil.playSong(MainActivity.this,mCurrentSong.getMusicFileName());

完整的代码如下:

 /**
         *初始化当前歌曲的方法
         */
        private void initCurrentSong() {
               // 获取当前歌对象
               if (mCurrentSongIndex <mSongLists.size()) {
                       mCurrentSong =mSongLists.get(mCurrentSongIndex++);
                       // 获取并适配当前6个歌手图片(其中有一张是正确的,其它五个是随机的)
                       tempList = newArrayList<Song>();
                       tempList.add(mCurrentSong);
                       Random random = newRandom();
                       for (;;) {
                               int index =random.nextInt(mSongLists.size());
                               Song tempSong =mSongLists.get(index);
                               if(!tempSong.getMusicName().equals(mCurrentSong.getMusicName())) {
                                      if(!tempList.contains(tempSong)) {
                                              tempList.add(tempSong);
                                      } else {
                                              continue;
                                      }
 
                               } else {
                                      continue;
                               }
                               if(tempList.size() == 6) {
 
                                      // 打乱顺序,但包含正确答案
                                      Collections.shuffle(tempList);
                                      break;
                               }
 
                       }
                       // 创建歌手图片适配器,将图片显示到GridView上
                       mAdapter = newSingerPicAdapter(tempList, this);
                       mSingerPicGv.setAdapter(mAdapter);
                       // 播放当前关音乐
                       MediaPlayerUtil.playSong(MainActivity.this,mCurrentSong.getMusicFileName());
               } else {
                       // 停止音乐
                       MediaPlayerUtil.stopSong(this);
                       // 代表游戏结束
                       isGameOver = true;
                       //弹出游戏结束对话框
                       showAlertDialog(GAME_OVER_DIALOG);
               }
 
        }

        有了上面这个方法,我们可以在进入MainActivity的时候就初始当前歌曲,并开始播放。所以可以在init()方法中调用一次intitCurrentSong()方法。

  /**
         *初始化方法
         */
        private void init() {
               initData();
               initView();
               controlTimeBar();
               initCurrentSong();
        }

        到此,运行程序已经可以播放音乐了,并且可以看到6张歌手的图片,同时也可以看到时间进度在从60秒倒计时。想想我们还需要做什么呢?我想当用户听到音乐播放后可以会点击对应的歌手头像,或者点击播放/暂停按钮,暂停下来想想这首歌的歌手是谁,而在他想的时候一不小心走神了,回过神来发现60时间到,弹出一个对话框,告诉他60秒时间到,可以重玩游戏,可以退出游戏。他选择了重玩游戏,但这次没有走神,一口气连猜了几首,并听到金币的加减声,也看到了金币数里一会加一会减,不一会又弹出一个对话框告诉他游戏关卡已到最后,并记录了他猜对多少首歌,猜错多少首歌。

       为了实现用户需要经历的这一切,我们和程序需要监听与处理歌手图片点击事件,监听与处理播放/暂停按钮的点击事件,金币数量的加减、界面刷新以及音效,记录猜对个数,猜错个数,弹出对话框提供用户,重玩游戏,退出游戏这些功能。

 

处理播放/暂停按钮点击事件

       为了统一点击事件的代码,设计了一个setListener()方法,并在init()方法中调用。

/**
* 初始化方法
*/
privatevoid init() {
        initData();
        initView();
        setListener();
        controlTimeBar();
        initCurrentSong();
}
/**
* 注册与处理点击事件的方法
*/
privatevoid setListener() {
 
}

然后在setListener()方法中为播放/暂停按钮注册点击监听器,并处理音乐播放与暂停逻辑。

/**
* 注册与处理点击事件的方法
*/
 private void setListener() {
// 监听播放按钮点击事件
 mPlayIb.setOnClickListener(newOnClickListener() {
 @Override
 public void onClick(View v) {
 if (MediaPlayerUtil.mMediaPlayer != null&& MediaPlayerUtil.mMediaPlayer.isPlaying()) {
// 暂停播放
MediaPlayerUtil.pauseSong(MainActivity.this);
 } else {
// 开始播放
MediaPlayerUtil.playSong(MainActivity.this,mCurrentSong.getMusicFileName());
   }
   }
});
}

       在setListener()方法中为GridView注册歌手图片条目点击监听器,并处理点击事件。点击歌手图片时,判断点击的条目对应是歌曲是不是当前播放的歌曲,如果是则代表选择正确,金币数量要+10,并Toast提示用户,同时记录正确的变量也应+1,否则代表选择错误,金币数量要-10,并Toast提示用户,

同时记录错误的变量也应-1,而无论金币加还是减都会播放金币音效。

(1)设计金币加减相关的方法。

  /**
         *加减金币,并刷新界面上的金币数的方法,其中num为负代表减,否则为加
         *
         *@param num
         */
        private void addCoin(int num) {
               mCurrentCoinNum = mCurrentCoinNum+ num;
               // 刷新界面上的金币数
               mCoinNumTv.setText(mCurrentCoinNum+ "");
               // 播放金币加减音效
               if (toneFileNames != null&& toneFileNames[MediaPlayerUtil.COIN_TONE] != null) {
                       MediaPlayerUtil.playTone(this,toneFileNames[MediaPlayerUtil.COIN_TONE]);
               }
               if (num > 0) {
                       Toast.makeText(MainActivity.this,"答案正确+" + num, Toast.LENGTH_LONG).show();
               } else {
                       Toast.makeText(MainActivity.this,"答案错误" + num, Toast.LENGTH_LONG).show();
               }
        }

(2)为GridView注册歌手图片条目点击监听器,并处理点击事件。

// 监听用户对歌手图片的选中事件
mSingerPicGv.setOnItemClickListener(newOnItemClickListener() {
 
        @Override
        public void onItemClick(AdapterView<?>parent, View view, int position, long id) {
        // 获取用户选中的图片对应的歌对象
        Song selectedSong =tempList.get(position);
        // 判断用户是否猜对,通过用户点击的歌手图片对应的歌曲名和当前播放的歌曲名比较,
               if(selectedSong.getMusicName().equals(mCurrentSong.getMusicName())) {
                       // 当前金币加10
                       addCoin(10);
                       // 答对数加1
                       okNum++;
               } else {
                       // 当前金币减10
                       addCoin(-10);
                       // 答错数加1
                       failNum++;
               }
 
               // 初始化下一首歌,并播放
                       initCurrentSong();
               }
});

setListener()方法完整代码如下:

/**
 * 注册与处理点击事件的方法
 */
privatevoid setListener() {
        // 监听播放按钮点击事件
        mPlayIb.setOnClickListener(newOnClickListener() {
 
        @Override
        public void onClick(View v) {
        if (MediaPlayerUtil.mMediaPlayer != null&& MediaPlayerUtil.mMediaPlayer.isPlaying()) {
        // 暂停播放
        MediaPlayerUtil.pauseSong(MainActivity.this);
        } else {
        // 开始播放
        MediaPlayerUtil.playSong(MainActivity.this,mCurrentSong.getMusicFileName());
        }
 
        }
        });
   // 监听用户对歌手图片的选中事件
    mSingerPicGv.setOnItemClickListener(newOnItemClickListener() {
 
        @Override
        public voidonItemClick(AdapterView<?> parent, View view, int position, long id) {
          // 获取用户选中的图片对应的歌对象
           Song selectedSong =tempList.get(position);
          // 判断用户是否猜对,通过用户点击的歌手图片对应的歌曲名和当前播放的歌曲名比较,
          if (selectedSong.getMusicName().equals(mCurrentSong.getMusicName())) {
               // 当前金币加10
               addCoin(10);
               // 答对数加1
               okNum++;
          } else {
               // 当前金币减10
               addCoin(-10);
               // 答错数加1
               failNum++;
          }
 
          // 初始化下一首歌,并播放
          initCurrentSong();
         }
        });
}
 


重玩游戏方法完善:基本思路是变量清零和重新控制时间进度条和要播放的歌曲对象。

 

  /**
         *重玩游戏的方法,主要是清零
         */
        private void reStartGame() {
               // 重置游戏金币
               mCurrentCoinNum = 0;
               mCoinNumTv.setText(mCurrentCoinNum+ "");
               // 重置当前歌曲为第一首
               mCurrentSongIndex = 0;
               // 重置记录用户选择正确歌曲数
               okNum = 0;
               // 重置记录用户选择错误歌曲数
               failNum = 0;
               // 重置时间进度
               controlTimeBar();
               // 重置当前歌曲
               initCurrentSong();
        }


 最后贴出MainActivity.java这个类的代码以方便大家参考:

 

packagecom.kedi.songs;
 
importjava.util.ArrayList;
importjava.util.Collections;
importjava.util.List;
importjava.util.Random;
 
importcom.kedi.songs.adapter.SingerPicAdapter;
importcom.kedi.songs.callback.DialogCallBack;
importcom.kedi.songs.model.Song;
importcom.kedi.songs.utils.DialogUtil;
importcom.kedi.songs.utils.MediaPlayerUtil;
importcom.kedi.songs.R;
 
importandroid.app.Activity;
importandroid.os.Bundle;
importandroid.os.Handler;
importandroid.view.View;
importandroid.view.View.OnClickListener;
importandroid.widget.AdapterView;
importandroid.widget.AdapterView.OnItemClickListener;
importandroid.widget.GridView;
importandroid.widget.ImageButton;
importandroid.widget.ProgressBar;
importandroid.widget.TextView;
importandroid.widget.Toast;
 
/**
 * 主界面Activity类
 *
 * @author 张科勇
 *
 */
publicclass MainActivity extends Activity {
        // 显示标题的控件
        private TextView mTitleTv;
        // 保存歌曲对象的集合
        private List<Song> mSongLists =new ArrayList<Song>();
        // 歌手名数组
        private String[] singerNames;
        // 歌曲名数组
        private String[] musicNames;
        // 歌曲文件名数组
        private String[] musicFileNames;
        // 歌手图片名数组
        private String[] singerPicNames;
 
        private GridView mSingerPicGv;// 显示歌手名的视图View
        private SingerPicAdapter mAdapter;// 歌手图片适配器
 
        // 时间进度条控件
        private ProgressBar mTimePb;
        // 默认时间 为60秒
        private int time = 60;
        Handler mHandler = new Handler();
        // 当前播放歌曲相关
        private int mCurrentSongIndex;
        // 当前播放的歌对象
        private Song mCurrentSong;
        private ImageButton mPlayIb;
        // 适配歌手图片用到的集合(只选6张图,且必须包含正确的)
        private List<Song> tempList;
        // 当前金币数
        private static int mCurrentCoinNum = 0;
        // 显示金币的View
        private TextView mCoinNumTv;
        // 答对歌数
        private int okNum;
        // 答错歌数
        private int failNum;
        // 判断游戏是否没歌曲的变量,也就是游戏结束的变量
        private boolean isGameOver = false;
        // 60秒时间耗完,显示对话框标志
        private final static intTIME_FINISH_DIALOG = 0;
        // 游戏结束对话框标志
        private final static intGAME_OVER_DIALOG = 1;
 
        // 音效文件名数组
        private String[] toneFileNames;
 
        @Override
        protected void onCreate(BundlesavedInstanceState) {
               super.onCreate(savedInstanceState);
               setContentView(R.layout.activity_main);
               init();
        }
 
        /**
         *初始化方法
         */
        private void init() {
               initData();
               initView();
               setListener();
               controlTimeBar();
               initCurrentSong();
        }
 
        /**
         *初始化数据的方法
         */
        private void initData() {
               // 获取歌曲相关数据,封装成Song对象,并保存到List集合中
               // 获取歌手名字符串数组
               singerNames =getResources().getStringArray(R.array.singer_names);
               // 获取歌手图片名字符串数组
               singerPicNames =getResources().getStringArray(R.array.singer_pic_names);
               // 获取歌曲字符串数组
               musicNames = getResources().getStringArray(R.array.music_names);
               // 获取歌曲文件名字符串数组
               musicFileNames =getResources().getStringArray(R.array.music_file_names);
               for (int i = 0; i <musicNames.length; i++) {
                       // 创建Song对象,并将对应的数组以构造方法的方式封装到Song对象中
                      Song song = newSong(singerNames[i], singerPicNames[i], musicFileNames[i], musicNames[i]);
                       // 将Song对象缓冲到mSongLists集合中
                       mSongLists.add(song);
               }
               // 获取音效文件名
               toneFileNames =getResources().getStringArray(R.array.tone_file_names);
        }
 
        /**
         *初始化View的方法
         */
        private void initView() {
               mTitleTv = (TextView)findViewById(R.id.tv_title);
               mTitleTv.setText("谁是歌手(" + mSongLists.size() + ")");
               mSingerPicGv = (GridView)findViewById(R.id.gv_singers);
               // 时间进度条相关
               mTimePb = (ProgressBar)findViewById(R.id.pb_time);
               mPlayIb = (ImageButton)findViewById(R.id.ib_start);
               mCoinNumTv = (TextView)findViewById(R.id.tv_coin_num);
        };
 
        /**
         *注册与处理点击事件的方法
         */
        private void setListener() {
               // 监听播放按钮点击事件
               mPlayIb.setOnClickListener(newOnClickListener() {
 
                       @Override
                       public void onClick(Viewv) {
                               if(MediaPlayerUtil.mMediaPlayer != null &&MediaPlayerUtil.mMediaPlayer.isPlaying()) {
                                      // 暂停播放
                                      MediaPlayerUtil.pauseSong(MainActivity.this);
                               } else {
                                      // 开始播放
                                      MediaPlayerUtil.playSong(MainActivity.this,mCurrentSong.getMusicFileName());
                               }
 
                       }
               });
               // 监听用户对歌手图片的选中事件
               mSingerPicGv.setOnItemClickListener(newOnItemClickListener() {
 
                       @Override
                       public voidonItemClick(AdapterView<?> parent, View view, int position, long id) {
                               // 获取用户选中的图片对应的歌对象
                               Song selectedSong= tempList.get(position);
                               // 判断用户是否猜对,通过用户点击的歌手图片对应的歌曲名和当前播放的歌曲名比较,若相等,则表示用户猜对,反之没有猜地
                               if(selectedSong.getMusicName().equals(mCurrentSong.getMusicName())) {
                                      // 当前金币加10
                                      addCoin(10);
                                      // 答对数加1
                                      okNum++;
                               } else {
                                      // 当前金币减10
                                      addCoin(-10);
                                      // 答错数加1
                                      failNum++;
                               }
 
                               // 初始化下一首歌,并播放
                               initCurrentSong();
                       }
               });
        }
 
        /**
         *加减金币,并刷新界面上的金币数的方法,其中num为负代表减,否则为加
         *
         *@param num
         */
        private void addCoin(int num) {
               mCurrentCoinNum = mCurrentCoinNum+ num;
               // 刷新界面上的金币数
               mCoinNumTv.setText(mCurrentCoinNum+ "");
               // 播放金币加减音效
               if (toneFileNames != null&& toneFileNames[MediaPlayerUtil.COIN_TONE] != null) {
                       MediaPlayerUtil.playTone(this,toneFileNames[MediaPlayerUtil.COIN_TONE]);
               }
               if (num > 0) {
                       Toast.makeText(MainActivity.this,"答案正确+" + num, Toast.LENGTH_LONG).show();
               } else {
                       Toast.makeText(MainActivity.this,"答案错误" + num, Toast.LENGTH_LONG).show();
               }
        }
 
        /**
         *控制时间进度
         */
 
        private void controlTimeBar() {
               isGameOver = false;
               time = 60;
               mTimePb.setProgress(time);
               mHandler.postDelayed(newRunnable() {
 
                       @Override
                       public void run() {
                               if (!isGameOver){
                                      if (--time> 0) {
                                              mTimePb.setProgress(time);
                                              mHandler.postDelayed(this,1000);
                                       } else {
                                              //时间耗尽,设置时间进度为0
                                              mTimePb.setProgress(0);
                                              //弹出对话框,提示用户重玩或退出
                                              MediaPlayerUtil.stopSong(MainActivity.this);
                                              showAlertDialog(TIME_FINISH_DIALOG);
                                      }
                               }
 
                       }
               }, 1000);
        }
 
        /**
         *初始化当前歌曲的方法
         */
        private void initCurrentSong() {
               // 获取当前歌对象
               if (mCurrentSongIndex <mSongLists.size()) {
                       mCurrentSong =mSongLists.get(mCurrentSongIndex++);
                       // 获取并适配当前6个歌手图片(其中有一张是正确的,其它五个是随机的)
                       tempList = newArrayList<Song>();
                       tempList.add(mCurrentSong);
                       Random random = new Random();
                       for (;;) {
                               int index =random.nextInt(mSongLists.size());
                               Song tempSong =mSongLists.get(index);
                               if(!tempSong.getMusicName().equals(mCurrentSong.getMusicName())) {
                                      if(!tempList.contains(tempSong)) {
                                              tempList.add(tempSong);
                                      } else {
                                              continue;
                                      }
 
                               } else {
                                      continue;
                               }
                               if(tempList.size() == 6) {
 
                                      // 打乱顺序,但包含正确答案
                                      Collections.shuffle(tempList);
                                      break;
                               }
 
                       }
                       // 创建歌手图片适配器,将图片显示到GridView上
                       mAdapter = newSingerPicAdapter(tempList, this);
                       mSingerPicGv.setAdapter(mAdapter);
                       // 播放当前关音乐
                       MediaPlayerUtil.playSong(MainActivity.this,mCurrentSong.getMusicFileName());
               } else {
                       // 停止音乐
                       MediaPlayerUtil.stopSong(this);
                       // 代表游戏结束
                       isGameOver = true;
                       //弹出游戏结束对话框
                       showAlertDialog(GAME_OVER_DIALOG);
               }
 
        }
 
        /**
         *重玩游戏的方法,主要是清零
         */
        private void reStartGame() {
                //重置游戏金币
               mCurrentCoinNum = 0;
               mCoinNumTv.setText(mCurrentCoinNum+ "");
               // 重置当前歌曲为第一首
               mCurrentSongIndex = 0;
               // 重置记录用户选择正确歌曲数
               okNum = 0;
               // 重置记录用户选择错误歌曲数
               failNum = 0;
               // 重置时间进度
               controlTimeBar();
               // 重置当前歌曲
               initCurrentSong();
        }
 
        @Override
        protected void onDestroy() {
               super.onDestroy();
               MediaPlayerUtil.stopSong(this);
        }
 
        /**
         *显示对话框的方法
         *
         *@param flag
         */
        private void showAlertDialog(int flag) {
               switch (flag) {
               case TIME_FINISH_DIALOG:
                       // 时间耗完,显示对话框
                       DialogUtil.showDialog(MainActivity.this,"60秒时间到!", toneFileNames, new DialogCallBack() {
 
                               @Override
                               public void ok(){
                                      // 重新开始游戏
                                      reStartGame();
                               }
 
                               @Override
                               public voidcancel() {
                                      // 退出界面
                                      finish();
 
                               }
                       });
                       break;
 
               case GAME_OVER_DIALOG:
                       // 游戏结束,显示对话框
                       String tipMessage = null;
                       if (okNum ==mSongLists.size() && failNum == 0) {
                               tipMessage ="恭喜你通关了!";
                       } else {
                               tipMessage ="一共" + mSongLists.size() + "首歌\n" + "答对:" + okNum + "首,答错:" + failNum + "首";
                       }
                       DialogUtil.showDialog(MainActivity.this,tipMessage, toneFileNames, new DialogCallBack() {
 
                               @Override
                               public void ok(){
                                      // 重新开始游戏
                                      reStartGame();
 
                               }
 
                               @Override
                               public voidcancel() {
                                      // 退出界面
                                      finish();
 
                               }
                       });
                       break;
               default:
                       break;
               }
        }
}

 

       到此基本实现了【谁是歌手】项目的所有逻辑,当然我们可以以此为原型扩展很多附加的功能,比如可以添加提示功能,可以提示用户一个正确答案,但需要扣除一定的金币,可以添加删除一个干扰项功能,也需要扣除一定的金币,或添加多个关卡,每个关卡几首歌,用户猜完一个关卡的几首歌,全正确后可以激活下一关,等等。更好玩的功能需要大家自行扩展了。

  源码下载:http://download.youkuaiyun.com/download/kedi_study/9459187


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值