文章目录
前言
本篇文章主要使用Android Studio来进行微信聊天框架的开发,开发语言为Java。
通过学习本文内容,可以自己实现一个微信的聊天框架,当作一个期中APP小作业。
并且后续还可以在此框架上增添自己需要的内容,完成自己的APP开发(可以应付学校的开发大作业)
安卓四大组件包括Activity、Service、BroadcastReceiver和Content Provider。
主要的知识有Activity、Service、Recycleview、Fragment、Adapter,也就是会用到前两个组件。
本文主要目的是为了实现最终的微信的框架,但为了便于理解以及能够学习到一定的Android和Java知识,会通过介绍必备的知识,引导读者一步一步地实现最终的框架。
文章最后有代码地址,欢迎clone!!!
最终效果如下:
一、实现Activity与xml的连接
1.Activity
Activity,顾名思义,就是活动,是Android应用程序的核心组件之一。
在安卓使用Java开发中,Activity对应着一个Java类,但这个Java类拥有属于自己的生命周期。
我们操作软件进行使用时,就是在进行Activity的切换操作。你也可以把每一个Activity理解为一个UI,也就是所谓的用户界面。
在java文件夹中,右键创建一个空Activity,如下图,这样创建可以自动生成activity对应的xml文件。
这里可以命名你的activity,下面是对应的layout命名,也就是xml布局文件的自动命名生成。
package com.example.app;
import android.os.Bundle;
import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
setContentView(R.layout.activity_main);
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
return insets;
});
}
}
从上面自动生成的代码可以看出系统帮我们自动重写了Activity的onCreate方法,这也是Activity生命周期的第一个部分
(这里建议去单独学习一下Activity的生命周期,有利于理解Activity的创建与销毁(在我往期的文章中有)Activity生命周期理解)
在自动生成的代码中,可以删掉一些可有可无的代码,保留到
setContentView(R.layout.activity_main);
这一行就好
package com.example.app;
import android.os.Bundle;
import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
setContentView(R.layout.activity_main);
}
}
2.XML
XML是一种标记语言,用于描述和存储数据。
你可以将其理解为一个页面中你所看到的内容,都是xml所显示出来的,比如上面的按钮,上面的文字(所做即所见)
每一个Activity对于着一个xml,你可以在xml上手动创建自己的界面
AndroidStudio中可以通过编写xml代码,或者通过控件的拖拽来实现xml的布局设置。
AndroidStudio中提供了两种方式修改xml文件,一个是修改代码,另一个就是直接拖动各种控件到布局中(如上图右),系统会自动生成代码。
4.Activity连接XML中的内容
既然说每一个Activity对应着每一个xml文件,而且Activity和xml又对应着不同的文件,那么我如何把这两个文件连接起来,使我的Activity对应着我xml中的内容呢。
在Activity的onCreate()方法中调用setContentView(R.layout.activity_main),将名为activity_main.xml的布局文件加载到当前Activity中。这样,Activity就可以访问并操作XML布局文件中的控件了。
package com.example.app;
import android.os.Bundle;
import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
setContentView(R.layout.activity_main);
}
}
在上述代码中,可以看到setContentView(R.layout.activity_main);,这个便是将Activity与xml文件连接起来的语句。
5. 实现Activity与xml中的控件绑定(以按钮为例)
xml在手动添加各种控件后都是独立的,也就是系统不会帮你自动连接到Activity。
你可以在使用到该控件的时候,在Activity中使用findviewbyid()方法手动连接一个控件。
接下来我们手动拖入一个button按钮
如上,在xml中手动拖入一个button按钮,并调节其适合的大小,系统帮我们自动生成代码。
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<Button
android:id="@+id/button1"
android:layout_width="197dp"
android:layout_height="117dp"
android:text="Button"
tools:layout_editor_absoluteX="97dp"
tools:layout_editor_absoluteY="241dp"
tools:ignore="MissingConstraints" />
</androidx.constraintlayout.widget.ConstraintLayout>
上面是xml的代码,记住**android:id=“@+id/button1”**每一个控件对应一个唯一的id,记住这个id,后面要考
接下来我们进行Activity与xml中控件(button)的连接。
package com.example.app;
import android.os.Bundle;
import android.widget.Button;
import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
setContentView(R.layout.activity_main);
Button button =findViewById(R.id.button1);
}
}
如上代码,只需添加一行**Button button =findViewById(R.id.button1);**即可将Activity与xml进行连接
使用系统自带的Button类来实例化一个button,并且通过调用findViewById来进行与xml中button的绑定,其中(R.id.button1)中所对应的button便是上面xml中的button的id。
三、实现两个Activity之间的跳转
前面讲到,每一个Activity都是一个用户界面,那么我应该如何实现从一个用户界面跳转到另一个用户界面呢
这里就需要用到我们之前创建的button了,button类自带方法,可以让我们在点击它的时候实现某种逻辑,我们可以通过点击button,调用button中自带的方法来实现我们的跳转。
除此以外,还需要用到一个Intent类,这个可以将它理解为一个指路牌,实现从这——那
(一)不带参数实现跳转
创建一个新的Activity2,并按照上述步骤添加一个按钮,略微完善一下布局()
MainActivity:
package com.example.app;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import androidx.appcompat.app.AppCompatActivity;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button button = findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 创建 Intent 对象,启动 Activity2
Intent intent = new Intent(MainActivity.this, Activity2.class);
startActivity(intent);
}
});
}
}
Activity2:
package com.example.app;
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
public class Activity2 extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_2);
}
}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp">
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="点击跳转到 Activity2" />
</LinearLayout>
activity_2.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="欢迎来到 Activity2!"
android:textSize="18sp" />
</LinearLayout>
启动效果如上图所示,点击按钮,跳转到另一个Activity2
(二)实现带参数跳转
有时候我们跳转到另一个Activity时,想携带着这个Activity的一些参数,所以这个时候我们就需要用到Intent以及Bundle
Bundle的作用则可以理解一个打包的工具,将其捆绑起来,一起传递多个参数。
MainActivity.java:
package com.example.app;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import androidx.appcompat.app.AppCompatActivity;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button button = findViewById(R.id.button); // 找到布局中的按钮
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 创建Intent用于跳转到Activity2
Intent intent = new Intent(MainActivity.this, Activity2.class);
// 创建Bundle并打包数据
Bundle bundle = new Bundle();
bundle.putString("name", "Dreamyheart"); // 传递name
bundle.putInt("age", 25); // 传递age
// 将Bundle附加到Intent中
intent.putExtras(bundle);
startActivity(intent); // 启动Activity2
}
});
}
}
Activity2:
package com.example.app;
import android.os.Bundle;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
public class Activity2 extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_2);
// 获取Intent中的Bundle
Bundle bundle = getIntent().getExtras();
if (bundle != null) {
// 从Bundle中提取参数
String name = bundle.getString("name");
int age = bundle.getInt("age", 0); // 默认值为0
// 在TextView中显示接收到的name和age
TextView textView = findViewById(R.id.textView);
textView.setText("Name: " + name + "\nAge: " + age);
}
}
}
activity_main:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:ignore="ExtraText">
<Button
android:id="@+id/button"
android:layout_width="207dp"
android:layout_height="119dp"
android:text="Go to activity2,and take some parameter"
android:textAppearance="@style/TextAppearance.AppCompat.Body2"
android:textSize="24sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" /><!-- 调整字体大小 -->
android:textStyle="bold" <!-- 设置字体为粗体 -->
android:fontFamily="sans-serif" <!-- 设置字体 -->
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
activity_2.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Welcome to Activity2"
android:textAppearance="@style/TextAppearance.AppCompat.Display1"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.5" />
</androidx.constraintlayout.widget.ConstraintLayout>
最终效果如上图,点击按钮,携带参数跳转,并将参数显示在另一个Activity上
四、recycleview
-
RecyclerView是Android 5.0推出的,是support-v7包中的新组件,它被用来代替ListView和GridView,并且能够实现瀑布流的布局,更加高级并且更加灵活,提供更为高效的回收复用机制,同时实现管理与视图的解耦合。
简单点来说就是我们平时上下滑动的列表,比如微信联系人、音乐播放列表等。
这些列表因为需要频繁更改刷新数据,所以就有一个专门的recycleview来处理这个逻辑。 -
Service也是Android系统四大组件之一,它是一种长生命周期、没有可视化界面、运行于后台的一种服务程序。
比如播放音乐的时候,有可能想边听音乐边干些其他事情,当退出播放音乐的应用,如果不用Service,我们就听不到歌了,所以这时候就得用到Service了。
Android后台运行的很多Service是在系统启动时被开启的,以支持系统的正常工作。比如,MountService监听是否有SD卡安装及移除,PackageManagerService提供软件包的安装、移除及查看等。 -
接下来我们直接通过对比绑定式和非绑定式服务实现音乐列表播放器,也就是有多个音乐可滑动选择,最终效果如下:
因为这是一个实验中的最终效果,我便直接使用该布局,所以可以忽略最上方的按钮
(一)recycleview与item
recycleview和button、textview一样,都是一种布局文件,所以他是一个单独的个体。
既然recycleview是一个列表,那么我应该如何展示我列表中的每一项,让其显示不同的信息呢?
这个时候就需要新建一个xml文件,用来显示recycleview中的每一项,这就是item
activity_main
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp">
<Button
android:id="@+id/myButton"
android:layout_width="369dp"
android:layout_height="71dp"
android:layout_marginBottom="16dp"
android:text="跳转到另一个activity测试绑定式和非绑定式的区别"
android:textSize="16sp" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
上述代码只是在activity_main里添加了recycleview布局,就相当于添加了一个button一样
item_song.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="8dp">
<ImageView
android:id="@+id/albumImage"
android:layout_width="60dp"
android:layout_height="60dp"
android:layout_marginEnd="16dp"
android:src="@drawable/ic_album" />
<LinearLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:orientation="vertical">
<TextView
android:id="@+id/songTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Song Title"
android:textSize="18sp"
android:textStyle="bold" />
<TextView
android:id="@+id/artistName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Artist Name"
android:textSize="14sp" />
</LinearLayout>
<ImageButton
android:id="@+id/playPauseButton"
android:layout_width="63dp"
android:layout_height="54dp"
android:layout_margin="4dp"
android:background="?android:attr/selectableItemBackground"
android:contentDescription="Play/Pause"
android:scaleType="centerInside"
android:src="@drawable/ic_play" />
</LinearLayout>
上面是一个新的xml布局文件,用来显示列表中每一行(每一项)的内容
(二)recycleview与adapter
Adapter 是一个用于在 AdapterView 中提供数据和定义数据如何显示的桥梁类
在这个实验里面就是他连接了我的recycleview和我的MainActivity,实现了将Activity中的数据传到recycleview上显示出来(大致可以这么理解,可以将其理解成一个数据传递的工具人)
下面以音乐播放器为例讲解adapter,这里不单独讲
进阶版recycleview用法可以参考:
https://blog.csdn.net/ZhaiKun68/article/details/100557688
https://blog.csdn.net/jxf_access/article/details/79564669?spm=a2c4e.10696291.0.0.69a619a4fuqkSm
五、对比实验绑定式和非绑定式服务的音乐播放器
(一)非绑定式服务实现音乐播放器
MusicService.java:
package com.example.myapplication;
import android.app.Service;
import android.content.Intent;
import android.media.MediaPlayer;
import android.os.IBinder;
import android.util.Log;
public class MusicService extends Service {
private MediaPlayer mediaPlayer; // 声明 MediaPlayer 对象,用于播放音乐
private int currentTrackResId; // 当前播放的资源ID
// 在服务创建时调用
@Override
public void onCreate() {
super.onCreate();
mediaPlayer = new MediaPlayer(); // 初始化 MediaPlayer 对象
}
// 当服务被启动时调用
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (intent != null && intent.hasExtra("track_res_id")) { // 判断 Intent 是否存在并且包含歌曲资源 ID
int resId = intent.getIntExtra("track_res_id", -1); // 获取传递过来的资源 ID,默认为 -1
if (resId != -1) { // 如果资源 ID 不为 -1,则播放音乐
playMusic(resId); // 调用 playMusic 方法播放音乐
}
}
return START_STICKY; // 返回 START_STICKY,表示服务在被系统终止后会自动重启
}
// 播放指定ID的音乐
public void playMusic(int resId) {
if (mediaPlayer.isPlaying()) { // 如果当前正在播放音乐
mediaPlayer.stop(); // 停止当前播放的音乐
mediaPlayer.reset(); // 重置 MediaPlayer,使其恢复到初始化状态
}
mediaPlayer = MediaPlayer.create(getApplicationContext(), resId); // 根据资源 ID 创建新的 MediaPlayer 实例
mediaPlayer.start(); // 开始播放音乐
currentTrackResId = resId; // 更新当前播放的资源 ID
}
// 暂停音乐播放
public void pauseMusic() {
if (mediaPlayer.isPlaying()) { // 如果音乐正在播放
mediaPlayer.pause(); // 暂停播放
}
}
// 停止音乐播放
public void stopMusic() {
if (mediaPlayer.isPlaying()) { // 如果音乐正在播放
mediaPlayer.stop(); // 停止播放
mediaPlayer.reset(); // 重置 MediaPlayer
}
}
// 服务销毁时调用,释放资源
@Override
public void onDestroy() {
super.onDestroy();
if (mediaPlayer != null) { // 如果 MediaPlayer 不为空
mediaPlayer.release(); // 释放 MediaPlayer 占用的资源
}
}
// 因为这是对普通启动方式进行实验,所以不需调用,直接返回null
@Override
public IBinder onBind(Intent intent) {
return null;
}
}
在代码中,首先重写onCreate方法创建服务,并初始化mediaplayer对象,用来实现音乐播放。
在代码中,重写onStartCommand方法,这就是使用非绑定式服务时,用来启动服务的入口。
在代码中,实现了playMusic方法用来播放指定ID的音乐。因为我是使用到了recycleview,所以在服务中要播放的不止一首音乐。调用这个方法后,会进行条件判断,判断是否当前有在播放音乐,然后进行播放指定ID的音乐。
然后实现了pauseMusic和stopMusic的方法,分别用来暂停音乐的播放和停止音乐的播放。
接着重写了onDestory方法,当服务被销毁时调用该方法,用来释放之前创建的服务中的资源,这也是与绑定式服务不同的一个点。
在最后因为不需要调用onBind,所以直接返回了null。
但是对于不使用的onBind,有点好奇能不能删掉不用,验证是不是程序所必须要的,所以便尝试注释掉。
但是删掉后便报错,报错为MusicService类继承自抽象类service,想要其作为一个具体的类来使用,那么它必须实现service类中的所有抽象方法,包括onBind(Intent)。
点击进入Service类中,发现其是抽象类,因为musicService类是继承自service虚拟类,而我又把musicService作为了一个具体的类来使用。所以如果我要将其作为一个具体的类来使用的话,那我必须实现service里面的所有的抽象方法,所以必须有onBind()方法来满足编译要求。
MainActivity.java:
package com.example.myapplication;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.widget.Button;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import java.util.ArrayList;
import java.util.List;
public class MainActivity extends AppCompatActivity {
private RecyclerView recyclerView;
private List<Song> musicList;
private MusicAdapter adapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
recyclerView = findViewById(R.id.recyclerView);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
musicList = new ArrayList<>();
musicList.add(new Song("普通朋友","陶喆", R.raw.normalfriend, R.drawable.taozhe));
musicList.add(new Song("晴天","周杰伦", R.raw.normalfriend, R.drawable.jchou));
musicList.add(new Song("富士山下","陈奕迅", R.raw.fushishanxia, R.drawable.eson));
musicList.add(new Song("开始懂了", "孙燕姿", R.raw.beginunderstand, R.drawable.beginunderstand));
musicList.add(new Song("浪漫血液", "林俊杰", R.raw.langmanxueye, R.drawable.ljj));
musicList.add(new Song("多远都要在一起", "邓紫棋", R.raw.duoyuandouyaozaiyiqi, R.drawable.dzq));
musicList.add(new Song("天外来物","薛之谦", R.raw.tianwailaiwu, R.drawable.xzq));
musicList.add(new Song("你不知道的事", "王力宏", R.raw.nibuzhidaodeshi, R.drawable.wlh));
musicList.add(new Song("最炫民族风", "凤凰传奇", R.raw.zuixuanminzufeng, R.drawable.fhcq));
adapter = new MusicAdapter(musicList, song -> {
// Start service to play music
Intent intent = new Intent(MainActivity.this, MusicService.class);
intent.putExtra("track_res_id", song.getResId());
startService(intent);
});
recyclerView.setAdapter(adapter);
Button navigateButton = findViewById(R.id.myButton);
navigateButton.setOnClickListener(v -> {
Intent intent = new Intent(MainActivity.this, Activity1.class);
startActivity(intent);
});
}
@Override
protected void onStart() {
super.onStart();
}
@Override
protected void onStop() {
super.onStop();
}
}
在代码中重写了onCreate方法,用来创建我的音乐Activity。 然后实现了将音乐添加到musiclist的逻辑
然后,将musiclist传给adapter。
接着,还实现了一个跳转到另一个activity的按钮逻辑,这个按钮用来实现后面测试绑定式和非绑定式service的不同。
最后,分别重写了onStart和onStop的方法。
MusicAdapter.java:
package com.example.myapplication;
import android.annotation.SuppressLint;
import android.media.MediaPlayer;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import java.util.List;
public class MusicAdapter extends RecyclerView.Adapter<MusicAdapter.MusicViewHolder> {
private List<Song> musicList; // 使用 Song 对象的列表
private OnItemClickListener listener;
private int currentPlayingPosition = -1; // 记录当前播放的歌曲位置
private MediaPlayer mediaPlayer;
private static final String TAG = "MusicAdapter"; // 添加 TAG
// 点击事件监听器接口
public interface OnItemClickListener {
void onItemClick(Song song); // 当点击歌曲时调用
}
// 构造函数,传入音乐列表和点击事件监听器
public MusicAdapter(List<Song> musicList, OnItemClickListener listener) {
this.musicList = musicList;
this.listener = listener;
}
@NonNull
@Override
public MusicViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
// 加载 item_music.xml 布局
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_song, parent, false);
return new MusicViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull MusicViewHolder holder, @SuppressLint("RecyclerView") int position) {
// 获取当前歌曲对象
Song song = musicList.get(position);
holder.songTitle.setText(song.getTitle()); // 设置歌曲名称
holder.songArtist.setText(song.getArtist());
holder.songIcon.setImageResource(song.getIconResId()); // 设置封面图标
// 处理播放/暂停按钮的点击事件
holder.playPauseButton.setOnClickListener(v -> {
if (position == currentPlayingPosition) {
// 如果点击的是正在播放的歌曲,切换播放/暂停状态
if (mediaPlayer != null && mediaPlayer.isPlaying()) {
mediaPlayer.pause();
holder.playPauseButton.setImageResource(R.drawable.ic_play); // 设置为播放图标
} else {
if (mediaPlayer != null) {
mediaPlayer.start();
holder.playPauseButton.setImageResource(R.drawable.ic_pause); // 设置为暂停图标
}
}
} else {
// 播放新的歌曲
if (currentPlayingPosition != -1 && mediaPlayer != null && mediaPlayer.isPlaying()) {
// 停止并释放当前播放的歌曲
mediaPlayer.stop();
mediaPlayer.release();
}
currentPlayingPosition = position;
// 获取歌曲资源 ID
int trackResId = song.getResId(); // 通过 Song 对象获取资源ID
// 初始化 MediaPlayer 并开始播放
mediaPlayer = MediaPlayer.create(holder.itemView.getContext(), trackResId);
mediaPlayer.setOnPreparedListener(mp -> {
mp.start(); // 开始播放音乐
holder.playPauseButton.setImageResource(R.drawable.ic_pause); // 设置为暂停图标
Log.d(TAG, "Started playing: " + song.getTitle());
});
// 防止 MediaPlayer 无法播放
mediaPlayer.setOnErrorListener((mp, what, extra) -> {
mp.reset();
Log.e(TAG, "Error while playing: " + song.getTitle());
return true;
});
// 设置播放完成监听,自动停止并释放资源
mediaPlayer.setOnCompletionListener(mp -> {
holder.playPauseButton.setImageResource(R.drawable.ic_play); // 设置为播放图标
mediaPlayer.release();
mediaPlayer = null;
});
}
});
}
@Override
public int getItemCount() {
return musicList.size(); // 返回音乐列表的长度
}
// 用于释放资源和停止播放
@Override
public void onViewRecycled(@NonNull MusicViewHolder holder) {
super.onViewRecycled(holder);
if (mediaPlayer != null) {
mediaPlayer.release();
mediaPlayer = null;
}
}
// 用于保存当前播放状态和释放资源
public void releaseMediaPlayer() {
if (mediaPlayer != null) {
mediaPlayer.release();
mediaPlayer = null;
}
}
public static class MusicViewHolder extends RecyclerView.ViewHolder {
TextView songTitle;
ImageButton playPauseButton;
ImageView songIcon;
TextView songArtist;
public MusicViewHolder(View itemView) {
super(itemView);
songTitle = itemView.findViewById(R.id.songTitle);
playPauseButton = itemView.findViewById(R.id.playPauseButton);
songIcon = itemView.findViewById(R.id.albumImage);
songArtist = itemView.findViewById(R.id.artistName);
}
}
}
在代码行中,实现了点击事件的监听器接口,当点击某一项歌曲时会调用。
然后实现了MusicAdapter构造函数,用于初始化传入音乐列表和点击事件的监听器。
接着重写了onBindViewHolder方法,实现了音乐播放和暂停的点击逻辑,因为我最终实现的是音乐列表,也就是有多首歌曲,所以我需要实现一个逻辑,来处理我每一次点击某一个按钮时,是播放还是暂停某一首音乐,并且还要更新我的图标。
后面的代码均为处理我音乐播放时的逻辑
后面实现了两个方法,用来停止播放和释放资源。
最后则是实现了MusicViewHolder,用来更新ViewHolder 中缓存的视图元素,展示每一个列表项。
Song.java
package com.example.myapplication;
public class Song {
private String title; // 歌曲标题
private int resId; // 歌曲资源ID(即 raw 资源文件)
private int iconResId; // 歌曲封面图标资源ID
private String artist;
// 构造函数
public Song(String title, String artist,int resId, int iconResId) {
this.title = title;
this.resId = resId;
this.iconResId = iconResId;
this.artist= artist;
}
public String getTitle() {
return title;
}
public String getArtist(){
return artist;
}
public void setTitle(String title) {
this.title = title;
}
public int getResId() {
return resId;
}
public void setResId(int resId) {
this.resId = resId;
}
public int getIconResId() {
return iconResId;
}
public void setIconResId(int iconResId) {
this.iconResId = iconResId;
}
}
在这个Song类中,实现了一些获取我的歌曲资源的方法,比如获取歌曲标题,歌曲作者,歌曲专辑图片等。
activity_main.xml:
这个代码可以在上面讲recycleview与item的时候获取
最终效果:
点击每一个播放按钮,会播放对应的音乐,并且暂停和播放按钮会发生变化。
(二)绑定式服务实现音乐播放器
MainActivity.java:
package com.example.myapplication;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.util.Log;
import android.widget.Button;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import java.util.ArrayList;
import java.util.List;
public class MainActivity extends AppCompatActivity {
private RecyclerView recyclerView;
private List<Song> musicList;
private MusicAdapter adapter;
private MusicService musicService;
private boolean isBound = false;
private ServiceConnection serviceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
MusicService.MusicBinder binder = (MusicService.MusicBinder) service;
musicService = binder.getService();
isBound = true;
}
@Override
public void onServiceDisconnected(ComponentName name) {
isBound = false;
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
recyclerView = findViewById(R.id.recyclerView);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
musicList = new ArrayList<>();
musicList.add(new Song("普通朋友", "陶喆", R.raw.normalfriend, R.drawable.taozhe));
musicList.add(new Song("晴天", "周杰伦", R.raw.normalfriend, R.drawable.jchou));
musicList.add(new Song("富士山下", "陈奕迅", R.raw.fushishanxia, R.drawable.eson));
musicList.add(new Song("开始懂了", "孙燕姿", R.raw.beginunderstand, R.drawable.beginunderstand));
musicList.add(new Song("浪漫血液", "林俊杰", R.raw.langmanxueye, R.drawable.ljj));
musicList.add(new Song("多远都要在一起", "邓紫棋", R.raw.duoyuandouyaozaiyiqi, R.drawable.dzq));
musicList.add(new Song("天外来物", "薛之谦", R.raw.tianwailaiwu, R.drawable.xzq));
musicList.add(new Song("你不知道的事", "王力宏", R.raw.nibuzhidaodeshi, R.drawable.wlh));
musicList.add(new Song("最炫民族风", "凤凰传奇", R.raw.zuixuanminzufeng, R.drawable.fhcq));
adapter = new MusicAdapter(musicList, song -> {
if (isBound) {
musicService.playMusic(song.getResId());
} else {
Toast.makeText(MainActivity.this, "Music Service not bound", Toast.LENGTH_SHORT).show();
}
});
recyclerView.setAdapter(adapter);
Button navigateButton = findViewById(R.id.myButton);
navigateButton.setOnClickListener(v -> {
Intent intent = new Intent(MainActivity.this, Activity1.class);
startActivity(intent);
});
}
@Override
protected void onStart() {
super.onStart();
Intent intent = new Intent(this, MusicService.class);
bindService(intent, serviceConnection, BIND_AUTO_CREATE); // 绑定到服务
}
@Override
protected void onStop() {
super.onStop();
if (isBound) {
unbindService(serviceConnection); // 取消绑定
isBound = false;
}
}
}
在代码中,创建了一个serviceConnection类,里面有onServiceConnected和onServiceDisconnected两个方法,用来处理与服务的连接和断开连接。
然后,重写了两个方法,分别用来实现音乐服务的绑定启动和取消绑定暂停服务。
MusicService.java:
package com.example.myapplication;
import android.app.Service;
import android.content.Intent;
import android.media.MediaPlayer;
import android.os.Binder;
import android.os.IBinder;
import android.util.Log;
public class MusicService extends Service {
private MediaPlayer mediaPlayer;
private int currentTrackResId;
private final IBinder binder = new MusicBinder();
@Override
public void onCreate() {
super.onCreate();
mediaPlayer = new MediaPlayer();
}
public void playMusic(int resId) {
if (mediaPlayer.isPlaying()) {
mediaPlayer.stop();
mediaPlayer.reset();
}
mediaPlayer = MediaPlayer.create(getApplicationContext(), resId);
mediaPlayer.start();
currentTrackResId = resId;
}
public void pauseMusic() {
if (mediaPlayer.isPlaying()) {
mediaPlayer.pause();
}
}
public void stopMusic() {
if (mediaPlayer.isPlaying()) {
mediaPlayer.stop();
mediaPlayer.reset();
}
}
@Override
public void onDestroy() {
super.onDestroy();
if (mediaPlayer != null) {
mediaPlayer.release();
}
}
@Override
public IBinder onBind(Intent intent) {
return binder;
}
public class MusicBinder extends Binder {
public MusicService getService() {
return MusicService.this;
}
}
}
在代码行中,重写了onBind方法,并且返回了binde。
然后创建了一个MusicBinder,继承自Binder类,里面定义了一个getService方法,该方法返回服务的一个实例,指的是外部Service类的当前实例。
(三)实验测试绑定式与非绑定式服务的区别
在MainActivity中添加一个Activity1,用来测试绑定式和非绑定式的区别——看其离开了原来的activity后,是否还能实现继续播放音乐
Activity1:
1.在非绑定式时
点击最上面的大按钮,跳转到另外一个简单activity1中,此时音乐继续播放。
点击最上面的大按钮,跳转到另外一个简单activity1中,此时离开了原来的MainActivity,因为没有绑定到activity,所以音乐还在后台播放。
2.在绑定式时
点击最上面的大按钮,跳转到另外一个简单activity1中,此时离开了原来的MainActivity,音乐暂停了。
六、认识Fragment,实现不同tab页面间的切换
回到我们的最终目标——实现微信框架
在我们日常使用微信的过程中,会在“聊天”——“联系人”——“发现”等栏目中跳转,但是我们可以发现,在跳转后,最下面的内容是几乎不变的,变化的只有点击和被点击的按钮
使用上面这个逻辑。是通过Fragment来实现的,也就是将底部固定,然后中间的聊天列表、联系人列表等,是一个个Frament,当需要用到这个页面的时候,就把这个Fragment展现出来,其他的就隐藏起来
(一)搭建页面的视图
首先,我们需要微信的顶部和底部都不变,只有中间的内容变化。
top.xml:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/textView5"
android:layout_width="245dp"
android:layout_height="50dp"
android:text="我的微信"
android:textAlignment="center"
android:textColor="#FFC107"
android:textSize="34sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.06" />
</androidx.constraintlayout.widget.ConstraintLayout>
这就是微信每一个页面的最上面不动的部分
botton.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<!-- 第一个选项 -->
<LinearLayout
android:layout_width="0dp"
android:layout_height="113dp"
android:layout_weight="1"
android:gravity="center"
android:orientation="vertical"
android:padding="16dp">
<ImageView
android:id="@+id/image1"
android:layout_width="50dp"
android:layout_height="50dp"
android:src="@drawable/img_1" />
<Button
android:id="@+id/button1"
android:layout_width="50dp"
android:layout_height="50dp"
android:gravity="center"
android:visibility="visible" />
<TextView
android:id="@+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="首页"
android:textAlignment="center"
android:textSize="20sp" />
</LinearLayout>
<!-- 第二个选项 -->
<LinearLayout
android:layout_width="0dp"
android:layout_height="113dp"
android:layout_weight="1"
android:gravity="center"
android:orientation="vertical"
android:padding="16dp">
<ImageView
android:id="@+id/image2"
android:layout_width="50dp"
android:layout_height="50dp"
android:src="@drawable/img_1" />
<Button
android:id="@+id/button2"
android:layout_width="50dp"
android:layout_height="50dp"
android:gravity="center"
android:visibility="visible" />
<TextView
android:id="@+id/textView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="联系人"
android:textAlignment="center"
android:textSize="20sp" />
</LinearLayout>
<!-- 第三个选项 -->
<LinearLayout
android:layout_width="0dp"
android:layout_height="113dp"
android:layout_weight="1"
android:gravity="center"
android:orientation="vertical"
android:padding="16dp">
<ImageView
android:id="@+id/image3"
android:layout_width="50dp"
android:layout_height="50dp"
android:src="@drawable/img_1" />
<Button
android:id="@+id/button3"
android:layout_width="50dp"
android:layout_height="50dp"
android:gravity="center"
android:visibility="visible" />
<TextView
android:id="@+id/textView3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="发现"
android:textAlignment="center"
android:textSize="20sp" />
</LinearLayout>
<!-- 第四个选项 -->
<LinearLayout
android:layout_width="0dp"
android:layout_height="113dp"
android:layout_weight="1"
android:gravity="center"
android:orientation="vertical"
android:padding="16dp">
<ImageView
android:id="@+id/image4"
android:layout_width="50dp"
android:layout_height="50dp"
android:src="@drawable/img_1" />
<Button
android:id="@+id/button4"
android:layout_width="50dp"
android:layout_height="50dp"
android:gravity="center"
android:visibility="visible" />
<TextView
android:id="@+id/textView4"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="我的"
android:textAlignment="center"
android:textSize="20sp" />
</LinearLayout>
</LinearLayout>
上面是微信中最下面的选择菜单栏(这个版本还没有将按钮隐藏,有点丑())后面会修改。
然后我们进行中间页面的设置
fragment_layout1.xml:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/fragment1"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".Fragment1">
<!-- TODO: Update blank fragment layout -->
<TextView
android:id="@+id/framentlayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="这是微信聊天界面"
android:textAlignment="center"
android:textSize="48sp" />
</FrameLayout>
这是中间的内容,只是简单的文字。
后面再次创建相同的三个页面的fragment_layout2、fragment_layout3、fragment_layout4
然后在一个主文件activity_fragment_main.xml中引入上中下部分:
activity_fragment_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".Fragment_MainActivity">
<!-- 上部区域:引入top布局 -->
<include
layout="@layout/top"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<!-- 中部区域:FrameLayout,用于Fragment -->
<FrameLayout
android:id="@+id/fragment_container"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:background="#f0f0f0" />
<!-- 下部区域:引入botton布局 -->
<include
layout="@layout/botton"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
最终效果如上,静态页面设置完成
(二)实现页面的切换
要实现页面的切换,就不能只停留在xml布局的静态页面中,要回到java文件中
创建Fragment_MainActivity、Fragment1、Fragment2、Fragment3、Fragment4
Fragment_MainActivity:
package com.example.myapplication; // 定义包名
import android.os.Bundle; // 导入 Bundle 类,用于存储和传递活动的状态信息
import android.view.View;
import android.widget.Button;
import androidx.activity.EdgeToEdge; // 导入 EdgeToEdge 类,处理全屏模式的 UI 布局
import androidx.appcompat.app.AppCompatActivity; // 导入 AppCompatActivity 类,它是支持向后兼容的 Activity 类
import androidx.core.graphics.Insets; // 导入 Insets 类,处理窗口插图(状态栏、导航栏等)
import androidx.core.view.ViewCompat; // 导入 ViewCompat 类,提供视图的兼容性支持
import androidx.core.view.WindowInsetsCompat; // 导入 WindowInsetsCompat 类,管理窗口插图的兼容性
import androidx.fragment.app.Fragment; // 导入 Fragment 类,定义片段行为
import androidx.fragment.app.FragmentManager; // 导入 FragmentManager 类,管理片段的生命周期
import androidx.fragment.app.FragmentTransaction; // 导入 FragmentTransaction 类,执行片段事务(添加、移除、替换)
// 定义 Fragment_MainActivity 类,继承自 AppCompatActivity
public class Fragment_MainActivity extends AppCompatActivity implements View.OnClickListener {
Fragment fragment1, fragment2, fragment3, fragment4;
FragmentManager manager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_fragment_main); // 确保这是你的主布局
// 初始化 Fragment
fragment1 = new Fragment1();
fragment2 = new Fragment2();
fragment3 = new Fragment3();
fragment4 = new Fragment4();
manager = getSupportFragmentManager();
FragmentTransaction transaction = manager.beginTransaction();
// 添加 Fragment 并初始隐藏
transaction.add(R.id.fragment_container, fragment1);
transaction.add(R.id.fragment_container, fragment2);
transaction.add(R.id.fragment_container, fragment3);
transaction.add(R.id.fragment_container, fragment4);
transaction.hide(fragment2);
transaction.hide(fragment3);
transaction.hide(fragment4);
transaction.commit();
// 设置按钮并监听,确保这些按钮在 activity_fragment_main.xml 中存在
Button button1 = findViewById(R.id.button1);
Button button2 = findViewById(R.id.button2);
Button button3 = findViewById(R.id.button3);
Button button4 = findViewById(R.id.button4);
button1.setOnClickListener(this);
button2.setOnClickListener(this);
button3.setOnClickListener(this);
button4.setOnClickListener(this);
}
@Override
public void onClick(View view) {
Fragment fragmentToShow = null;
if (view.getId() == R.id.button1) {
fragmentToShow = fragment1;
} else if (view.getId() == R.id.button2) {
fragmentToShow = fragment2;
} else if (view.getId() == R.id.button3) {
fragmentToShow = fragment3;
} else if (view.getId() == R.id.button4) {
fragmentToShow = fragment4;
}
if (fragmentToShow != null) {
showFragment(fragmentToShow);
}
}
private void showFragment(Fragment fragment) {
FragmentTransaction transaction = manager.beginTransaction();
// 隐藏所有 Fragment
transaction.hide(fragment1);
transaction.hide(fragment2);
transaction.hide(fragment3);
transaction.hide(fragment4);
// 显示所选的 Fragment
transaction.show(fragment);
transaction.commit();
}
}
Fragment1:
package com.example.myapplication;
import android.os.Bundle;
import androidx.fragment.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
/**
* A simple {@link Fragment} subclass.
* Use the {@link Fragment1#newInstance} factory method to
* create an instance of this fragment.
*/
public class Fragment1 extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_layout1, container, false);
}
}
其他三个Fragment几乎一样
如上图,点击最下面的丑丑的按钮()可以实现页面的跳转了!
自己再精修一下就可以实现基本的微信框架了!!!!
七、给聊天框架增添亿点点内容
(一)添加一个recycleview并且可以点进去查看详情
Fragment_MainActivity.java不变 Fragment1、Fragment3、Fragment4不变
新增Adapter2(同来将数据绑定到recycle中)、layout_item.xml(recycleview中的每一项怎么放)、DetailActivity(用来查看每一项的详情)、activity_detail.xml(你想要的详情页面的布局)
Fragment2:
package com.example.myapplication;
import android.content.Intent;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class Fragment2 extends Fragment {
private RecyclerView recyclerView;
private List<Map<String, Object>> list1;
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_layout2, container, false);
recyclerView = view.findViewById(R.id.recyclerViewInFragment);
// 数据初始化
int[] phoneimageID = {R.drawable.xiaomi14, R.drawable.iphone16, R.drawable.vivox200pro, R.drawable.oppofindx8};
String[] price = {"4899", "7999", "3999", "4000"};
String[] config = {"骁龙8", "A18", "天玑9400", "天玑9400"};
list1 = new ArrayList<>();
for (int i = 0; i < phoneimageID.length; i++) {
Map<String, Object> map = new HashMap<>();
map.put("imageID", phoneimageID[i]);
map.put("price", price[i]);
map.put("config", config[i]);
list1.add(map);
}
// 设置适配器
Adapter2 adapter2 = new Adapter2(list1);
recyclerView.setAdapter(adapter2);
recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
// Set click listener for RecyclerView items
adapter2.setOnItemClickListener(position -> {
// Handle click event
Map<String, Object> selectedItem = list1.get(position);
// Create an Intent to navigate to the detail Activity
Intent intent = new Intent(getContext(), DetailActivity.class);
// Pass the selected item data (image, price, config) to the detail Activity
intent.putExtra("imageID", (Integer) selectedItem.get("imageID"));
intent.putExtra("price", (String) selectedItem.get("price"));
intent.putExtra("config", (String) selectedItem.get("config"));
startActivity(intent);
});
return view;
}
}
Adapter2:
package com.example.myapplication;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import java.util.List;
import java.util.Map;
public class Adapter2 extends RecyclerView.Adapter<Adapter2.MyViewHolder> {
private List<Map<String, Object>> list;
private OnItemClickListener onItemClickListener;
// Constructor
public Adapter2(List<Map<String, Object>> list) {
this.list = list;
}
// Set the listener
public void setOnItemClickListener(OnItemClickListener listener) {
this.onItemClickListener = listener;
}
@NonNull
@Override
public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
Context context = parent.getContext();
View view = LayoutInflater.from(context).inflate(R.layout.layout_item, parent, false);
return new MyViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull MyViewHolder holder, int position) {
Map<String, Object> currentItem = list.get(position);
holder.imageView.setImageResource((Integer) currentItem.get("imageID"));
holder.textView2.setText((String) currentItem.get("price"));
holder.textView3.setText((String) currentItem.get("config"));
// Set click listener for the item
holder.itemView.setOnClickListener(v -> {
if (onItemClickListener != null) {
onItemClickListener.onItemClick(position);
}
});
}
@Override
public int getItemCount() {
return list.size();
}
public interface OnItemClickListener {
void onItemClick(int position);
}
class MyViewHolder extends RecyclerView.ViewHolder {
TextView textView2, textView3;
ImageView imageView;
public MyViewHolder(@NonNull View itemView) {
super(itemView);
textView2 = itemView.findViewById(R.id.rctextView);
textView3 = itemView.findViewById(R.id.rctextView2);
imageView = itemView.findViewById(R.id.rcimageView2);
}
}
}
DetailActivity.java:
package com.example.myapplication;
import android.content.Intent;
import android.os.Bundle;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
public class DetailActivity extends AppCompatActivity {
private ImageView imageView;
private TextView priceTextView, configTextView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_detail);
// Initialize the views
imageView = findViewById(R.id.imageViewDetail);
priceTextView = findViewById(R.id.textViewPrice);
configTextView = findViewById(R.id.textViewConfig);
// Get the data passed from the previous Activity (Fragment)
Intent intent = getIntent();
int imageID = intent.getIntExtra("imageID", 0); // Default value 0 if not found
String price = intent.getStringExtra("price");
String config = intent.getStringExtra("config");
// Set the data to the views
imageView.setImageResource(imageID);
priceTextView.setText("价格: " + price);
configTextView.setText("配置: " + config);
}
}
(二)在一个页面上实现音乐播放器(好像毫无意义())
Fragment4:
package com.example.myapplication;
import android.content.Intent;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import java.util.ArrayList;
import java.util.List;
public class Fragment4 extends Fragment {
private RecyclerView recyclerView;
private List<Song> musicList;
private MusicAdapter adapter;
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_layout4, container, false);
recyclerView = view.findViewById(R.id.recyclerViewMusic);
recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
// 初始化音乐列表,添加 Song 对象
musicList = new ArrayList<>();
musicList.add(new Song("普通朋友", R.raw.normalfriend, R.drawable.normalfriend)); // 歌曲名,资源ID,封面图标ID
musicList.add(new Song("晴天", R.raw.normalfriend, R.drawable.jchou));
musicList.add(new Song("富士山下", R.raw.fushishanxia, R.drawable.eson));
musicList.add(new Song("开始懂了", R.raw.beginunderstand, R.drawable.beginunderstand));
musicList.add(new Song("富士山下", R.raw.fushishanxia, R.drawable.eson));
musicList.add(new Song("普通朋友", R.raw.normalfriend, R.drawable.normalfriend));
musicList.add(new Song("富士山下", R.raw.fushishanxia, R.drawable.eson));
musicList.add(new Song("晴天", R.raw.normalfriend, R.drawable.jchou));
musicList.add(new Song("开始懂了", R.raw.beginunderstand, R.drawable.beginunderstand));
// 创建音乐适配器
adapter = new MusicAdapter(musicList, song -> {
// 启动服务播放音乐
Intent intent = new Intent(getContext(), MusicService.class);
intent.putExtra("track_res_id", song.getResId()); // 传递歌曲资源ID
getContext().startService(intent); // 启动服务播放音乐
});
recyclerView.setAdapter(adapter);
return view;
}
}
MusicService:
package com.example.myapplication;
import android.app.Service;
import android.content.Intent;
import android.media.MediaPlayer;
import android.os.IBinder;
import android.util.Log;
public class MusicService extends Service {
private MediaPlayer mediaPlayer; // 声明 MediaPlayer 对象,用于播放音乐
private int currentTrackResId; // 当前播放的资源ID
// 在服务创建时调用
@Override
public void onCreate() {
super.onCreate();
mediaPlayer = new MediaPlayer(); // 初始化 MediaPlayer 对象
}
// 当服务被启动时调用
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (intent != null && intent.hasExtra("track_res_id")) { // 判断 Intent 是否存在并且包含歌曲资源 ID
int resId = intent.getIntExtra("track_res_id", -1); // 获取传递过来的资源 ID,默认为 -1
if (resId != -1) { // 如果资源 ID 不为 -1,则播放音乐
playMusic(resId); // 调用 playMusic 方法播放音乐
}
}
return START_STICKY; // 返回 START_STICKY,表示服务在被系统终止后会自动重启
}
// 播放指定ID的音乐
public void playMusic(int resId) {
if (mediaPlayer.isPlaying()) { // 如果当前正在播放音乐
mediaPlayer.stop(); // 停止当前播放的音乐
mediaPlayer.reset(); // 重置 MediaPlayer,使其恢复到初始化状态
}
mediaPlayer = MediaPlayer.create(getApplicationContext(), resId); // 根据资源 ID 创建新的 MediaPlayer 实例
mediaPlayer.start(); // 开始播放音乐
currentTrackResId = resId; // 更新当前播放的资源 ID
}
// 暂停音乐播放
public void pauseMusic() {
if (mediaPlayer.isPlaying()) { // 如果音乐正在播放
mediaPlayer.pause(); // 暂停播放
}
}
// 停止音乐播放
public void stopMusic() {
if (mediaPlayer.isPlaying()) { // 如果音乐正在播放
mediaPlayer.stop(); // 停止播放
mediaPlayer.reset(); // 重置 MediaPlayer
}
}
// 检查音乐是否正在播放
public boolean isPlaying() {
return mediaPlayer.isPlaying(); // 返回 MediaPlayer 的播放状态
}
// 服务销毁时调用,释放资源
@Override
public void onDestroy() {
super.onDestroy();
if (mediaPlayer != null) { // 如果 MediaPlayer 不为空
mediaPlayer.release(); // 释放 MediaPlayer 占用的资源
}
}
// 因为这是对普通启动方式进行实验,所以不需调用,直接返回null
@Override
public IBinder onBind(Intent intent) {
return null;
}
}
Song.java:
package com.example.myapplication;
public class Song {
private String title; // 歌曲名称
private int resId; // 歌曲资源ID
private int iconResId; // 歌曲封面图标资源ID
// 构造函数
public Song(String title, int resId, int iconResId) {
this.title = title;
this.resId = resId;
this.iconResId = iconResId;
}
// 获取歌曲标题
public String getTitle() {
return title;
}
// 获取歌曲资源ID
public int getResId() {
return resId;
}
// 获取封面图标资源ID
public int getIconResId() {
return iconResId;
}
}
item_music.xml:
<!-- item_music.xml -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="10dp"
android:gravity="center_vertical">
<!-- 音乐封面图标 -->
<ImageView
android:id="@+id/songIcon"
android:layout_width="50dp"
android:layout_height="50dp"
android:src="@drawable/jchou" /> <!-- 可替换为你自己的图标 -->
<!-- 歌曲标题 -->
<TextView
android:id="@+id/songTitle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Song Name"
android:textSize="16sp"
android:paddingStart="10dp" />
<!-- 播放/暂停按钮 -->
<ImageButton
android:id="@+id/playPauseButton"
android:layout_width="50dp"
android:layout_height="50dp"
android:src="@drawable/ic_play"
android:contentDescription="Play/Pause"
android:background="?android:attr/selectableItemBackground"
android:scaleType="centerInside" />
</LinearLayout>
最终效果:
(三)在聊天页面模拟发送信息和接收信息(最终框架)
最终效果::
最终版本代码较多,不一一上传,在这里给代码仓库地址,欢迎clone下来进行魔改。代码仓库地址
总结
总的来说,最终实现了一个微信的框架,四个页面都有recycleview,也符合实际的微信情况,最后一个页面修改成了音乐播放器,可以在这一部分内容中学习service的知识。
主要用到的知识有Activity、Service、Recycleview、Fragment、Adapter
最后,如果这篇文章对你有帮助的,请帮忙点赞加收藏!