【Android】使用ViewPager2与TabLayout实现顶部导航栏+页面切换

【Android】使用ViewPager2与TabLayout实现顶部导航栏+页面切换

TabLayout与ViewPager2概述

TabLayout

image-20240726195519885

TabLayout 是 Android 支持库中的一个组件,它是 Design 支持库的一部分。TabLayout 提供了一个水平的标签页界面,允许用户在不同的视图或数据集之间进行切换。以下是 TabLayout 的一些主要特性和使用方法

主要特性

  1. 标签页模式TabLayout 可以显示多个标签页,每个标签页可以包含文本、图标或两者兼有。
  2. 动态标签页:可以动态地添加、删除或重新排序标签页。
  3. ViewPager 结合使用TabLayout 可以与 ViewPager 无缝集成,使得用户在滑动 ViewPager 的页面时,TabLayout 的选中标签页也会相应地变化。
  4. 自定义标签页:可以自定义标签页的布局、样式和行为。
  5. 滚动标签页:当标签页数量超过屏幕宽度时,它们可以水平滚动。

官方文档:

TabLayout | Android Developers (google.cn)

ViewPager2

ViewPager2 是 Android Jetpack 库中的一个组件,它是 ViewPager 的一个改进版本,提供了更好的性能和更多的功能。ViewPager2 允许用户左右滑动来浏览不同的视图,类似于一个滑动页面的控件。

主要特性

  1. 更好的性能ViewPager2 优化了滑动性能,特别是在处理大量页面或复杂视图时。
  2. 垂直和水平滑动:支持水平和垂直滑动,而 ViewPager 仅支持水平滑动。
  3. 动态添加和删除页面:可以动态地添加、删除或重新排序页面。
  4. TabLayout 集成:可以与 TabLayout 集成,实现标签页和页面视图的联动。
  5. 自定义适配器:支持自定义适配器,可以灵活地控制页面内容的显示。
  6. 滑动事件监听:提供滑动事件的监听,可以获取用户滑动的详细信息。

官方文档:

ViewPager2 | Jetpack | Android Developers (google.cn)

使用 ViewPager2 创建包含标签的滑动视图 | Android Developers (google.cn)

示例展示

6b75dd083172f70a8732 -small-original

本篇博客,笔者将带大家实现这样一个标题栏+Fragment水平切换页面的案例。

步骤详解

创建Activity并加入TabLayout和ViewPager2组件

创建Activity的步骤不再赘述,这里放上写好的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"tools:context=".MainActivity"android:orientation="vertical"style="@style/LayoutStyle"><com.google.android.material.tabs.TabLayoutandroid:id="@+id/tabLayout"android:layout_width="match_parent"android:layout_height="wrap_content"style="@style/TabStyle"/><androidx.viewpager2.widget.ViewPager2android:id="@+id/viewPager"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="horizontal" /></LinearLayout>

顺便展示一下Tab的样式配置:

    <style name="TabStyle"><item name="tabGravity">fill</item><item name="tabIndicatorFullWidth">false</item><item name="tabIndicatorAnimationMode">elastic</item><item name="tabMode">fixed</item><item name="tabUnboundedRipple">false</item><item name="tabBackground">@color/white</item><item name="tabTextAppearance">@style/TabTextStyle</item><item name="tabIndicator">@drawable/shape_tab_indicator</item><item name="tabIndicatorColor">@color/text_change_color</item><item name="tabTextColor">@color/black</item><item name="tabSelectedTextColor">@color/text_change_color</item></style>
  • tabGravity: 这个属性设置Tab的对齐方式,fill表示Tab将填充整个TabLayout的宽度。
  • tabIndicatorFullWidth: 设置指示器是否占据整个Tab的宽度,false表示指示器不会占据整个Tab的宽度。
  • tabIndicatorAnimationMode: 指示器动画模式,elastic表示弹性动画效果。
  • tabMode: 设置Tab的模式,fixed表示Tab的数量是固定的。
  • tabUnboundedRipple: 是否允许未绑定的涟漪效果,false表示不允许。
  • tabBackground: 设置Tab的背景颜色,这里使用了颜色资源@color/white
  • tabTextAppearance: 设置Tab文本的样式,这里引用了一个样式资源@style/TabTextStyle
  • tabIndicator: 设置Tab指示器的形状,这里引用了一个可绘制资源@drawable/shape_tab_indicator
  • tabIndicatorColor: 设置Tab指示器的颜色,使用了颜色资源@color/text_change_color
  • tabTextColor: 设置Tab未选中时的文本颜色,使用了颜色资源@color/black
  • tabSelectedTextColor: 设置Tab选中时的文本颜色,同样使用了颜色资源@color/text_change_color

