Android Mp3播放器,支持Service后台播放

大家好,作为小白,这是我第一篇文章,也是我的一个学习记录。废话就不多说了,开始进入正题吧。
这篇文章是我做Mp3播放器的一个过程,首先整理一下思路,我们做播放器会需要什么东西呢?
一:获取本地的音频文件。并且保存到一个List里面方便我们来读取。
二:需要一个界面来展示出我们所获取到的音频文件。
三:播放界面,需要有播放,下一首,等一系列操作。
四:Service,我们需要运用Service来达到后台播放目的,当然使用它的前提就是我们能够先了解到Service的基本使用方法,这里我就不多做说明了.等以后我会写一点点关于这个的东西。
好了,大概的流程就是这样,下面我们来用代码实现。


一:获取本地音频文件

public class Audio {
  private String mTitle,
                mTitleKey,//标题
                mArtist,//艺术家名
                mArtistKey,
                mComposer,
                mAlbum,
                mAlbumKey,
                mDisplayName,//歌曲名
                mMimeType,
                mPath;//路径

        private int mId,
                mArtistId,
                mAlbumId,
                mYear,
                mTrack;

        private int mDuration = 0,//时长
                mSize = 0;//大小

        private boolean isRingtone = false,
                isPodcast = false,
                isAlarm = false,
                isMusic = false,
                isNotification = false;

        public Audio(Bundle bundle) {
            mId = bundle.getInt(MediaStore.Audio.Media._ID);
            mTitle = bundle.getString(MediaStore.Audio.Media.TITLE);
            mTitleKey = bundle.getString(MediaStore.Audio.Media.TITLE_KEY);
            mArtist = bundle.getString(MediaStore.Audio.Media.ARTIST);
            mArtistKey = bundle.getString(MediaStore.Audio.Media.ARTIST_KEY);
            mComposer = bundle.getString(MediaStore.Audio.Media.COMPOSER);
            mAlbum = bundle.getString(MediaStore.Audio.Media.ALBUM);
            mAlbumKey = bundle.getString(MediaStore.Audio.Media.ALBUM_KEY);
            mDisplayName = bundle.getString(MediaStore.Audio.Media.DISPLAY_NAME);
            mYear = bundle.getInt(MediaStore.Audio.Media.YEAR);
            mMimeType = bundle.getString(MediaStore.Audio.Media.MIME_TYPE);
            mPath = bundle.getString(MediaStore.Audio.Media.DATA);

            mArtistId = bundle.getInt(MediaStore.Audio.Media.ARTIST_ID);
            mAlbumId = bundle.getInt(MediaStore.Audio.Media.ALBUM_ID);
            mTrack = bundle.getInt(MediaStore.Audio.Media.TRACK);
            mDuration = bundle.getInt(MediaStore.Audio.Media.DURATION);
            mSize = bundle.getInt(MediaStore.Audio.Media.SIZE);
            isRingtone = bundle.getInt(MediaStore.Audio.Media.IS_RINGTONE) == 1;
            isPodcast = bundle.getInt(MediaStore.Audio.Media.IS_PODCAST) == 1;
            isAlarm = bundle.getInt(MediaStore.Audio.Media.IS_ALARM) == 1;
            isMusic = bundle.getInt(MediaStore.Audio.Media.IS_MUSIC) == 1;
            isNotification = bundle.getInt(MediaStore.Audio.Media.IS_NOTIFICATION) == 1;

        }

        public int getId() {
            return mId;
        }

        public String getMimeType () {
            return mMimeType;
        }

        public int getDuration () {
            return mDuration;
        }

        public int getSize () {
            return mSize;
        }

        public boolean isRingtone () {
            return isRingtone;
        }

        public boolean isPodcast () {
            return isPodcast;
        }

        public boolean isAlarm () {
            return isAlarm;
        }

        public boolean isMusic () {
            return isMusic;
        }

        public boolean isNotification () {
            return isNotification;
        }

        public String getTitle () {
            return mTitle;
        }

        public String getTitleKey () {
            return mTitleKey;
        }

        public String getArtist () {
            return mArtist;
        }

        public int getArtistId () {
            return mArtistId;
        }

