目录
一、功能需求
1、完成一个类似微信页面的布局、需要实现4个tab切换效果;本功能要求需要的技术为:activity、xml、fragment
2、使用recyclerView实现滚动列表,并实现点击联系人展开显示详细聊天信息。
二、页面布局设计
1. 顶部top.xml布局
创建layout文件top.xml中,添加文本框,添加文字,设置为居中,修改文字颜色和LinearLayout背景颜色。
代码如下:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:orientation="vertical" android:gravity="center" android:layout_height="50dp" android:background="#0E0D0D" > <TextView android:id="@+id/textView" android:layout_width="wrap_content" android:layout_height="match_parent" android:layout_weight="1" android:gravity="center_horizontal" android:text="我的微信" android:textColor="@color/white" android:textSize="25sp" /> </LinearLayout>
效果如下:
2. 底部bottom.xml布局
新建layout布局文件button.xml。
先添加一个LinearLayout(vertical),修改LinearLayout大小和颜色:修改gravity所对应的配置信息为center,再修改大小,添加id。
在刚刚添加的LinearLayout(vertical)下添加ImageButton,选择要放入的图片。
代码如下:
<?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" android:layout_width="match_parent" android:layout_height="60dp" android:background="@color/colorViewNormal" android:gravity="center"> <LinearLayout android:id="@+id/chat" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_weight="1" android:gravity="center" android:orientation="vertical"> <ImageView android:id="@+id/imageView1" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_weight="1" app:srcCompat="@drawable/chat1" /> </LinearLayout> <LinearLayout android:id="@+id/contacts" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_weight="1" android:gravity="center" android:orientation="vertical"> <ImageView android:id="@+id/imageView2" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_weight="1" app:srcCompat="@drawable/contracts2" /> </LinearLayout> <LinearLayout android:id="@+id/circle_friend" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_weight="1" android:gravity="center" android:orientation="vertical"> <ImageView android:id="@+id/imageView3" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_weight="1" app:srcCompat="@drawable/circleoffriend2" /> </LinearLayout> <LinearLayout android:id="@+id/settings" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_weight="1" android:gravity="center" android:orientation="vertical"> <ImageView android:id="@+id/imageView4" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_weight="1" app:srcCompat="@drawable/setting2" android:scaleType="fitXY"/> </LinearLayout> </LinearLayout>
复制三个修改好的LinearLayout,修改ImageButton的资源路径,分别修改LinearLayout、ImageView的id。
效果如下:
3. 中间页面设置
新建四个layout.xml文件,分别添加一个ImageView,修改文字内容,并将ImageView设置居中
代码如下:
<?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:context=".chat"> <ImageView android:id="@+id/imageView6" android:layout_width="match_parent" android:layout_height="wrap_content" android:src="@drawable/chat" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" android:scaleType="fitXY"/> </androidx.constraintlayout.widget.ConstraintLayout>
4. item.xml
RecyclerView列表中的行样式文件,item.xml.装载显示联系人头像、姓名、发送的信息以及发送信息时间。同时含有一个初始为隐藏的消息详情展开栏。
代码如下:
<?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:id="@+id/Clicked" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@drawable/underline" android:clickable="true" android:orientation="vertical"> <LinearLayout android:layout_width="match_parent" android:layout_height="100dp" android:gravity="center" android:orientation="horizontal"> <ImageView android:id="@+id/photo" android:layout_width="75dp" android:layout_height="75dp" android:layout_gravity="center" /> <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="1" android:orientation="vertical"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal"> <TextView android:id="@+id/cctName" android:layout_width="match_parent" android:layout_height="40dp" android:layout_weight="1" android:gravity="left" android:text="姓名" android:textSize="30sp" /> <TextView android:id="@+id/time" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="right" android:layout_weight="1" android:gravity="right" android:text="时间" android:textSize="15sp" /> </LinearLayout> <TextView android:id="@+id/cctMsg" android:layout_width="match_parent" android:layout_height="50dp" android:layout_gravity="center" android:gravity="bottom" android:text="内容" android:textSize="20sp" /> </LinearLayout> </LinearLayout> <LinearLayout android:id="@+id/detail" android:layout_width="match_parent" android:layout_height="80dp" android:background="@color/cardview_shadow_start_color" android:orientation="vertical" android:visibility="gone"> <TextView android:id="@+id/detail_text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="展开内容" android:textSize="20sp" /> </LinearLayout> </LinearLayout>
样式预览:
5. activity_main.xml
我们想要的效果是页面最上方是标题,中间显示文本,最下方有四个按钮,故在activity_main.xml中先添加一个LinearLayout
在LinearLayout中按顺序添加三个部分:
通过include标签添加top.xml和button.xml
由于四个显示文本的页面都是在中间,故选择通过放一个FrameLayout在top和button的中间
代码如下:
<?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="vertical" tools:context="com.example.myapplication.MainActivity"> <include layout="@layout/top"></include> <FrameLayout android:id="@+id/frame_content" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_weight="1"> <ImageView android:id="@+id/imageView9" android:layout_width="wrap_content" android:layout_height="wrap_content" app:srcCompat="@drawable/setting" android:scaleType="fitXY"/> </FrameLayout> <include layout="@layout/bottom"></include> </LinearLayout>
6. 资源控制drawable文件
在使用之前先将图片文件导入到/res/drawable中,可以直接将windows本地文件拖拽至目标文件夹中
三、页面跳转控制(java文件)
1. MainActivity
这里是MainActivity类,并且实现了View.OnClickListener接口,表示该类的实例可以响应View的点击事件。
接下来定义了四个Fragment变量,这里通过new关键字创建四个不同的Fragment对象,分别表示微信、联系人、朋友圈和设置界面。
定义了四个LinearLayout,分别对应四个底部菜单栏中的菜单项,这里使用了相对布局。
接着定义了四个ImageView,分别对应四个底部菜单栏中的菜单项的图标,定义了一个FragmentManager对象和一个RecyclerView对象。
onCreate()是Activity生命周期中的一个重要方法,它在这里用于实现界面的初始化,包括初始化视图、初始化界面、初始化事件等等。
代码如下:
public class MainActivity extends AppCompatActivity implements View.OnClickListener{ //Fragment private Fragment weixinFragment =new weixinFragment(); private Fragment Fragment_contacts =new contacts(); private Fragment Fragment_circleoffriend =new circleoffriend(); private Fragment Fragment_setting =new setting(); //底端菜单栏LinearLayout private LinearLayout linear_chat; private LinearLayout linear_contacts; private LinearLayout linear_circleoffriend; private LinearLayout linear_setting; //底端菜单栏中的Imageview private ImageView imageView_chat; private ImageView imageView_contacts; private ImageView imageView_circleoffriend; private ImageView imageView_setting; //FragmentManager private FragmentManager fragmentManager; private RecyclerView recyclerView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Context context = this; initView(); initFragment(); initEvent(); Fragment_select(0); //将第一个图标设为选中状态 imageView_chat.setImageResource(R.drawable.chat); imageView_chat.setImageResource(R.drawable.chat1); }
onClick()方法是View.OnClickListener接口中定义的方法,用于响应View组件的点击事件。
在本例中,当用户点击底部菜单栏中的图标时,会触发该方法,并执行相应的操作。
首先调用restartButton()方法,将所有的ImageView设置为未选中。
然后获取被点击的View的ID,根据ID判断是哪个菜单项被点击了。
根据用户点击的菜单项来切换Fragment,同时改变对应菜单图标的状态。
代码如下:
@Override public void onClick(View view) { // 每次点击之后,将所有的ImageView和TextView设置为未选中 restartButton(); int viewId = view.getId(); // 将view的id赋值给变量viewId if (viewId == R.id.chat) { // 选择所点击的菜单对应的图层片段 Fragment_select(0); // 将该菜单的点击状态置为点击态 imageView_chat.setImageResource(R.drawable.chat); imageView_chat.setImageResource(R.drawable.chat1); } else if (viewId == R.id.contacts) { Fragment_select(1); imageView_contacts.setImageResource(R.drawable.contracts); imageView_contacts.setImageResource(R.drawable.contracts1); } else if (viewId == R.id.circle_friend) { Fragment_select(2); imageView_circleoffriend.setImageResource(R.drawable.circleoffirend); imageView_circleoffriend.setImageResource(R.drawable.circleoffriend1); } else if (viewId == R.id.settings) { Fragment_select(3); imageView_setting.setImageResource(R.drawable.setting); imageView_setting.setImageResource(R.drawable.setting1); } else { // 处理默认情况 } }
这里定义了一个restartButton()方法,用于将底部菜单栏中所有的ImageView设置为未选中状态。
代码如下:
//重置菜单的点击状态,设为未点击 private void restartButton() { //设置为未点击状态 imageView_chat.setImageResource(R.drawable.chat); imageView_chat.setImageResource(R.drawable.chat2); imageView_contacts.setImageResource(R.drawable.contracts); imageView_contacts.setImageResource(R.drawable.contracts2); imageView_circleoffriend.setImageResource(R.drawable.circleoffirend); imageView_circleoffriend.setImageResource(R.drawable.circleoffriend2); imageView_setting.setImageResource(R.drawable.setting); imageView_setting.setImageResource(R.drawable.setting2); }
initFragment()方法用于初始化图层片段,通过FragmentManager管理四个Fragment的显示和隐藏。
由于我们需要在同一时刻只显示一个Fragment,因此需要调用hideView()方法隐藏所有Fragment,然后再调用show()方法显示所选中的Fragment。
代码如下:
//初始化中间的部分的图层片段 private void initFragment(){ fragmentManager=getSupportFragmentManager(); FragmentTransaction transaction=fragmentManager.beginTransaction(); transaction.add(R.id.frame_content, weixinFragment); transaction.add(R.id.frame_content, Fragment_contacts); transaction.add(R.id.frame_content, Fragment_circleoffriend); transaction.add(R.id.frame_content, Fragment_setting); hideView(transaction); transaction.show(weixinFragment); //提交事务 transaction.commit(); }
initView()方法用于初始化底部菜单栏的各个组件,包括LinearLayout和ImageView。
代码如下:
//初始化各底端的LinearLayout、ImageView和TextView组件 private void initView(){ //改变图标颜色 linear_chat =findViewById(R.id.chat); linear_contacts =findViewById(R.id.contacts); linear_circleoffriend =findViewById(R.id.circle_friend); linear_setting =findViewById(R.id.settings); imageView_chat =findViewById(R.id.imageView1); imageView_contacts =findViewById(R.id.imageView2); imageView_circleoffriend =findViewById(R.id.imageView3); imageView_setting =findViewById(R.id.imageView4); }
initEvent()方法用于初始化点击监听事件。
这里为底部菜单栏中的菜单项设置了点击事件,当用户点击某个菜单项时,触发onClick()方法进行相应的操作。
代码如下:
//初始化点击监听事件 private void initEvent(){ linear_chat.setOnClickListener(this); linear_contacts.setOnClickListener(this); linear_circleoffriend.setOnClickListener(this); linear_setting.setOnClickListener(this); }
hideView()方法用于隐藏所有的Fragment,以便在切换Fragment时调用。
代码如下:
//把没有使用的界面的内容隐藏 private void hideView(FragmentTransaction transaction){ transaction.hide(weixinFragment); transaction.hide(Fragment_contacts); transaction.hide(Fragment_circleoffriend); transaction.hide(Fragment_setting); }
最后是Fragment_select()方法,用于根据用户点击的底部菜单栏中的图标,选择相应的Fragment进行显示。该方法首先调用hideView()方法隐藏所有的Fragment,然后根据用户点击的菜单栏来选择需要显示的Fragment,最后提交事务。
代码如下:
private void Fragment_select(int i){ FragmentTransaction transaction=fragmentManager.beginTransaction(); //调用隐藏所有图层函数 hideView(transaction); switch (i){ case 0: transaction.show(weixinFragment); break; case 1: transaction.show(Fragment_contacts); break; case 2: transaction.show(Fragment_circleoffriend); break; case 3: transaction.show(Fragment_setting); break; default: break; } //提交转换事务 transaction.commit(); } }
2. weixinFragment
这个Fragment,用于显示聊天消息列表,并在初始化时加载了一些示例数据,并且使用recyclerView实现滚动列表,并实现点击联系人展开显示详细聊天信息。
导入所需的Fragment类和RecyclerView相关的类,并定义了一个名为weixinFragment的类,它继承自Fragment类。并且定义了一些私有变量,其中data是一个存储聊天消息数据的列表,view是Fragment的视图,recyclerView是用于显示消息列表的RecyclerView,myAdapter是RecyclerView的适配器。
代码如下:
private List<Map<String, Object>> data = new ArrayList<Map<String, Object>>(); private View view; private RecyclerView recyclerView; private MyAdapter myAdapter;
重写了Fragment的onCreateView方法,在该方法中初始化Fragment的视图,并调用了initRecyclerView和initData方法。最后返回Fragment的视图。
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { view = inflater.inflate(R.layout.fragment_weixin, container, false); initRecyclerView(); initData(); // Inflate the layout for this fragment return view; }
initData方法用于初始化聊天消息数据,包括聊天头像、昵称、时间和消息内容等。
代码如下:
private void initData() { String[] imgSrc = {"https://img.touxiangwu.com/zb_users/upload/2022/12/202212051670208807419337.jpg", "https://www.beihaiting.com/uploads/allimg/160212/10723-1602120951452J.jpg", "https://upload-images.jianshu.io/upload_images/18279216-625cefbb47c316a1?imageMogr2/auto-orient/strip|imageView2/2/format/webp", "https://tse4-mm.cn.bing.net/th/id/OIP-C.5edzIeOn6loCRwkYK1LnjgHaED?pid=ImgDet&rs=1", "https://ts1.cn.mm.bing.net/th/id/R-C.e3ee06dd3fd26471fc34656a7c45df79?rik=X%2bxecOO1lLXcLw&riu=http%3a%2f%2fqimg.hxnews.com%2f2017%2f0820%2f1503191526949.jpeg&ehk=4V4cqIgdhI2z8WAfrhCRw2zQlaiDUQycu0OG4xNuEy0%3d&risl=&pid=ImgRaw&r=0", "https://ts1.cn.mm.bing.net/th/id/R-C.d63793ae33409c0c1d34d9d0a018b070?rik=bSj2dvd%2fJzQzFQ&riu=http%3a%2f%2fimg1.gtimg.com%2fent%2fpics%2fhv1%2f86%2f39%2f1704%2f110812631.jpg&ehk=IMqiuo4zmlFwCz6GcDc1ksbEDLaAxJRsCnfOx8pFaSg%3d&risl=&pid=ImgRaw&r=0", "https://c-ssl.duitang.com/uploads/item/202004/24/20200424193152_cdpzf.jpeg", "https://gss0.baidu.com/7Po3dSag_xI4khGko9WTAnF6hhy/zhidao/wh%3D600%2C800/sign=8ae53caa70899e5178db32127297f50b/0823dd54564e92586e1669b99782d158ccbf4e43.jpg" }; String[] cttName = {"蓦山溪", "离亭燕", "山亭柳","两同心","泠青沼","山月","秋日薄暮","摘星"}; String[] cttTime = {"下午4:32","下午3:10","上午11:20","上午8:21","昨天","昨天","3月21日","2021年12月10日"}; String[] cttMsg = {"须知少时凌云志","加油","怎么回事?","可以可以","哈哈","也许不过如此","牛的","万丈高楼平地起"}; for (int i = 0; i < imgSrc.length; i++) { Map<String, Object> itemData = new HashMap<String, Object>(); itemData.put("key1", imgSrc[i]); itemData.put("key2", cttName[i]); itemData.put("key3",cttTime[i]); itemData.put("key4",cttMsg[i]); data.add(itemData); } }
3. MyAdapter
编写RecyclerView的适配器MyAdapter,继承自RecyclerView.Adapter.其中实现了联系人信息的获取,以及展开栏的点击事件。其中头像是通过Glide加载的网络图片。
代码如下:
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.LinearLayout; import android.widget.TextView; import androidx.annotation.NonNull; import androidx.recyclerview.widget.RecyclerView; import com.bumptech.glide.Glide; import java.util.List; import java.util.Map; public class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyViewHolder>{ private Context context; private List<Map<String,Object>> data; public MyAdapter(Context context,List<Map<String,Object>> data){ this.context = context; this.data = data; } @NonNull @Override public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { View view = LayoutInflater.from(context).inflate(R.layout.item,parent,false); MyViewHolder holder = new MyViewHolder(view); return holder; } @Override public void onBindViewHolder(@NonNull MyViewHolder holder, int position) { Glide.with(context) .load(data.get(position).get("key1").toString()) .into(holder.imageView1); holder.textView5.setText(data.get(position).get("key2").toString()); holder.textView6.setText(data.get(position).get("key3").toString()); holder.textView7.setText(data.get(position).get("key4").toString()); holder.detail_text.setText(data.get(position).get("key4").toString()); holder.Clicked.setOnClickListener(onClickListener); } View.OnClickListener onClickListener = new View.OnClickListener() { @Override public void onClick(View view) { LinearLayout detail = view.findViewById(R.id.detail); TextView cctMsg = view.findViewById(R.id.cctMsg); if(detail.getVisibility()==View.VISIBLE) { detail.setVisibility(View.GONE); } else { detail.setVisibility(View.VISIBLE); } if(cctMsg.getVisibility()==View.VISIBLE) { cctMsg.setVisibility(View.INVISIBLE); } else { cctMsg.setVisibility(View.VISIBLE); } } }; @Override public int getItemCount() { return data.size(); } public class MyViewHolder extends RecyclerView.ViewHolder{ ImageView imageView1; TextView textView5; TextView textView6; TextView textView7; LinearLayout Clicked; TextView detail_text; public MyViewHolder(@NonNull View itemView) { super(itemView); imageView1 = itemView.findViewById(R.id.photo); textView5 = itemView.findViewById(R.id.cctName); textView6 = itemView.findViewById(R.id.time); textView7 = itemView.findViewById(R.id.cctMsg); Clicked = itemView.findViewById(R.id.Clicked); detail_text = itemView.findViewById(R.id.detail_text); } } }
修改初始weixinFragment,写入RecyclerView初始化方法initRecyclerView()
代码如下:
private void initRecyclerView () { recyclerView = view.findViewById(R.id.recyclerView); recyclerView.getItemAnimator().setChangeDuration(500); recyclerView.getItemAnimator().setMoveDuration(500); myAdapter = new MyAdapter(getActivity(), data); LinearLayoutManager manager = new LinearLayoutManager(getActivity()); manager.setOrientation(RecyclerView.VERTICAL); recyclerView.setAdapter(myAdapter); recyclerView.setLayoutManager(manager); }
4. 其他Java文件
这里还有剩下的三个页面,分别是联系人、朋友圈、我,对应着contacts.java、circleoffriend.java、setting.java文件。
这三个文件重写了Fragment的onCreateView方法,在该方法中初始化Fragment的视图,并返回该视图。它分别实现了在初始化时加载了一个XML布局文件作为其视图。
代码如下:
package com.example.myapplication; import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import androidx.fragment.app.Fragment; public class contacts extends Fragment { public contacts() { } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { // return super.onCreateView(inflater, container, savedInstanceState); return inflater.inflate(R.layout.contracts,container,false); } }
四、运行界面展示
中间的内容,我并没选择使用文本框,而是自己实际微信界面的截图
按住鼠标下拉实现消息列表下拉翻看
ps:
1. 运行时如果出现报错Cannot resolve symbol 'bumptech' ,Cannot resolve symbol 'Glide'
这个错误通常表示缺少Glide库的依赖或导入问题。
解决这个问题可以尝试以下方法:
检查Gradle文件:在你的项目的根目录下的build.gradle文件中,确保你已经正确添加了Glide库的依赖。例如,在dependencies部分添加以下代码:
implementation("com.github.bumptech.glide:glide:4.0.0") annotationProcessor("com.github.bumptech.glide:compiler:4.0.0")
2. 运行时如果出现消息列表头像图片无法加载问题,可能是以下几个原因导致:
(1)布局文件问题:请确保fragment_weixin.xml布局文件中包含一个RecyclerView控件,并且其id为"recyclerView",并且添加网络权限,可以将网络权限添加到AndroidManifest.xml文件中的<manifest>标签内。代码如下:
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"> <uses-permission android:name="android.permission.INTERNET"<!-- ... 其他代码 ... -->
</manifest>
(2)图片链接错误:请确认你使用的图片链接是否可用,可以尝试在浏览器中直接打开图片链接来验证。如果图片链接无效或过期,则无法加载图片。
(3)图片加载框架:请确保你已经在项目中添加相应的库,并在加载图片时使用相应的方法。
五、源码
详细代码已上传至github:BKINGING/ASWeChat: First Android job (github.com)