仅作样式展示,大家喜欢的话可以直接cv走,如果想要自定义可以自行去网络上搜索配置属性,或者是查看源码。

创建需要的Fragments

下一步,我们创建需要的Fragments,这里仅演示一下标题栏的写法,故而Fragment就简单一些。

简简单单,一个Fragment。

<?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"tools:context=".TextFragment"><TextViewandroid:layout_width="match_parent"android:layout_height="match_parent"android:gravity="center"android:text="人尔 女子"android:textSize="40dp"/></LinearLayout>

大概长这样:

image-20240726201253079

创建FragmentAdapter(关联Fragment和ViewPager2)

接下来我们来写FragmentAdapter。

首先创建一个类,使他继承FragmentStateAdapter

FragmentStateAdapter 是 Android 开发中的一部分,它是一个适配器类,用于管理与 Fragment 相关的数据集合。FragmentStateAdapter 继承自 FragmentActivity.CallbacksFragmentManager.FragmentLifecycleCallbacks,因此它可以接收到 Fragment 生命周期的回调,并根据数据的变化来管理 Fragment 的状态。

FragmentStateAdapter 通常与 ViewPager2 一起使用,为 ViewPager2 提供 Fragment。

image-20240726201541472

刚继承完就爆红了,不要慌,alt+enter重写几个方法。

FragmentStateAdapter要求我们重写createFragment方法和getItemCount方法,

  1. createFragment(int position):
    • 这个方法用于创建并返回一个 Fragment 对象,对应于 ViewPager2 中的每个页面。
    • position 参数表示当前页面的位置(索引),从0开始。
    • 你需要重写这个方法来提供具体的 Fragment 实例。
  2. getItemCount():
    • 这个方法返回 ViewPager2 中页面的数量。
    • 你需要重写这个方法来指定你的 ViewPager2 应该有多少个页面。

先创建一个管理fragment的list成员变量,用于管理fragment页面,然后填充几个函数即可,别忘了写一个新的构造方法。

代码如下:

public class FragmentAdapter extends FragmentStateAdapter {private List<Fragment> fragmentList;public FragmentAdapter(@NonNull FragmentActivity fragmentActivity, List<Fragment> fragmentList) {super(fragmentActivity);this.fragmentList = fragmentList;}@NonNull@Overridepublic Fragment createFragment(int position) {return fragmentList == null ? null : fragmentList.get(position);}@Overridepublic int getItemCount() {return fragmentList == null ? 0:fragmentList.size();}
}

为了防止空指针异常,我们在每次返回时,判断一次fragment是否为空。

将ViewPager2配置好

配置ViewPager2的步骤也不复杂,首先回到MainActivity中,把刚刚写好的适配器和用于管理的List列表写出来:

private FragmentAdapter fragmentAdapter;
private List<Fragment> fragmentList;

然后将适配器实例化,再设置给viewPager组件即可,完整代码如下:

public class MainActivity extends AppCompatActivity {private ActivityMainBinding binding;private FragmentAdapter fragmentAdapter;private List<Fragment> fragmentList;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);EdgeToEdge.enable(this);binding = ActivityMainBinding.inflate(getLayoutInflater());setContentView(binding.getRoot());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;});initData();fragmentAdapter = new FragmentAdapter(this,fragmentList);binding.viewPager.setAdapter(fragmentAdapter);}private void initData() {fragmentList = new ArrayList<>();fragmentList.add(new TextFragment());fragmentList.add(new TextFragment());fragmentList.add(new TextFragment());}
}