        public String getArtistKey () {
            return mArtistKey;
        }

        public String getComposer () {
            return mComposer;
        }

        public String getAlbum () {
            return mAlbum;
        }

        public int getAlbumId () {
            return mAlbumId;
        }

        public String getAlbumKey () {
            return mAlbumKey;
        }

        public String getDisplayName () {
            return mDisplayName;
        }

        public int getYear () {
            return mYear;
        }

        public int getTrack () {
            return mTrack;
        }

        public String getPath () {
            return mPath;
        }

    }

这里我们写了一个Audio的音频文件类,它所有的属性有mTitle,
mTitleKey,
mArtist,
mArtistKey,
mComposer,
mAlbum,
mAlbumKey,
mDisplayName,
mMimeType,
mPath;
我们从名字中就可以看到其作用,歌曲标题,艺术家名等.
public Audio(Bundle bundle) 这个构造的函数的作用呢,也就相当于我们取到本地音频的一系列值,然后存储到这个类中来。
然后就是实现这个类的属性的get方法,从类中获取到这些属性。

接下来,我们需要去浏览本地文件,选出音频文件进行保存:

public class MediaUtils {
    public static final String[] AUDIO_KEYS = new String[]{
            MediaStore.Audio.Media._ID,
            MediaStore.Audio.Media.TITLE,
            MediaStore.Audio.Media.TITLE_KEY,
            MediaStore.Audio.Media.ARTIST,
            MediaStore.Audio.Media.ARTIST_ID,
            MediaStore.Audio.Media.ARTIST_KEY,
            MediaStore.Audio.Media.COMPOSER,
            MediaStore.Audio.Media.ALBUM,
            MediaStore.Audio.Media.ALBUM_ID,
            MediaStore.Audio.Media.ALBUM_KEY,
            MediaStore.Audio.Media.DISPLAY_NAME,
            MediaStore.Audio.Media.DURATION,
            MediaStore.Audio.Media.SIZE,
            MediaStore.Audio.Media.YEAR,
            MediaStore.Audio.Media.TRACK,
            MediaStore.Audio.Media.IS_RINGTONE,
            MediaStore.Audio.Media.IS_PODCAST,
            MediaStore.Audio.Media.IS_ALARM,
            MediaStore.Audio.Media.IS_MUSIC,
            MediaStore.Audio.Media.IS_NOTIFICATION,
            MediaStore.Audio.Media.MIME_TYPE,
            MediaStore.Audio.Media.DATA
    };
    //读取本地音频文件,保存到list里并返回
    public static List<Audio> getAudioList(Context context){
        List<Audio> audioList = new ArrayList<Audio>();
        ContentResolver resolver  = context.getContentResolver();
        Cursor cursor = resolver.query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
                AUDIO_KEYS,
                null,
                null,
                null);
        for(cursor.moveToFirst();!cursor.isAfterLast();cursor.moveToNext()){
                Bundle bundle  = new Bundle();
            for(int i=0;i<AUDIO_KEYS.length;i++){
                final String key  = AUDIO_KEYS[i];
                final int columnIndex = cursor.getColumnIndex(key);
                final int type = cursor.getType(columnIndex);
                switch (type) {
                    case Cursor.FIELD_TYPE_BLOB:
                        break;
                    case Cursor.FIELD_TYPE_FLOAT:
                        float floatValue = cursor.getFloat(columnIndex);
                        bundle.putFloat(key, floatValue);
                        break;
                    case Cursor.FIELD_TYPE_INTEGER:
                        int intValue = cursor.getInt(columnIndex);
                        bundle.putInt(key, intValue);
                        break;
                    case Cursor.FIELD_TYPE_NULL:
                        break;
                    case Cursor.FIELD_TYPE_STRING:
                        String strValue = cursor.getString(columnIndex);
                        bundle.putString(key, strValue);
                        break;
                }
            }
            Audio audio = new Audio (bundle);
            audioList.add(audio);
        }
        cursor.close();
        return audioList;
    }
}

这一个类的主要目的就是去通过Cusor索引,然后去获取到本地的音频文件,然后保存到List里面,在返回一个List

