官方Android 12的
Splash Screen文档地址
。
https://developer.android.com/guide/topics/ui/splash-screen
官方Splash Screen兼容库
,支持所有版本系统。
https://developer.android.com/guide/topics/ui/splash-screen/migrate
本篇文章主要围绕下面三个问题来介绍:
1、我们能从Android 12 SplashScreen API里面学到什么?
2、新出的SplashScreen兼容库又是什么?能做成什么样子?
3、小甲同学:我想看Android12 SplashScreen源码,可以吗?
进入正题,我们先介绍:SplashScreen如何使用,以及目前会遇到的问题,如何无缝过渡?
会出现什么问题,怎么解决?
SplashScreen使用
首先我们需要把compileSdk和targetSdk(可选)升级到31。
2.1.Android12版本
(A).主题和外观配置
<!--文章末尾我们会把包含所有示例的链接地址提供出来,如有需要:请翻到文章末尾-->
<!-- values-v31/themes.xml -->
<!--单一颜色填充「启动画面」窗口背景-->
<
item
name
=
"android:windowSplashScreenBackground"
>
@color/...
</
item
>
<!--「启动画面」中心的图标,
可以配置AnimationDrawable 和 AnimatedVectorDrawable类型的drawable-->
<
item
name
=
"android:windowSplashScreenAnimatedIcon"
>
@drawable/...
</
item
>
<!--「启动画面」中心图标动画的持续时间,这个属性不会对屏幕显示的实际时间产生任何影响-->
<
item
name
=
"android:windowSplashScreenAnimationDuration"
>
1000
</
item
>
<!--「启动画面」中心图标后面设置背景-->
<
item
name
=
"android:windowSplashScreenIconBackgroundColor"
>
@color/...
</
item
>
<!--「启动画面」底部显示的品牌图标-->
<
item
name
=
"android:windowSplashScreenBrandingImage"
>
@drawable/...
</
item
>
(B).延长启动画面
val
content: View = findViewById(android.R.id.content)
content.viewTreeObserver.addOnPreDrawListener(
object
: ViewTreeObserver.OnPreDrawListener {
override
fun
onPreDraw
:
Boolean
{
// 模拟一些数据的初始化,再取消挂起
return
if
(viewModel.isReady) {
// 取消挂起,恢复页面内容绘制
content.viewTreeObserver.removeOnPreDrawListener(
this
)
}
else
{
// 挂起,内容还没有准备好
false
(C).关闭启画面的动画
// 自己定制关闭的动画
splashScreen.setOnExitAnimationListener { splashScreenView ->
val slideUp = ObjectAnimator.ofFloat(
// 你们自己控制,自己随便写什么动画,这里我们测试让图标移动
splashScreenView.iconView,
View.TRANSLATION_Y,
-splashScreenView.height.toFloat
slideUp.interpolator = AnticipateInterpolator
slideUp.duration =
200
L
slideUp.doOnEnd { splashScreenView.
remove
}
slideUp.start
(D).遇到的问题
1、
android:windowSplashScreenBrandingImage
定义的图片尺寸要求是多少?总觉得有点拉伸;
2、使用
AnimationDrawable
或者
AnimatedVectorDrawable
,来设置中心图标,会出现“中心图标”消失的情况,静态图标不会有这种问题出现;
3、Android12父主题设置
android:windowBackground
被覆盖,看不到效果。
问题1
: 在源码里面也没有看到具体的值或者比例大小,怎么办呢?
小技巧: 使用一个超大的正方形的图标设置进去测试了一下,拉伸不要紧,我们要的是比例, 然后测量了一下比例为:2.5 : 1,所以设计品牌名图标的时候,可以设置为400 * 160这样的比例为2.5:1的图标。
问题2
: 针对中心图标会闪现消失的问题做测试,
测试一:静态Icon、测试二:动态Icon
静态中心图标 - 正常
下面我们来测试动态中心图标,为了方便测试出效果,我们覆盖住图标后面的背景色,方便对比,最后发现:测试结果不太理想,效果不行。
<!--AnimationDrawable写法-->
<!--没有真机测试,这个写法,效果看起来也挺奇怪的,可能是模拟器且是预览版的问题吧-->
<
animation-list
xmlns:android
=
"http://schemas.android.com/apk/res/android"
android:oneshot
=
"true"
>
<
item
android:drawable
=
"@mipmap/ic_launcher"
android:duration
=
"600"
/>
<
item
android:drawable
=
"@drawable/api12_logo"
android:duration
=
"200"
/>
<
item
android:drawable
=
"@mipmap/ic_launcher"
android:duration
=
"200"
/>
</
animation-list
>
动态中心图标,不正常
仔细看图标后面的「蓝色背景」
我们再来看一下官方文档中的顺滑效果。
官方效果顺滑
对比官方的效果,猜测可能是模拟器和预览版Android12的问题,主要是没有真机来测试Android12这个效果,不过这难不到我们,如果你的模拟器也有同样问题,请跟着我们做如下操作:
下面我们使用
AnimatedVectorDrawable
来制作动态图标,为了观察出效果:我们打开模拟器的开发者选项,找到Animator时长缩放设置为:动画时长x10,来往下看效果:
10倍慢放 - 看着才正常
笑脸眼睛动画的矢量图文件 ,点击查看
在线制作矢量图动画
。
https://shapeshifter.design/
<!--仅测试玩耍,感兴趣的可以自己去制作一个-->
<?xml version="1.0" encoding="utf-8"?>
<
animated-vector
xmlns:android
=
"http://schemas.android.com/apk/res/android"
xmlns:aapt
=
"http://schemas.android.com/aapt"
>
<
aapt:attr
name
=
"android:drawable"
>
<
vector
android:name
=
"vector"
android:width
=
"24dp"
android:height
=
"24dp"
android:viewportWidth
=
"24"
android:viewportHeight
=
"24"
>
<
group
android:name
=
"group"
>
android:name
=
"path_4"
android:pathData
=
"M 11.99 2 C 6.47 2 2 6.48 2 12 C 2 17.52 6.47 22 11.99 22 C 17.52 22 22 17.52 22 12 C 22 6.48 17.52 2 11.99 2 Z M 12 20 C 7.58 20 4 16.42 4 12 C 4 7.58 7.58 4 12 4 C 16.42 4 20 7.58 20 12 C 20 16.42 16.42 20 12 20 Z M 12 17.5 C 14.33 17.5 16.32 16.05 17.12 14 L 15.45 14 C 14.76 15.19 13.48 16 12 16 C 10.52 16 9.25 15.19 8.55 14 L 6.88 14 C 7.68 16.05 9.67 17.5 12 17.5 Z"
android:fillColor
=
"#FFFFFF"
/>
</
group
>
<
group
android:name
=
"group_1"
>
android:name
=
"path_1"
android:pathData
=
"M 8.5 9.5 M 7 9.5 C 7 9.102 7.158 8.721 7.439 8.439 C 7.721 8.158 8.102 8 8.5 8 C 8.898 8 9.279 8.158 9.561 8.439 C 9.842 8.721 10 9.102 10 9.5 C 10 9.898 9.842 10.279 9.561 10.561 C 9.279 10.842 8.898 11 8.5 11 C 8.102 11 7.721 10.842 7.439 10.561 C 7.158 10.279 7 9.898 7 9.5"
android:fillColor
=
"#FFFFFF"
/>
android:name
=
"path_3"
android:pathData
=
"M 8.5 9.5 M 7 9.5 C 7 9.102 7.158 8.721 7.439 8.439 C 7.721 8.158 8.102 8 8.5 8 C 8.898 8 9.279 8.158 9.561 8.439 C 9.842 8.721 10 9.102 10 9.5 C 10 9.898 9.842 10.279 9.561 10.561 C 9.279 10.842 8.898 11 8.5 11 C 8.102 11 7.721 10.842 7.439 10.561 C 7.158 10.279 7 9.898 7 9.5"
android:fillColor
=
"#FFFFFF"
/>
</
group
>
<
group
android:name
=
"group_2"
>
android:name
=
"path"
android:pathData
=
"M 15.5 9.5 M 14 9.5 C 14 9.102 14.158 8.721 14.439 8.439 C 14.721 8.158 15.102 8 15.5 8 C 15.898 8 16.279 8.158 16.561 8.439 C 16.842 8.721 17 9.102 17 9.5 C 17 9.898 16.842 10.279 16.561 10.561 C 16.279 10.842 15.898 11 15.5 11 C 15.102 11 14.721 10.842 14.439 10.561 C 14.158 10.279 14 9.898 14 9.5"
android:fillColor
=
"#FFFFFF"
/>
android:name
=
"path_2"
android:pathData
=
"M 15.5 9.5 M 14 9.5 C 14 9.102 14.158 8.721 14.439 8.439 C 14.721 8.158 15.102 8 15.5 8 C 15.898 8 16.279 8.158 16.561 8.439 C 16.842 8.721 17 9.102 17 9.5 C 17 9.898 16.842 10.279 16.561 10.561 C 16.279 10.842 15.898 11 15.5 11 C 15.102 11 14.721 10.842 14.439 10.561 C 14.158 10.279 14 9.898 14 9.5"
android:fillColor
=
"#FFFFFF"
/>
</
group
>
</
vector
>
</
aapt:attr
>
<
target
android:name
=
"group_1"
>
<
aapt:attr
name
=
"android:animation"
>
<
objectAnimator
android:propertyName
=
"translateX"
android:duration
=
"1000"
android:valueFrom
=
"0"
android:valueTo
=
"7"
android:valueType
=
"floatType"
android:interpolator
=
"@android:interpolator/fast_out_slow_in"
/>
</
aapt:attr
>
</
target
>
<
target
android:name
=
"group_2"
>
<
aapt:attr
name
=
"android:animation"
>
<
objectAnimator
android:propertyName
=
"translateX"
android:duration
=
"1000"
android:valueFrom
=
"0"
android:valueTo
=
"-7"
android:valueType
=
"floatType"
android:interpolator
=
"@android:interpolator/fast_out_slow_in"
/>
</
aapt:attr
>
</
target
>
</
animated-vector
>
后来我们又用了
AnimationDrawable
测试了一下慢放效果也不行,你仔细想一下:图片轮播放效果能好吗?
所以:
AnimationDrawable
AnimatedVectorDrawable
为矢量图添加动画效果。
问题3
:Android12父主题设置
android:windowBackground
被覆盖,看不到效果。
不要紧,只要我们的UI设计师(美工)按照如下尺寸规范来设计,使用静态中心图标,一样可以实现同样效果:
中心图标
: 图标内容区域内边距2/3,防止元素被切。
品牌名图标
: 设计的尺寸比例为:2.5:1。
2.2.SplashScreen兼容库
点击查看
官方Splash Screen兼容库文档
。
https://developer.android.com/guide/topics/ui/splash-screen/migrate
(A).依赖库
点击查看
Core库里面的最新版本
。
https://developer.android.com/jetpack/androidx/releases/core
// 可在所有Android版本上使用的兼容库
implementation
'androidx.core:core-splashscreen:1.0.0-alpha02'
(B).主题和外观配置
定义Activity应该使用的主题
<
style
name
=
"Theme.App"
parent
=
"Theme.MaterialComponents.xxxxx.DarkActionBar"
>
<
item
name
=
"android:windowBackground"
>
@color/...
</
item
>
<
item
name
=
"android:statusBarColor"
>
@android:color/transparent
</
item
>
<
item
name
=
"android:windowLightStatusBar"
tools:targetApi
=
"m"
>
......
</
item
>
<
item
name
=
"android:navigationBarColor"
>
@android:color/transparent
</
item
>
</
style
>
创建父主题给启动画面使用
<
style
name
=
"Theme.App.Starting"
parent
=
"Theme.SplashScreen.IconBackground"
>
<
item
name
=
"android:windowBackground"
>
@drawable/...
</
item
>
<
item
name
=
"windowSplashScreenBackground"
>
@color/...
</
item
>
<
item
name
=
"windowSplashScreenAnimationDuration"
>
200
</
item
>
<
item
name
=
"postSplashScreenTheme"
>
@style/Theme.App
</
item
>
</
style
>
AndroidManifest.xml配置Activity的主题
<
manifest
>
<
application
android:theme
=
"@style/Theme.App.Starting"
>
<!-- application和activity,两个选一个配置: @style/Theme.App.Starting -->
<
activity
android:theme
=
"@style/Theme.App.Starting"
>
(C).初始化SplashScreen
override
fun
onCreate
(savedInstanceState:
Bundle
?)
{
super
.onCreate(savedInstanceState)
val
splashScreen = installSplashScreen
setContent { ...... }
splashScreen.setKeepVisibleCondition {
!mainViewModel.mockDataLoading
splashScreen.setOnExitAnimationListener(
this
)
(D).中心图标大小修改
<
item
name
=
"splashScreenIconSize"
>
@dimen/....
</
item
>
(E).遇到的问题
兼容库目前存在的问题:
1、没有
android:windowSplashScreenBrandingImage
这个属性。
2、配置了中心图标,会裁剪成圆形。
3、低版本系统不配置
windowSplashScreenAnimatedIcon
会出现默认的Icon。
4、Android12父主题设置
android:windowBackground
被覆盖,看不到效果。
问题1
: 是因为兼容库的layout文件目录下面的
splash_screen_view.xml
没有“品牌名的视图”,大家点击查看一下,两个布局的xml内容就知道了:
frameworks下面的
Android12的splash_screen_view.xml
https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/res/res/layout/splash_screen_view.xml
core-splashscreen下面的
兼容库的splash_screen_view.xml
https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:core/core-splashscreen/src/main/res/layout/splash_screen_view.xml
但是我们在Android12即values-v31的
themes.xml
里面依然可以配置
android:windowSplashScreenBrandingImage
这个属性,因为Android12的SplashScreen是集成在frameworks里面的。
问题2
: 是因为兼容库里面使用了
MaskedDrawable
包装了Icon,会裁剪成圆形,图标内容设计要保留2/3的内边距,否则会出现内容被裁剪掉的问题。
如何修复这个裁剪圆形问题呢?
把源码拷贝出来,总共就3个源代码文件,自己复制出来修改删除也可以的。或者,图标设计准则为:内容保留内边距为2/3,防止元素被裁剪。
问题3
: 写一个透明的
drawable.xml
然后替换就行了,类似如下方式:
<?xml version="1.0" encoding="utf-8"?>
<
shape
android:shape
=
"rectangle"
xmlns:android
=
"http://schemas.android.com/apk/res/android"
>
<
solid
android:color
=
"@android:color/transparent"
/>
<
size
android:width
=
"0dp"
android:height
=
"0dp"
/>
</
shape
>
问题4
:Android12父主题设置
android:windowBackground
被覆盖,看不到效果。
不要紧,只要我们的UI设计师(美工)按照如下尺寸规范来设计,使用静态中心图标,一样可以实现同样效果:
中心图标
: 图标内容区域内边距2/3,防止元素被切。
品牌名图标
: 设计的尺寸比例为:2.5:1。
(F).制作一个启动页
1.模仿快手App的启动页
只需要配置父主题的
android:windowBackground
。
Android5.0 ~ Android11 效果
由于我们在文章上面介绍到Android12上,无法为SplashScreen设置父主题的
android:windowBackground
,但我们依然可以通过配置静态中心图标来做到一样的效果的,请看下面的效果:
Android12 效果
如果你的UI设计师,给你矢量图,那么你就可以让中心图标在Android12系统上动起来了。
另外,可以建议UI设计师:统一所有系统上,启动页“中心”图标,居中展示,不然会有点怪。
2.动态图标启动页
如果设计成动态启动图标,这个需要考虑2个因素:
一、 低版本系统表现效果不一致,有些系统里面,动态图标只在启动页关闭的时候才显示(亲测Android平板5.1.1系统就是这样的)。
二、 如何兼容低版本系统,是先展示底部品牌名,最后只能等动态图标显示咯?
如果喜欢折腾的同学,可以多测试测试,我在使用Android10.0测试动态图标的时候,效果看着还可以,具体系统下限在哪?
这个看你们产品设计定位了,还有测试妹妹是否同意你们用,效果是否符合你们的产品。
建议的做法是:
1、Android 5.0 ~ Android 11.0系统,都统一使用
android:windowBackground
配置启动页背景。
2、Android12.0 如果UI设计师给你做了矢量图,你可以做动态的中心图标,不给你,使用静态图标也可以的,参考上面:模拟快手App启动页。
为了在模拟器上能正常显示出效果,我们在模拟器的开发者选项,找到Animator时长缩放设置为:动画时长x10,放慢10倍,缺真机测试。
Android12 动态启动页图标
我们这里只分析Android12 SplashScreen,兼容库没有太多内容不足500行,感兴趣的同学可以自己阅读一下。
我们在XXXActivity里面第一次用到了
splashScreen.setOnExitAnimationListener
,从这里开始往源头开始找,在下面的方法初始化了SplashScreen。
//android.app.Activity
private
SplashScreen
getOrCreateSplashScreen
{
synchronized
(
this
) {
if
(mSplashScreen ==
null
) {
mSplashScreen =
new
SplashScreen.SplashScreenImpl(
this
);
return
mSplashScreen;
我们来看
SplashScreenImpl
实现类。
//android.app.Activity
class
SplashScreenImpl
implements
SplashScreen
{
......
//把SplashScreenImpl添加到这个单例类里面
private
final
SplashScreenManagerGlobal mGlobal;
public
SplashScreenImpl
(Context context)
{
mGlobal = SplashScreenManagerGlobal.getInstance;
@Override
public
void
setOnExitAnimationListener
(@NonNull SplashScreen.OnExitAnimationListener listener)
{
......
mGlobal.addImpl(
this
);
// 用于后面执行启动画面将退出的回调
......
public
void
setSplashScreenTheme
(@StyleRes
int
themeId)
{
......
try
{
//设置启动画面的主题
AppGlobals.getPackageManager.setSplashScreenTheme(......);
}
catch
(RemoteException e) {
Log.w(TAG,
"Couldn't persist the starting theme"
, e);
// 启动画面管理器
class
SplashScreenManagerGlobal
{
......
// 管理多个闪屏实现
private
final
ArrayList<SplashScreenImpl> mImpls =
new
ArrayList<>;
private
SplashScreenManagerGlobal
{
// 向此进程注册启动画面管理器
ActivityThread.currentActivityThread.registerSplashScreenManager(
this
);
......
private
static
final
Singleton<SplashScreenManagerGlobal> sInstance =
new
Singleton<SplashScreenManagerGlobal> {
@Override
protected
SplashScreenManagerGlobal
create
{
return
new
SplashScreenManagerGlobal;
private
void
addImpl
(SplashScreenImpl impl)
{
synchronized
(mGlobalLock) {
mImpls.add(impl);
private
void
removeImpl
(SplashScreenImpl impl)
{
synchronized
(mGlobalLock) {
mImpls.remove(impl);
......
public
void
handOverSplashScreenView
(IBinder token,SplashScreenView splashScreenView)
{
//调用的是 => splashScreenView.transferSurface;
transferSurface(splashScreenView);
//回调 => impl.mExitAnimationListener.onSplashScreenExit(view);
dispatchOnExitAnimation(token, splashScreenView);
......
我们看到初始化
SplashScreenManagerGlobal
的时候,向此进程注册启动画面管理器。
//android.app.ActivityThread
public
void
registerSplashScreenManager
(SplashScreen.SplashScreenManagerGlobal manager)
{
synchronized
(
this
) {
mSplashScreenGlobal = manager;
如何把SplashScreen添加到当前的窗口的呢?
ActivityThread继承
ClientTransactionHandler
,里面有一个这样的抽象方法:
//android.app.ClientTransactionHandler
public
abstract
void
handleAttachSplashScreenView
(
@NonNull ActivityClientRecord r,
@NonNull SplashScreenViewParcelable parcelable ) ;
ActivityThread肯定会实现这个方法,那么是谁调用了它呢?由于篇幅问题,就不一行一行代码的去介绍分析了,感兴趣的同学,可以自己深入研究,我们下面贴出来调用的流程图。
谁调用了
handleAttachSplashScreenView
,它的调用链流程图如下:
好了,看完流程图,我们再看一下
ActivityThread#handleAttachSplashScreenView
。
//android.app.ActivityThread
@Override
public
void
handleAttachSplashScreenView
(@NonNull ActivityClientRecord r,
@Nullable SplashScreenView.SplashScreenViewParcelable parcelable) {
final
DecorView decorView = (DecorView) r.window.peekDecorView;
if
(parcelable !=
null
&& decorView !=
null
) {
createSplashScreen(r, decorView, parcelable);
......
private
void
createSplashScreen
(ActivityClientRecord r, DecorView decorView,
SplashScreenView.SplashScreenViewParcelable parcelable) {
// 初始化SplashScreenView构建器
final
SplashScreenView.Builder builder =
new
SplashScreenView.Builder(r.activity);
// 从parcelable中获取配置数据,并通过build初始化SplashScreenView,设置数据
final
SplashScreenView view = builder.createFromParcel(parcelable).build;
// 把SplashScreenView添加到DecorView中
decorView.addView(view);
// 设置SystemUI颜色
view.attachHostActivityAndSetSystemUIColors(r.activity, r.window);
// 刷新视图
view.requestLayout;
......
核心的部分源码已经分析差不多了,剩下的一些源码,感兴趣的同学可以自己查看阅读。
compileSdk升级到31
产品中统一使用兼容库SplashScreen
implementation
'androidx.core:core-splashscreen:最新版本'
演示示例中资源目录
drawable —— 定义低版本的drawable资源
drawable-v23 —— 定义
6.0
以上的资源
drawable-v31 —— 定义Android12及以上的资源
values
—— 定义默认资源
values
-night —— 定义默认深色模式资源
values
-v23 —— 定义
6.0
以上系统资源
values
-v31 —— 定义Android12及以上的资源
values
-night-v31 —— 定义Android12及以上的深色模式资源
启动页图标设计准则
中心图标大图,内容需要保留2/3的内边距,否则图标会被裁剪掉,另外:图标尺寸大小可以修改。
底部品牌名图标:尺寸比例需要为 2.5:1。
兼容库SplashScreen低版本不支持设置底部品牌图标。
Android12需要在values-v31目录手动添加如下属性,才可以显示品牌名图标。
<!--兼容库没有这个属性,我们需要在values-v31单独配置一下-->
<
item
name
=
"android:windowSplashScreenBrandingImage"
>
@drawable/...
</
item
>
Android12以下系统可以使用
android:windowBackground
为父主题设置窗口背景,切记不要在Android12及以上系统设置父主题的窗口背景(因为没有效果)。
Android12系统以下系统,使用
android:windowBackground
的话,一定要给
windowSplashScreenAnimatedIcon
设置一个透明的drawable,否则会出现机器人图标。
windowSplashScreenBackground
这个属性的颜色一定要注意,配置有问题的话,启动页过渡到主页面的时候,会有这个颜色闪出来,建议和Activity的
android:windowBackground
配置成一样的颜色。
在启动画面上面,添加一个“广告或者推广页面”,代码和效果如下:
override
fun
onSplashScreenExit
(splashScreenViewProvider:
SplashScreenViewProvider
)
{
if
(splashScreenViewProvider.view
is
ViewGroup){
// 在这里添加一个启动页广告或者推广页面
val
composeView = ComposeView(
this
@SplashScreenCompatActivity
).apply {
setContent {
SplashAdScreen(onCloseAd = {
splashScreenViewProvider.remove
(splashScreenViewProvider.view
as
ViewGroup).addView(composeView)
return
实践 - 启动页添加广告或者推广页
文章中示例的演示APK及源码地址:
静态图标启动页
动态图标启动页
(Android12系统有动画效果)
启动页加广告
下载:
SplashScreen快手启动页效果的apk001
https://wws.lanzoui.com/iV0M5vexz7a
下载:
SplashScreen快手启动页效果的apk002
https://wws.lanzoui.com/ijLzZvezk1g
下载:
SplashScreen启动页广告apk
https://wws.lanzoui.com/igAufvftyfe
提取码:7gj2
提取码:b6ce
提取码:fnva
点击查看:
SplashScreen演示示例的源码
。
https://github.com/TheMelody/SplashScreenExample
在线流程图制作
https://www.bullmind.com/
官方文档 Splash screens
https://developer.android.com/guide/topics/graphics/vector-drawable-resources
Google官网介绍矢量图
https://developer.android.com/guide/topics/graphics/vector-drawable-resources.html
在线svg编辑器
https://editor.method.ac/
在线制作矢量图动画
https://shapeshifter.design/
在线合并多个GIF制作
https://ezgif.com/combine
最后推荐一下我做的网站,玩Android:
wanandroid.com
,包含详尽的知识体系、好用的工具,还有本公众号文章合集,欢迎体验和收藏!
再见,内存泄漏!
新技术又又叒叒叒来了?
Android包体积常规、进阶、极致优化方案分享
点击
关注我的公众号
如果你想要跟大家分享你的文章,欢迎投稿~
┏(^0^)┛明天见!
返回搜狐,查看更多
责任编辑:
声明:该文观点仅代表作者本人,搜狐号系信息发布平台,搜狐仅提供信息存储空间服务。