省去实例化Fragment的过程,仅仅两行代码就完成配置了,真是便便又捷捷呀。

我们不妨点进去这个ViewPager2的setAdapter方法,看看他都干了些什么。在网上搜索ViewPager2和ViewPager的区别的时候,总能看到一句话:两者最大的区别就是可以直接把ViewPager2看成RecyclerView,先放下这些疑惑,我们点进去看看:

image-20240726203454838

点进去第一眼就是这个非常显眼的RecyclerView,真是封封又装装啊。

来都来了,我们看看这个方法都做了些什么。

    public void setAdapter(@Nullable @SuppressWarnings("rawtypes") Adapter adapter) {final Adapter<?> currentAdapter = mRecyclerView.getAdapter();mAccessibilityProvider.onDetachAdapter(currentAdapter);unregisterCurrentItemDataSetTracker(currentAdapter);mRecyclerView.setAdapter(adapter);mCurrentItem = 0;restorePendingState();mAccessibilityProvider.onAttachAdapter(adapter);registerCurrentItemDataSetTracker(adapter);}

笔者大概查询了一下,这段代码首先用

final Adapter<?> currentAdapter = mRecyclerView.getAdapter();

这段代码获取当前RecyclerView的适配器,并将其存储在currentAdapter变量中。

mAccessibilityProvider.onDetachAdapter(currentAdapter);

unregisterCurrentItemDataSetTracker(currentAdapter);

这段代码通知辅助功能提供者(Accessibility Provider)当前适配器已经被分离(detached)。这是为了在适配器更换时更新辅助功能的状态,而后取消注册当前适配器的数据集变化跟踪器。这通常是在适配器更换时进行的,以确保旧的适配器不再被跟踪。

mRecyclerView.setAdapter(adapter);

这行代码将一个新的适配器adapter设置给RecyclerView。这是实际更换适配器的操作。

restorePendingState();

这行代码尝试恢复挂起的状态。这可能是一个自定义方法,用于在适配器更换后恢复之前的状态,例如恢复滚动位置等。

mAccessibilityProvider.onAttachAdapter(adapter);

这行代码通知辅助功能提供者新的适配器已经被附加(attached)。这是为了确保辅助功能能够正确地与新的适配器交互。

registerCurrentItemDataSetTracker(adapter);

这行代码注册新的适配器的数据集变化跟踪器。这允许RecyclerView监听数据集的变化,并在变化发生时进行适当的更新。

大概总结一下,这段代码的作用就是更加安全地为内置的RecyclerView更换了适配器,并且确保一系列的辅助功能能正确的运行与更新。

看都看了,不妨再看一点:

image-20240726205134808

翻着翻着发现ViewPager2的构造方法都要用到一个initalize方法,我们看看这个initalize方法都干了什么:

image-20240726205308500

非常长一大串,不过没关系,我们忽略其中的大部分内容。看到这句话:

image-20240726205419932

这两段想必并不陌生,这是创建RecyclerView视图时必不可缺的两句话,第一句用于创建Recycler实例,第二句用于创建一个线性布局管理器,而后将管理器设置到RecyclerView。

image-20240726205556239

最后的这句代码也能望文生义,也就是将刚刚创建的RecyclerView附加到父视图上。

以上就是对ViewPager2的简单介绍了,希望对大家理解这个组件能有一些帮助。

关联TabLayout

在Activity的onCreate方法中增加一句话即可:

new TabLayoutMediator(binding.tabLayout, binding.viewPager, new TabLayoutMediator.TabConfigurationStrategy() {@Overridepublic void onConfigureTab(@NonNull TabLayout.Tab tab, int i) {tab.setText("nihao");}
}).attach();

这段代码是Android开发中用于连接TabLayoutViewPager2(或ViewPager)的TabLayoutMediator类的使用示例。TabLayoutMediator是一个实用工具类,它帮助开发者将TabLayout的标签与ViewPagerViewPager2的页面同步。以下是代码的详细分析:

创建TabLayoutMediator实例:

new TabLayoutMediator(binding.tabLayout, binding.viewPager, …)

这行代码创建了一个新的TabLayoutMediator对象。它接收三个参数:

  • binding.tabLayout: 一个TabLayout实例,表示包含标签的组件。
  • binding.viewPager: 一个ViewPagerViewPager2实例,表示用户可以左右滑动浏览的组件。
  • 一个实现了TabLayoutMediator.TabConfigurationStrategy接口的匿名类实例,这个接口定义了如何配置每个标签。

配置标签:

new TabLayoutMediator.TabConfigurationStrategy() {…}

这是一个匿名内部类,实现了TabLayoutMediator.TabConfigurationStrategy接口。这个接口包含一个方法onConfigureTab,用于配置每个标签。

@Override public void onConfigureTab(@NonNull TabLayout.Tab tab, int i) { … }

这是onConfigureTab方法的重写,它接收两个参数:

  • @NonNull TabLayout.Tab tab: 当前需要配置的Tab对象。
  • int i: 表示当前标签在TabLayout中的位置(索引)。

连接TabLayoutViewPager:

.attach();

  • 这行代码调用TabLayoutMediator实例的attach方法,它的作用是将TabLayoutViewPager连接起来。一旦连接,当ViewPager的页面发生变化时,TabLayout会相应地更新当前选中的标签。同样,当用户点击某个标签时,ViewPager会滚动到对应的页面。

笔者不小心手滑,刚写完代码就点进去了一个陌生的页面。

image-20240726210908910

原来是attach()方法的实现原理,正巧笔者也对TabLayout如何连接两个组件非常好奇,不妨来看一看。

image-20240726211059830

首先这个方法会检查Mediator是否连接到了viewPager,而后会检查viewPager的适配器是否存在,保障其安全性。

一切准备就绪后,将attached的状态设置为true,接下来就是激动人心的逻辑环节了:

image-20240726211240514

创建页面变化回调:

  • this.onPageChangeCallback = new TabLayoutOnPageChangeCallback(this.tabLayout);
    
    • 创建一个 TabLayoutOnPageChangeCallback 实例,用于处理 ViewPager2 页面变化事件,并更新 TabLayout

注册页面变化回调:

  • this.viewPager.registerOnPageChangeCallback(this.onPageChangeCallback);
    
    • 将创建的页面变化回调注册到 ViewPager2

创建标签选择监听器:

  • this.onTabSelectedListener = new ViewPagerOnTabSelectedListener(this.viewPager, this.smoothScroll);
    

    创建一个 ViewPagerOnTabSelectedListener 实例,用于处理 TabLayout 标签选择事件,并更新 ViewPager2

添加标签选择监听器:

  • this.tabLayout.addOnTabSelectedListener(this.onTabSelectedListener);
    

    将创建的标签选择监听器添加到 TabLayout

设置标签滚动位置:

  • this.tabLayout.setScrollPosition(this.viewPager.getCurrentItem(), 0.0F, true);
  • 设置 TabLayout 中当前选中标签的滚动位置,确保用户可以看到当前页面对应的标签。

中间有一段实在不知道干什么用的,就留给读者去解决啦~

结语

本文参考:

【Android】ViewPager2和TabLayout协同使用,实现多Fragment页面切换类似于QQ音乐,bilibili效果_tablayout和viewpager2-CSDN博客

【Android】ViewPager2监听页面切换事件_viewpager2 监听-CSDN博客

安卓:TabLayout+ViewPager2+Fragment使用(java)_tablayout viewpager2 fragment-CSDN博客

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/49512.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

解决Todesk远程连接没有接显示屏的服务器,出现黑屏现象

解决办法就是虚拟出来一个显示屏 通过终端安装虚拟显示器软件。 $ sudo apt-get install xserver-xorg-core-hwe-18.04 $ sudo apt-get install xserver-xorg-video-dummyubuntu20.04、 ubuntu22.04都适用 2. 添加配置文件 sudo vim /usr/share/X11/xorg.conf.d/xorg.conf…