这里我们已经拿到了步骤一种所说的,保存有本地所有音频文件的List

然后如何将这个List显示在手机界面上,并且可以点击的呢。
MainActivity

@ContentView(R.layout.activity_main)
public class MainActivity extends Activity {

  @ViewInject(R.id.mp3_ListView)
    ListView mp3ListView;
    private Context mContext;
    static List<Audio> mp3Infos = new ArrayList<Audio>();
    private Mp3Adapter adapter;
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        x.view().inject(this);
        init();
        //给MusicPlayActivity这个类的mp3Infos赋值
        MusicPlayActivity.mp3Infos=mp3Infos;
    }
    public void init(){
        this.mContext=this;
        mp3Infos = (ArrayList<Audio>) MediaUtils.getAudioList(this.mContext);
        adapter = new Mp3Adapter(this.mContext,mp3Infos);
        mp3ListView.setAdapter(adapter);
        //设置ListView Item点击监听器
        mp3ListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {

               Intent intent1   = new Intent();
                //启动音乐播放界面
                intent1 .setClass(MainActivity.this,MusicPlayActivity.class);
                //带值跳转,传入点击的歌曲位置
                intent1.putExtra("position", position);
                startActivity(intent1);
            }
        });
    }


}

Mp3Adapter

public class Mp3Adapter extends BaseAdapter{
    private Context context=null;
    private List<Audio> mp3Infos ;
    public Mp3Adapter(Context context){
        this.context=context;
    }
    public Mp3Adapter(Context context, List<Audio> mp3Infos){
        this.context=context;
        this.mp3Infos=mp3Infos;
    }
    public int getCount() {
        return mp3Infos.size();
    }

    @Override
    public Object getItem(int position) {
        return mp3Infos.get(position);
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        if(mp3Infos==null){
            return null;
        }
        if(convertView==null){
            AppView view  = new AppView(this.context);
            view.update(mp3Infos.get(position));
            convertView=view;
            convertView.setTag(view);
        }else{
            convertView= (View) convertView.getTag();
        }
        return convertView;
    }
}

AppView

public class AppView extends RelativeLayout{

    ImageView mp3ImageView;

    TextView mp3ArtistName;

    TextView mp3Name;

    TextView mp3Duration;
    private Context context;
    public AppView(Context context){
        super(context);
        this.context=context;
        init(context);
    }
    public void init(Context context){
        this.context=context;
        View view = LayoutInflater.from(this.context).inflate(R.layout.listview_item_activity,null);
        mp3ImageView = (ImageView) view.findViewById(R.id.mpe3_imageView);
        mp3ArtistName = (TextView) view.findViewById(R.id.mp3_ArttistName);
        mp3Duration = (TextView) view.findViewById(R.id.mp3_dration);
        mp3Name = (TextView) view.findViewById(R.id.Mp3_name);
        addView(view);
    }
    public void update(Audio info){
        mp3ArtistName.setText(info.getArtist());
        mp3ImageView.setImageResource(R.mipmap.xxx);
        mp3Duration.setText(TimeTransform.secToTime(info.getDuration()/1000));
        String x = info.getDisplayName();
        if(x.length()>=15){
            mp3Name.setText(x.subSequence(0,15)+"...");
        }else
            mp3Name.setText(x);
    }
}

上述代码是三个类,一个是Activity,一个是Adapter,一个是View
这三个类就是比较标准的实现显示一个ListView的操作吧,相信大家不会陌生,就是拿着我们返回的所有音频的List并且给他写好适配器,然后进行显示操作。当然代码中也有建立ListView的Item监听器。

现在我们实现了获取所有音频文件,然后将其显示在界面上,并且可以点击,现在点击后会需要跳转到播放界面。

上面代码中我已经写了跳转部分,现在我们实现以下需要跳转的类:

MusicPlayActivity

