- 话不多说,先上效果图
- 这里由于模拟器的适配不太好,一些显示的细节并不如意,在我的手机上就都能实现功能
- 比如我每次打开软件是一张图片,后台执行筛选音乐文件,模拟器上却不显示图片
- 我每次放歌都有个Notification告诉我在放哪一首,然后消失,模拟器上却也不不显示
- 基本细节就是这些吧,这个软件运行在手机上效果更理想一些。
基本思路
- 音乐播放器其实就是应用了Activity,Service和BroadcaReceiver这三个组件,再配合MediaPlayer的使用,完成各种逻辑的设计,就可以实现一个简单的本地音乐播放器了。
- 我这里使用了一个服务专门用来处理各种播放音乐的逻辑,用服务是因为Activity可能被回收,而且依赖Activity的逻辑会在界面隐藏或者退出后无法继续播放音乐,而是用服务就可以实现后台播放的功能。
- 同时我们的主界面和服务之间的通信,我们用到了广播机制,通过广播我们可以轻松的实现主界面和服务之间的协调、信息交流等事宜。
界面设计
- 主界面的布局如图所示,布局较为简单,用到了一个ListView展示音乐,一个SeekBar展示播放进度,若干个Button进行播放的控制等。
- 布局较为简单,这里就不再过多解释了。
<?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:background="@mipmap/background"
tools:context=".activity.MainActivity">
<ListView
android:id="@+id/listView_music"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"></ListView>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:id="@+id/textView_nowDuration"
android:layout_width="30dp"
android:layout_height="wrap_content"
android:layout_marginLeft="20dp"
android:layout_gravity="center_vertical"
android:gravity="center"
android:text="00:00"/>
<SeekBar
android:id="@+id/seekBar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_weight="1"/>
<TextView
android:id="@+id/textView_duration"
android:layout_width="30dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:gravity="center"
android:layout_marginRight="20dp"
android:text="00:00"/>
</LinearLayout>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
>
<Button
android:id="@+id/button_shunxu"
android:layout_width="@dimen/many_pic"
android:layout_height="@dimen/many_pic"
android:background="@mipmap/shunxu_shunxu"
android:layout_centerVertical="true"
android:layout_toLeftOf= "@+id/button_left"/>
<Button
android:id="@+id/button_left"
android:layout_width="@dimen/many_pic"
android:layout_height="@dimen/many_pic"
android:layout_marginLeft="30dp"
android:layout_centerVertical="true"
android:layout_toLeftOf="@+id/button_play"
android:background="@mipmap/left_pic" />
<Button
android:id="@+id/button_play"
android:layout_width="@dimen/play_pic"
android:layout_height="@dimen/play_pic"
android:layout_marginLeft="40dp"
android:layout_marginRight="40dp"
android:layout_marginTop="20dp"
android:layout_marginBottom="20dp"
android:layout_centerHorizontal="true"
android:background="@mipmap/pause_pic" />
<Button
android:id="@+id/button_right"
android:layout_width="@dimen/many_pic"
android:layout_height="@dimen/many_pic"
android:layout_centerVertical="true"
android:layout_toRightOf="@+id/button_play"
android:layout_marginRight="30dp"
android:background="@mipmap/right_pic" />
<Button
android:layout_width="@dimen/many_pic"
android:layout_height="@dimen/many_pic"
android:background="@mipmap/list_music_pic"
android:layout_centerVertical="true"
android:layout_toRightOf="@+id/button_right"/>
</RelativeLayout>
</LinearLayout>
-ListView子项布局如下
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal" android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_margin="10dp"
android:src="@mipmap/music_ic"/>
<LinearLayout
android:id="@+id/linearlayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/music_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="5dp"
android:textSize="20sp"
android:singleLine="true"
android:text="歌曲名称" />
<TextView
android:id="@+id/music_singer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:textSize="15sp"
android:text="歌手" />
</LinearLayout>
</LinearLayout>
- 状态栏的下拉菜单如下
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal" android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:layout_width="70dp"
android:layout_height="match_parent"
android:padding="10dp"
android:layout_gravity="center"
android:src="@mipmap/stateline_ic"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/musicbar_songname"
android:textSize="20dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:textColor="#000000"
android:singleLine="true"
android:text="歌曲名称"/>
<TextView
android:id="@+id/musicbar_exit"
android:layout_width="20dp"
android:layout_height="wrap_content"
android:textColor="#000000"
android:text="X"/>
</LinearLayout>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/musicbar_left"
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_toLeftOf="@+id/musicbar_play"
android:layout_centerVertical="true"
android:src="@mipmap/left_pic"/>
<ImageView
android:id="@+id/musicbar_play"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_marginLeft="30dp"
android:layout_marginRight="30dp"
android:padding="5dp"
android:layout_centerHorizontal="true"
android:src="@mipmap/play_pic"/>
<ImageView
android:id="@+id/musicbar_right"
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_toRightOf="@+id/musicbar_play"
android:layout_centerVertical="true"
android:src="@mipmap/right_pic"/>
</RelativeLayout>
</LinearLayout>
</LinearLayout>
主要逻辑功能
- 当我们从主界面操作的时候,通过点击事件的监听,判断逻辑,先去更新我们的UI变化,同时开启服务,将所必须的歌曲的位置等信息通过广播传递给服务,而后服务在处理逻辑判断,进行相应的操作。
public class MainActivity extends BaseActivity implements View.OnClickListener, AdapterView.OnItemClickListener, SeekBar.OnSeekBarChangeListener {
private List<File> musicList;
private ListView listView_music;
private TextView textView_duration;
private TextView textView_nowDuration;
private SeekBar seekBar;
private int nowPosition = 0;
private Button button_play, button_left, button_right,button_shunxu;
private UtilsReceiver utilsReceiver;
private IntentFilter utilsFilter;
private boolean isPlaying=false;
private int nowDuration=0;
private boolean flag=true;
private boolean isOrderPlay=true;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
musicList = MusicListFactory.newInstance().getList();
init();
initUtilsReceiver();
MusicAdapter adapter = new MusicAdapter(getApplicationContext(), musicList);
listView_music.setAdapter(adapter);
listView_music.setOnItemClickListener(this);
Intent intent=new Intent("UPDATE_UI");
sendBroadcast(intent);
}
private void initUtilsReceiver() {
utilsReceiver = new UtilsReceiver();
utilsFilter = new IntentFilter();
utilsFilter.addAction("IS_PLAYING");
utilsFilter.addAction("NOT_PLAYING");
utilsFilter.addAction("ALL_SECONDS");
utilsFilter.addAction("UPDATE_DURATION");
utilsFilter.addAction("NEW_SONG");
utilsFilter.addAction("PAUSE_SONG");
utilsFilter.addAction("COUNTINUE_SONG");
utilsFilter.addAction("BACK_UPDATE_UI");
utilsFilter.addAction("UPDATE_POSITION");
registerReceiver(utilsReceiver, utilsFilter);
Thread countThread=new Thread(new Runnable() {
@Override
public void run() {
while (flag){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (isPlaying){
nowDuration++;
}
Intent intent=new Intent("UPDATE_DURATION");
intent.putExtra("nowSeconds",nowDuration);
sendBroadcast(intent);
}
}
});
countThread.start();
}
private void init() {
listView_music = (ListView) findViewById(R.id.listView_music);
textView_duration = (TextView) findViewById(R.id.textView_duration);
textView_nowDuration = (TextView) findViewById(R.id.textView_nowDuration);
seekBar = (SeekBar) findViewById(R.id.seekBar);
seekBar.setOnSeekBarChangeListener(this);
button_play = (Button) findViewById(R.id.button_play);
button_left = (Button) findViewById(R.id.button_left);
button_right = (Button) findViewById(R.id.button_right);
button_right.setOnClickListener(this);
button_left.setOnClickListener(this);
button_play.setOnClickListener(this);
button_shunxu= (Button) findViewById(R.id.button_shunxu);
button_shunxu.setOnClickListener(this);
}
@Override
public void onClick(View v) {
Intent intent;
switch (v.getId()) {
case R.id.button_play:
intent = new Intent(this, MusicService.class);
intent.putExtra("POSITION", nowPosition);
startService(intent);
break;
case R.id.button_left:
clickLeftButton();
break;
case R.id.button_right:
clickRightButton();
break;
case R.id.button_shunxu:
changePlayOrder();
break;
}
}
private void changePlayOrder() {
isOrderPlay=!isOrderPlay;
if (isOrderPlay){
Toast.makeText(MainActivity.this, "顺序播放", Toast.LENGTH_SHORT).show();
button_shunxu.setBackgroundResource(R.mipmap.shunxu_shunxu);
}else {
Toast.makeText(MainActivity.this, "随机播放", Toast.LENGTH_SHORT).show();
button_shunxu.setBackgroundResource(R.mipmap.shunxu_sujip);
}
Intent intent=new Intent("UPDATE_PLAY_ORDER");
intent.putExtra("isOrderPlay",isOrderPlay);
sendBroadcast(intent);
}
private void clickLeftButton() {
Intent intent;
if (isOrderPlay) {
if (nowPosition == 0) {
Toast.makeText(MainActivity.this, "当前已经第一首", Toast.LENGTH_SHORT).show();
}else{
nowPosition--;
}
}else {
nowPosition=(int)(Math.random()*musicList.size());
}
intent = new Intent(this, MusicService.class);
intent.putExtra("POSITION", nowPosition);
startService(intent);
}
private void clickRightButton() {
Intent intent;
if (isOrderPlay){
if (nowPosition == musicList.size()-1) {
isPlaying=false;
Toast.makeText(MainActivity.this, "当前已经最后一首", Toast.LENGTH_SHORT).show();
}else{
nowPosition++;
}
}else {
nowPosition=(int)(Math.random()*musicList.size());
}
intent = new Intent(this, MusicService.class);
intent.putExtra("POSITION", nowPosition);
startService(intent);
}
class UtilsReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
switch (intent.getAction()) {
case "IS_PLAYING":
button_play.setBackgroundResource(R.mipmap.play_pic);
break;
case "NOT_PLAYING":
button_play.setBackgroundResource(R.mipmap.pause_pic);
break;
case "ALL_SECONDS":
int allSeconds=intent.getIntExtra("allSeconds",0);
seekBar.setMax(allSeconds);
textView_duration.setText(allSeconds/60+":"+allSeconds%60);
break;
case "UPDATE_DURATION":
int nowSeconds=intent.getIntExtra("nowSeconds",0);
seekBar.setProgress(nowSeconds);
textView_nowDuration.setText(nowSeconds/60+":"+nowSeconds%60);
break;
case "NEW_SONG":
nowDuration=0;
isPlaying=true;
break;
case "PAUSE_SONG":
isPlaying=false;
break;
case "COUNTINUE_SONG":
isPlaying=true;
break;
case "UPDATE_POSITION":
nowPosition=intent.getIntExtra("POSITION",0);
Log.d("***","888888"+"**"+nowPosition);
break;
case "BACK_UPDATE_UI":
isOrderPlay=intent.getBooleanExtra("isOrderPlay",true);
nowPosition=intent.getIntExtra("nowPosition",0);
isPlaying=intent.getBooleanExtra("MEDIA_IS_PLAYING",false);
nowDuration=intent.getIntExtra("nowDuration",0);
int duration=intent.getIntExtra("duration",0);
textView_duration.setText(duration/60+":"+duration%60);
textView_nowDuration.setText(nowDuration/60+":"+nowDuration%60);
seekBar.setMax(duration);
if (isPlaying){
button_play.setBackgroundResource(R.mipmap.play_pic);
}else {
button_play.setBackgroundResource(R.mipmap.pause_pic);
}
if (isOrderPlay){
button_shunxu.setBackgroundResource(R.mipmap.shunxu_shunxu);
}else {
button_shunxu.setBackgroundResource(R.mipmap.shunxu_sujip);
}
break;
}
}
}
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
nowPosition=position;
Intent intent = new Intent(this, MusicService.class);
intent.putExtra("POSITION", nowPosition);
startService(intent);
}
//seekBar中用不到的两个方法
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {}
public void onStartTrackingTouch(SeekBar seekBar) {}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
Intent intent=new Intent("SEEKBAR_UPDATE");
intent.putExtra("seekBar_pargress",seekBar.getProgress());
sendBroadcast(intent);
nowDuration=seekBar.getProgress();
textView_nowDuration.setText(seekBar.getProgress()/60+":"+seekBar.getProgress()%60);
}
@Override
protected void onDestroy() {
super.onDestroy();
flag=false;
unregisterReceiver(utilsReceiver);
}
@Override
public void onBackPressed() {
moveTaskToBack(true);
}
}
- 服务中的逻辑代码如下:
- 要想让MusicBar在下拉菜单中显示出来,还必须把它设置为前台服务
- 同时对其中的点击事件设置逻辑处理。
public class MusicService extends Service {
private MediaPlayer mediaPlayer;
private String nowFilePath = null;
private static int startTime = 0;
private RemoteViews rv;
private List<File> musicList;
private int nowPosition;
private MySefReceiver mySefReceiver;
private IntentFilter mySefFilter;
private boolean flag = true;
private boolean isOrderPlay = true;
public MusicService() {
this.musicList = MusicListFactory.newInstance().getList();
}
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onCreate() {
super.onCreate();
init();
initMySefReceiver();
}
private void initMySefReceiver() {
mySefReceiver = new MySefReceiver();
mySefFilter = new IntentFilter();
mySefFilter.addAction("MUSIC_EXIT");
mySefFilter.addAction("SEEKBAR_UPDATE");
mySefFilter.addAction("MUSICBAR_PLAY");
mySefFilter.addAction("MUSICBAR_LEFT");
mySefFilter.addAction("MUSICBAR_RIGHT");
mySefFilter.addAction("UPDATE_PLAY_ORDER");
mySefFilter.addAction("UPDATE_UI");
registerReceiver(mySefReceiver, mySefFilter);
}
private void showMusicBar(String singer, String songName) {
Notification notification = new Notification();
notification.icon = R.mipmap.stateline_ic;
rv = new RemoteViews(getPackageName(), R.layout.layout_musicbar);
PendingIntent intent_play = PendingIntent.getBroadcast(getApplicationContext(), 0, new Intent("MUSICBAR_PLAY"), 0);
rv.setOnClickPendingIntent(R.id.musicbar_play, intent_play);
PendingIntent intent_left = PendingIntent.getBroadcast(getApplicationContext(), 0, new Intent("MUSICBAR_LEFT"), 0);
rv.setOnClickPendingIntent(R.id.musicbar_left, intent_left);
PendingIntent intent_right = PendingIntent.getBroadcast(getApplicationContext(), 0, new Intent("MUSICBAR_RIGHT"), 0);
rv.setOnClickPendingIntent(R.id.musicbar_right, intent_right);
PendingIntent intent_exit = PendingIntent.getBroadcast(getApplicationContext(), 0, new Intent("MUSIC_EXIT"), 0);
rv.setOnClickPendingIntent(R.id.musicbar_exit, intent_exit);
rv.setTextViewText(R.id.musicbar_songname, (nowPosition + 1) + "." + songName + "-" + singer);
if (mediaPlayer.isPlaying()) {
rv.setImageViewResource(R.id.musicbar_play, R.mipmap.play_pic);
} else {
rv.setImageViewResource(R.id.musicbar_play, R.mipmap.pause_pic);
}
notification.contentView = rv;
PendingIntent pendingIntent = PendingIntent.getActivity(getApplicationContext(), 0, new Intent(this, MainActivity.class), 0);
notification.contentIntent = pendingIntent;
startForeground(1, notification);
}
private void init() {
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (mediaPlayer == null) {
mediaPlayer = new MediaPlayer();
}
if (intent == null) {
//必须加入对intent是否为空的判断
} else {
nowPosition = intent.getIntExtra("POSITION", 0);
String filePath = musicList.get(nowPosition).getAbsolutePath();
MediaMetadataRetriever retriever = new MediaMetadataRetriever();
retriever.setDataSource(filePath);
String singer = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ARTIST);
String songName = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_TITLE);
getMusicDuration(retriever);
if (TextUtils.isEmpty(filePath)) {
} else if (!filePath.equals(nowFilePath)) {
nowFilePath = filePath;
mediaPlayer.reset();
try {
mediaPlayer.setDataSource(filePath);
mediaPlayer.prepare();
mediaPlayer.start();
startTime = 0;
Intent intent1 = new Intent("IS_PLAYING");
sendBroadcast(intent1);
showNotification("开始播放:" + (nowPosition + 1) + "." + songName);
sendBroadcast(new Intent("NEW_SONG"));
} catch (IOException e) {
e.printStackTrace();
}
} else {
if (mediaPlayer.isPlaying()) {
mediaPlayer.pause();
Intent intent1 = new Intent("NOT_PLAYING");
sendBroadcast(intent1);
showNotification("暂停播放" + (nowPosition + 1) + "." + songName);
sendBroadcast(new Intent("PAUSE_SONG"));
} else {
mediaPlayer.start();
Intent intent1 = new Intent("IS_PLAYING");
sendBroadcast(intent1);
showNotification("继续播放:" + (nowPosition + 1) + "." + songName);
sendBroadcast(new Intent("COUNTINUE_SONG"));
}
}
showMusicBar(singer, songName);
}
if (mediaPlayer != null) {
mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
@Override
public void onCompletion(MediaPlayer mp) {
sendBroadcast(new Intent("MUSICBAR_RIGHT"));
}
});
}
return super.onStartCommand(intent, flags, startId);
}
private void getMusicDuration(MediaMetadataRetriever retriever) {
int allSeconds = Integer.parseInt(retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)) / 1000;
Intent intent = new Intent("ALL_SECONDS");
intent.putExtra("allSeconds", allSeconds);
sendBroadcast(intent);
}
@Override
public void onDestroy() {
super.onDestroy();
}
public void showNotification(String msg) {
NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
Notification.Builder builder = new Notification.Builder(this);
builder.setSmallIcon(R.mipmap.ic_launcher);
builder.setTicker(msg);
Notification notification = builder.build();
manager.notify(2, notification);
manager.cancel(2);
}
class MySefReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
switch (intent.getAction()) {
case "MUSIC_EXIT":
flag = false;
if (mediaPlayer != null) {
mediaPlayer.stop();
mediaPlayer.release();
}
sendBroadcast(new Intent("PAUSE_SONG"));
Intent intent1 = new Intent("NOT_PLAYING");
sendBroadcast(intent1);
stopSelf();
unregisterReceiver(mySefReceiver);
break;
case "SEEKBAR_UPDATE":
startTime = intent.getIntExtra("seekBar_pargress", 0);
mediaPlayer.seekTo(startTime * 1000);
break;
case "MUSICBAR_PLAY":
Intent intent2 = new Intent(getApplicationContext(), MusicService.class);
intent2.putExtra("POSITION", nowPosition);
startService(intent2);
break;
case "MUSICBAR_LEFT":
if (isOrderPlay) {
if (nowPosition <= 0) {
showNotification("已经到列表第一首");
} else {
nowPosition--;
}
} else {
nowPosition=(int)(Math.random()*musicList.size());
}
Intent intent3 = new Intent(getApplicationContext(), MusicService.class);
intent3.putExtra("POSITION", nowPosition);
startService(intent3);
Intent left_intent=new Intent("UPDATE_POSITION");
left_intent.putExtra("POSITION", nowPosition);
sendBroadcast(left_intent);
break;
case "MUSICBAR_RIGHT":
if (isOrderPlay){
if (nowPosition == musicList.size() - 1) {
showNotification("已经到列表最后一首");
} else {
nowPosition++;
}
}else {
nowPosition=(int)(Math.random()*musicList.size());
}
Intent intent4 = new Intent(getApplicationContext(), MusicService.class);
intent4.putExtra("POSITION", nowPosition);
startService(intent4);
Intent right_intent=new Intent("UPDATE_POSITION");
right_intent.putExtra("POSITION", nowPosition);
sendBroadcast(right_intent);
break;
case "UPDATE_PLAY_ORDER":
isOrderPlay=intent.getBooleanExtra("isOrderPlay",true);
break;
case "UPDATE_UI":
Intent intent5=new Intent("BACK_UPDATE_UI");
intent5.putExtra("nowPosition",nowPosition);
intent5.putExtra("MEDIA_IS_PLAYING",mediaPlayer.isPlaying());
intent5.putExtra("isOrderPlay",isOrderPlay);
intent5.putExtra("nowDuration",mediaPlayer.getCurrentPosition()/1000);
intent5.putExtra("duration",mediaPlayer.getDuration()/1000);
sendBroadcast(intent5);
break;
}
}
}
}
- 其实上面的这些代码大概看一下你就会明白,这个思路不是很复杂,就是代码比较啰嗦
- 大概就是我从界面进行操作,我就给服务发送广播,告诉他我要干什么了,同时更新我自己的UI
- 当我从前台服务操作的时候,我也需要给界面活动发送一个广播,告诉他去实时的更新自己的界面UI
- 其次就是一些逻辑的处理部分了,比如我滑动seekBar,音乐也要跟着从制定位置播放啊,我界面退出了,音乐还在播放,我再次打开软件的时候,界面信息要从服务获取一下,这样才能和服务中播放信息一致等等。
- 主要的代码思路就是这些
- 还有一些小细节的代码并未贴出来,比如开机的界面,同时获取音乐文件,应用单例模式存放我们的音乐文件的List等等。
- 本人菜鸟一枚,代码中各种实现可能并不是处理的很合理,代码中的肯定也存在各种漏洞,欢迎大家指出改正,谢谢。
- 如果需要,欢迎下载源码
时间: 2024-10-14 09:54:56