TensorFlow 可用的植物分类模型有哪些,各有什么优缺点

在树莓派上运行CentOS 7,并使用TensorFlow Java版本实现植物分类功能可以通过以下步骤实现。以下是详细的指导: 一、安装和设置环境 1. 更新系统并安装基本工具 确保你的CentOS 7系统是最新的,并安装必要的工具: sudo yum update -y sudo yum install -y wget unzip gi…

CogVideo 实测,智谱「清影」AI视频生成,全民免费,连 API 都开放了!

不得不说&#xff0c;AI 视频生成界最近非常火热~ 前有快手「可灵」开放内测&#xff0c;一下子带火了老照片修复&#xff0c;全网刷屏&#xff1a; 怕是你还没拿到内测资格&#xff0c;被称为 “国货之光” 的「可灵」就结束了免费无限量模式。每天只有66点的免费额度&#x…

鸿蒙(API 12 Beta2版)【创建NDK工程】

创建NDK工程 下面通过DevEco Studio的NDK工程模板&#xff0c;来演示如何创建一个NDK工程。 说明 不同DevEco Studio版本的向导界面、模板默认参数等会有所不同&#xff0c;请根据实际工程需要&#xff0c;创建工程或修改工程参数。 通过如下两种方式&#xff0c;打开工程创…

2024-07-27 Unity Excel —— 使用 EPPlus 插件读取 Excel 文件

文章目录 1 前言2 项目地址3 使用方法3.1 写入 Excel3.2 读取 Excel3.3 读写 csv 文件 4 ExcelSheet 代码 1 前言 ​ 前几日&#xff0c;一直被如何在 Unity 中读取 Excel 的问题给困扰&#xff0c;网上搜索相关教程相对古老&#xff08;4、5 年以前了&#xff09;。之前想用 …

2024年最全网易大数据面试题及参考答案(3万字长文持续更新)

目录 如何评价新用户的留存指标有哪些? 游戏业务中有哪些常用指标? 怎么制定游戏业务的目标 游戏业务中哪三个业务最重要 数据分析指标的阈值怎么确定 怎么衡量你在业务部门的贡献 如何衡量一个活动的ROI 跟领导汇报游戏业务,你会选择哪5个指标,为什么 介绍一下Ha…

【openavis】明厨亮灶算法仓

明厨亮灶算法仓主要用于学校食堂&#xff0c;餐厅等饮食卫生安全监管场景&#xff0c;目前包含的算法如下&#xff1a; 算法类型 算法卡片 明厨亮灶算法仓 老鼠检测 垃圾桶未盖 厨师服检测 厨师帽检测 口罩检测 手套检测 动火离人 1. 算法规格介绍&#xff1a; 算…

基于STM32瑞士军刀--【FreeRTOS开发】学习笔记(一)|| RISC / 底层代码执行步骤 / 汇编指令