public class MusicPlayActivity extends Activity {
    @ViewInject(R.id.play_music)
    ImageView playMusicIv;
    @ViewInject(R.id.next_music)
    ImageView nextMusicIv;
    @ViewInject(R.id.pre_music)
    ImageView preMusicIv;
    @ViewInject(R.id.play_mode)
    ImageView playMusicModeIv;
    @ViewInject(R.id.music_name)
    TextView musicName;
    @ViewInject(R.id.artist_name)
    TextView artistName;
    @ViewInject(R.id.seekBar)
    SeekBar seekBar;
    @ViewInject(R.id.MusicCurrentTime)
    TextView MusicCurrentTime;
    @ViewInject(R.id.MusicTime)
    TextView MusicTime;
    int array[];
    public static List<Audio> mp3Infos;
    boolean mBound=false;//控制绑定和开启Service
    boolean flag = false;//判断播放状态
    Thread myThread;
    int position;
    private int playFlag = 0;//0 顺序,1 单曲, 2,随机
    boolean playStatus=true;//控制线程的开启和关闭
    Mp3Service mService;
    static String TAG="MusicPlayerActivity";
    //Handler用于时时更新进度条
    Handler mHandler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what){
                case 0:
                    double progress = msg.getData().getDouble("progress");
                    int currentTime = msg.getData().getInt("currentTime");
                    int max = seekBar.getMax();
                    int position = (int) (max * progress);
                    //设置seekbar的实际位置
                    seekBar.setProgress(position);
                    MusicCurrentTime.setText(TimeTransform.secToTime(currentTime/1000));//设置总时间显示
                    break;

                default:
                    break;
            }
        }
    };
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        x.view().inject(this);
        //获取到从MainActivity传过来的歌曲的位置
        Intent intent = getIntent();
        position = intent.getIntExtra("position",-1);
        playMusicModeIv.setImageDrawable(getResources().getDrawable(R.drawable.xunhuan_play_selector));
        init();
    }
    public void init(){
    //通过Mp3Service类中的isRunning来判断服务是否处于开启状态,如果是的话,则需要关闭辅助在进行别的操作
        if(Mp3Service.isRunning==true){
           // unbindService(mConnection);
            stopService(new Intent(MusicPlayActivity.this, Mp3Service.class));
            }
            //获取到Mp3的信息
        mp3Infos = MainActivity.mp3Infos;
 //设置界面       musicName.setText(mp3Infos.get(position).getDisplayName());
        MusicTime.setText(TimeTransform.secToTime(mp3Infos.get(position).getDuration()/1000));
        artistName.setText(mp3Infos.get(position).getArtist());
        Intent serviceIntent = new Intent(MusicPlayActivity.this,Mp3Service.class);
        serviceIntent.putExtra("position",position);
        if(mBound==false){
        //开启和绑定服务
            startService(serviceIntent);
            bindService(serviceIntent,mConnection,BIND_AUTO_CREATE);
        }
        myThread = new Thread(new UpdateProgress());
        //拖动seekBar调整播放进度
        seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
            @Override
            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {

            }

            @Override
            public void onStartTrackingTouch(SeekBar seekBar) {

            }

            @Override
            public void onStopTrackingTouch(SeekBar seekBar) {
                int dest = seekBar.getProgress();
                //seekbar的最大值
                int max = seekBar.getMax();
                //调用service调节播放进度
                mService.setProgress(max, dest);
            }
        });


    }
//播放界面按钮的监听器
    @Event(value={R.id.play_music,R.id.pre_music,R.id.next_music,R.id.play_mode})
    private void setOnClickListener(View view){
        switch (view.getId()) {
            case R.id.play_music:
                if (mBound && flag) {
                    playMusicIv.setImageDrawable(getResources().getDrawable(R.drawable.playmusic_selector));
                    mService.pause();
                    flag = false;
                } else {
                    playMusicIv.setImageDrawable(getResources().getDrawable(R.drawable.pause_music_selector));
                    mService.play();
                    flag = true;
                }
                break;
            case R.id.pre_music:
                position=position-1;
                if(position<0)
                    position = mp3Infos.size() - 1;
                    playStatus = false;
                    mBound=false;
                    unbindService(mConnection);
                    init();
                break;
            case R.id.next_music:
                switch (playFlag){
                    case 0:
                        position = position+1;
                        if(position>=mp3Infos.size()){
                            position=0;
                        }
                        break;
                    case 1:
                        position = position;
                        Toast.makeText(MusicPlayActivity.this, "单曲播放中", Toast.LENGTH_SHORT).show();
                        break;

                    case 2:
                       int x=(int)(Math.random()*mp3Infos.size()-1);
                        position=x;
                        break;
                }
                playStatus = false;
                mBound=false;
                unbindService(mConnection);
                init();
                break;

            case R.id.play_mode:
                switch (playFlag){
                    case 0:
                        playFlag++;
                        playMusicModeIv.setImageDrawable(getResources().getDrawable(R.drawable.danqu_play_selector));
                        break;
                    case 1:
                        playFlag++;
                        playMusicModeIv.setImageDrawable(getResources().getDrawable(R.drawable.suiji_play_selector));
                        break;
                    case 2:
                        playFlag=0;
                        playMusicModeIv.setImageDrawable(getResources().getDrawable(R.drawable.xunhuan_play_selector));
                        break;
                }
                    break;
        }
    }
    private ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {

            Mp3Service.MyBinder myBinder = (Mp3Service.MyBinder) service;
            mService = (Mp3Service) myBinder.getService();
            mBound=true;
            playStatus=true;
            playMusicIv.setImageDrawable(getResources().getDrawable(R.drawable.pause_music_selector));
           // mService.play();
            myThread.start();
            flag=true;
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

            mBound=false;
        }
    };

    public void onDestroy() {
        //销毁activity时,要记得销毁线程
        playStatus = false;
        super.onDestroy();
    }
    //更新进度线程,单位时间获取Service返回的
    private class UpdateProgress implements Runnable {
        Bundle data = new Bundle();
        int millisecond=100;
        double progress;
        int currentTime;
        @Override
        public void run() {
            while(playStatus){
                try {
                    if(mBound){
                        Message msg = new Message();
                        data.clear();
                        progress = mService.getProgress();
                        currentTime=mService.getDurationTime();
                        msg.what=0;
                        data.putDouble("progress",progress);
                        data.putInt("currentTime",currentTime);
                        msg.setData(data);
                        mHandler.sendMessage(msg);
                    }

                    Thread.sleep(millisecond);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    public static boolean isServiceRunning(Context mContext, String className) {
        boolean isRunning = false;
        ActivityManager activityManager = (ActivityManager)
                mContext.getSystemService(Context.ACTIVITY_SERVICE);
        List<ActivityManager.RunningServiceInfo> serviceList= activityManager.getRunningServices(50);
        if (!(serviceList.size()>0)) {
            return false;
        }
        for (int i=0; i<serviceList.size(); i++) {
            String a =serviceList.get(i).service.getClassName();
            if (serviceList.get(i).service.getClassName().equals(className) == true) {
                isRunning = true;
                break;
            }
        }
        return isRunning;
    }
}

这个类就是音乐的播放界面,在这里我们首先获取到了由ListView点击后传过来的位置参数,然后在开启服务之前首要去判断这个服务是否处于正在运行状态,如果是的话,就首先关闭服务,然后在进行别的操作,因为我们听歌的时候,切歌,必须先关闭前一首歌,再去播放另一首,然后isRunning就是Service里的属性,从这里判断是否服务正在进行。

当然更新进度也的放在这个类里,具体思想就是我们通过Thread和Handler去实现,线程不断从Service中获取歌曲播放进度,然后send给Handler,在由Handler去进行seekBar的更新。

下面就说一下我们比较核心的Service类,这个类就是后台播放歌曲的实现。

public class Mp3Service extends Service {
    int position;
    MediaPlayer mediaPlayer;//MediaPlayer 对象
    //判断是否服务开启了
    public static  boolean  isRunning=false;
    //构建Binder,通过binder和Activity进行交互
    MyBinder myBinder= new MyBinder();
    public static List<Audio> mp3Infos;
    public IBinder onBind(Intent intent) {
        return myBinder;
    }
    public class MyBinder extends Binder{
    //获取到Service
        public Service getService(){
            return Mp3Service.this;
        }
    }

    @Override
    public void onCreate() {
        super.onCreate();

    }

    @Override
      //开启服务
    public int onStartCommand(Intent intent, int flags, int startId) {

        isRunning=true;
        //取到带值跳转传过来的歌曲位置参数
        position=intent.getIntExtra("position",-1);
        mp3Infos= MusicPlayActivity.mp3Infos;
        init();
        return Service.START_NOT_STICKY;
    }
    //初始化音乐MediaPlayer
    public void init(){
        mediaPlayer = new MediaPlayer();
    //拿到歌曲位置进行初始化
             try {
            mediaPlayer.reset();
             mediaPlayer.setDataSource(mp3Infos.get(position).getPath());
            mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
            mediaPlayer.prepare();
            mediaPlayer.start();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
    //获取进度
    public double getProgress(){
        int position = mediaPlayer.getCurrentPosition();
        int time = mediaPlayer.getDuration();
        double progress = (double)position / (double)time;
        return progress;
    }
    //获取播放时间
    public int getDurationTime(){
        return mediaPlayer.getCurrentPosition();
    }
    //设置进度
    public void setProgress(int max , int dest){
        int time = mediaPlayer.getDuration();
        mediaPlayer.seekTo(time*dest/max);
    }
    public void play(){
            if(mediaPlayer!=null){
                mediaPlayer.start();

            }
    }
    public void pause(){
        if(mediaPlayer!=null&&mediaPlayer.isPlaying()){
            mediaPlayer.pause();

        }
    }

    @Override
    //服务销毁
    public void onDestroy() {
        if(mediaPlayer!=null&&mediaPlayer.isPlaying()){
            mediaPlayer.stop();
            mediaPlayer.release();
            mediaPlayer=null;
            isRunning=false;

        }
        super.onDestroy();
    }
}

这个Mp3Service类就是实现歌曲后台播放的类,在这里我们首先接收到了由MusicPlayActivity传入的歌曲位置,当MusicPlayActivity类开启和绑定服务之后,我服务类进行了初始化,首先实例化了mediaPlayer并且设置好了他的播放设置。然后这个类里写了歌曲的暂停和播放功能。
在这里要知道什么时候服务开启,什么时候服务关闭,然后在合适的地方设置好isRunning的值。

这个类中我们也写了获取进度和设置进度的方法,获取进度就是为了方便MusicPlayActivity去时时获取进度进行界面进度条更新,设置进度就是为了拖动bar的时候能够调节进度。

当然要完善销毁方法,释放Mediaplayer的一系列操作。

好了差不多就是这样,然后还有用到的工具类,我也复制在底下:

public class TimeTransform {
    public static String secToTime(int time) {
        String timeStr = null;
        int hour = 0;
        int minute = 0;
        int second = 0;
        if (time <= 0)
            return "00:00";
        else {
            minute = time / 60;
            if (minute < 60) {
                second = time % 60;
                timeStr = unitFormat(minute) + ":" + unitFormat(second);
            } else {
                hour = minute / 60;
                if (hour > 99)
                    return "99:59:59";
                minute = minute % 60;
                second = time - hour * 3600 - minute * 60;
                timeStr = unitFormat(hour) + ":" + unitFormat(minute) + ":" + unitFormat(second);
            }
        }
        return timeStr;
    }
    public static String unitFormat(int i) {
        String retStr = null;
        if (i >= 0 && i < 10)
            retStr = "0" + Integer.toString(i);
        else
            retStr = "" + i;
        return retStr;
    }
}

这个类的作用就是传入一个秒数,然后他会返回一个标准化时间输出的字符串,例如传入xxxxxx,他会 返回xx:xx。因为音乐播放界面的时间应该是标准化的。

要记住Mediaplayer获取到的时间都是以毫秒为单位的,所以传入的时候必须转化成秒在传入。xxxx/1000

主要进行传值我们都用的是带值跳转和static 声明存放歌曲的List,这样他们就可以共享这些数据了。布局文件我就不贴出来了,这个可以自己实现,大体的思路就是这样,可能有些太长了,因为我把所有的代码基本上都放出来了。

好了,基本就这么多,这篇文章主要为了记录学习过程,还有很多不完善和不规范的地方,请大神勿喷。作为新手仅仅是想做一点学习记录而已。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章