本篇文章基于韦东山老师讲课笔记和自己理解编写。 RISC ARM芯片属于精简指令集计算机(RISC&#xff1a;Reduced Instruction Set Computing)&#xff0c;它所用的指令比较简单&#xff0c;有如下特点&#xff1a; ① 对内存只有读、写指令 ② 对于数据的运算是在CPU内部实现 …

【MySQL进阶之路 | 高级篇】MVCC解决读写问题

1. 什么是MVCC MVCC (Multiversion Concurrency Control)&#xff0c;多版本并发控制。顾名思义&#xff0c;MVCC是通过数据行的多个版本管理来实现数据库的并发控制。这项技术使得在InnoDB的事务隔离级别下执行一致性读操作有了保证。换言之&#xff0c;就是为了查询一些正在…

你在找提升效率的解决方案还是追求效果的解决方案

企业在寻求“解决方案”时&#xff0c;最好先想清楚&#xff0c;你是想提升某项工作的效率&#xff0c;还是要改善某项工作的效果&#xff1f; 提升效率的解决方案主要是为了在保证质量的前提下提升某项确定工作的完成速度。以政务解决方案为例&#xff1a;当任何人都能通过移…

[C++] 小游戏 斗破苍穹2.12.2版本 zty出品

大家好&#xff0c;今天zty带来的是斗破苍穹的 2.12.2 版本&#xff0c;这个版本改进了许多皇冠竞技场的bug&#xff0c; 和一些文字仅存在一瞬间便消失了&#xff0c;废话不多说&#xff0c;请看code 先赞后看 养成习惯 CODE #include<stdio.h> #include<iostrea…

因即果,果即因

“有因才有果”、“先有因&#xff0c;后有果”&#xff0c;是人们通常的认知。 事实上有时可以理解为先有果再有因&#xff0c;为了某个果而造了某个因。 时间的方向可以理解为双向的——事情先发生了&#xff0c;然后给一个解释。 例如&#xff0c;某个人为了实现某个目标…

HAL STM32 SPI/ABZ/PWM方式读取MT6816磁编码器数据

HAL STM32 SPI/ABZ/PWM方式读取MT6816磁编码器数据 &#x1f4da;MT6816相关资料&#xff08;来自商家的相关资料&#xff09;&#xff1a; 资料&#xff1a;https://pan.baidu.com/s/1CAbdLBRi2dmL4D7cFve1XA?pwd8888 提取码&#xff1a;8888&#x1f4cd;驱动代码编写&…

某量JS逆向

https://chat.sensetime.com/wb/chat 目录 一、发起请求 二、观察发现只有入参 __data__ 进行了加密&#xff0c;返回是明文 三、 观察JS调用栈 四、从JS中搜索 __data__ 五、使用XHR对Ajax请求进行断点 六、再次发起请求就会断点拦住请求 七、对XHR入口分析 八、逐个…

【C++】选择结构- 嵌套if语句

嵌套if语句的语法格式&#xff1a; if(条件1) { if(条件1满足后判断是否满足此条件) {条件2满足后执行的操作} else {条件2不满足执行的操作} } 下面是一个实例 #include<iostream> using namespace std;int main4() {/*提示用户输入一个高考分数&#xff0c;根据分…

学习C语言第12天(数组练习)

1.走台阶问题 设一个函数fit(n)是求n阶台阶有几种走法 第一步迈1个台阶 那有fit(n-1)种走法 第一步迈连个台阶 有fit(n-2)种走法 所以n阶台阶一共有fit(n-1)fit(n-2)种走法 斐波那契数列int fit(int n) {if (n < 2)return n;elsereturn fit(n - 1) fit(n - 2); } in…

花几千上万学习Java,真没必要!(二十九)

1、基本数据类型包装类&#xff1a; 测试代码1&#xff1a; package apitest.com; //使用Integer类的不同方法处理整数。 //将字符串转换为整数&#xff08;parseInt&#xff09;和Integer对象&#xff08;valueOf&#xff09;&#xff0c; //将整数转换回字符串&#xff08;…

LeetCode19 删除链表的倒数第N个结点

前言 题目&#xff1a; 19. 删除链表的倒数第N个结点 文档&#xff1a; 代码随想录——删除链表的倒数第N个结点 编程语言&#xff1a; C 解题状态&#xff1a; 成功解答&#xff01; 思路 最直接的想法就是先获取到链表的整体长度&#xff0c;减去倒数的个数&#xff0c;正向…

【计算机网络】DNS命令练习与抓包分析实验

一&#xff1a;实验目的 1&#xff1a;掌握DNS缓存的清除方法&#xff0c;了解DNS缓存的作用和影响。 2&#xff1a;熟悉nslookup和dig等DNS查询工具的使用&#xff0c;理解DNS查询的基本原理和过程。 3&#xff1a;通过抓包和分析&#xff0c;深入了解DNS查询和响应消息的格…

html+css 实现悬浮按钮

前言&#xff1a;哈喽&#xff0c;大家好&#xff0c;今天给大家分享htmlcss 绚丽效果&#xff01;并提供具体代码帮助大家深入理解&#xff0c;彻底掌握&#xff01;创作不易&#xff0c;如果能帮助到大家或者给大家一些灵感和启发&#xff0c;欢迎收藏关注哦 &#x1f495; 文…