SwiftUI框架詳細(xì)解析 (二) —— 基于SwiftUI的閃屏頁(yè)的創(chuàng)建(一)

版本記錄

版本號(hào) 時(shí)間
V1.0 2019.09.16 星期一

前言

今天翻閱蘋(píng)果的API文檔,發(fā)現(xiàn)多了一個(gè)框架SwiftUI,這里我們就一起來(lái)看一下這個(gè)框架。感興趣的看下面幾篇文章。
1. SwiftUI框架詳細(xì)解析 (一) —— 基本概覽(一)

開(kāi)始

首先看下主要內(nèi)容

學(xué)習(xí)構(gòu)建一個(gè)使用動(dòng)畫(huà)和SwiftUI的啟動(dòng)畫(huà)面,超越典型的靜態(tài)啟動(dòng)畫(huà)面,并在應(yīng)用程序加載時(shí)讓用戶感興趣。

下面看一下寫(xiě)作環(huán)境

Swift 5, iOS 13, Xcode 11

精彩的啟動(dòng)畫(huà)面 - 開(kāi)發(fā)人員有機(jī)會(huì)玩有趣的動(dòng)畫(huà),因?yàn)閼?yīng)用程序瘋狂地為其需要運(yùn)行的關(guān)鍵數(shù)據(jù)執(zhí)行API端點(diǎn)。 與靜態(tài)無(wú)動(dòng)畫(huà)啟動(dòng)屏幕相反,啟動(dòng)畫(huà)面可以在應(yīng)用程序中發(fā)揮重要作用:在用戶等待應(yīng)用程序啟動(dòng)時(shí)保持用戶的興趣。

本教程將逐步指導(dǎo)您從沒(méi)有啟動(dòng)畫(huà)面的應(yīng)用程序到具有炫酷閃屏的應(yīng)用程序,這將是其他人的羨慕。 你還在等什么?

注意:本教程假設(shè)您對(duì)SwiftUI動(dòng)畫(huà),狀態(tài)和修飾符感到滿意。 本教程不是介紹這些概念,而是專注于使用它們來(lái)復(fù)制一個(gè)很酷的動(dòng)畫(huà)。

在本教程中,您將增強(qiáng)一個(gè)名為Fuber的應(yīng)用程序。 Fuber是一種按需乘車(chē)共享服務(wù),允許乘客請(qǐng)求Segway司機(jī)將他們運(yùn)送到城市環(huán)境中的不同位置。

Fuber發(fā)展迅速,目前在60多個(gè)國(guó)家為Segway代客提供服務(wù),但由于使用Segway司機(jī),它面臨眾多政府以及Segway工會(huì)的反對(duì)。

打開(kāi)啟動(dòng)項(xiàng)目并瀏覽一下。

正如您在ContentView.swift中看到的,當(dāng)前所有應(yīng)用程序都會(huì)顯示SplashScreen兩秒鐘,然后將其淡化以顯示MapView

注意:在生產(chǎn)應(yīng)用程序中,此循環(huán)的退出條件可能是API端點(diǎn)的握手成功,它為應(yīng)用程序提供了繼續(xù)所需的數(shù)據(jù)。

啟動(dòng)畫(huà)面位于自己的模塊中:SplashScreen.swift。 您可以看到它有一個(gè)帶有“F ber”標(biāo)簽的Fuber-blue背景,等待您添加動(dòng)畫(huà)“U”。

構(gòu)建并運(yùn)行啟動(dòng)項(xiàng)目。

幾秒鐘之后,你會(huì)看到一個(gè)不太令人興奮的靜態(tài)閃屏,它會(huì)轉(zhuǎn)換到地圖(Fuber的主屏幕)。

您將花費(fèi)本教程的其余部分將這個(gè)無(wú)聊的靜態(tài)啟動(dòng)畫(huà)面轉(zhuǎn)換為精美動(dòng)畫(huà)的屏幕,這將使您的用戶希望主屏幕永遠(yuǎn)不會(huì)加載。 看看你將構(gòu)建的內(nèi)容:

注意:如果您正在運(yùn)行macOS Catalina beta,您可以使用實(shí)時(shí)預(yù)覽代替在模擬器上構(gòu)建和運(yùn)行。


Understanding the Composition of Views and Layers

新的和改進(jìn)的SplashScreen將包含幾個(gè)子視圖,所有子視圖都在ZStack中方便地組織:

  • 網(wǎng)格背景由較小的“Chimes”圖像的圖塊組成,它與啟動(dòng)項(xiàng)目一起提供。
  • “F ber”文本,有動(dòng)畫(huà)FuberU的空間。
  • FuberU,代表'U'的圓形白色背景。
  • 一個(gè)矩形,代表FuberU中間的正方形。
  • 另一個(gè)矩形表示從FuberU中間到其外邊緣的直線。
  • Spacer視圖,以確保ZStack大小將覆蓋整個(gè)屏幕。

結(jié)合起來(lái),這些視圖創(chuàng)建了Fuber'U'動(dòng)畫(huà)。

starter項(xiàng)目提供TextSpacer視圖。 您將在以下部分中添加其余視圖。

既然您已經(jīng)知道如何構(gòu)建這些圖層,那么您可以開(kāi)始創(chuàng)建和動(dòng)畫(huà)FuberU


Animating the Circle

使用動(dòng)畫(huà)時(shí),最好關(guān)注您當(dāng)前正在實(shí)施的動(dòng)畫(huà)。 打開(kāi)ContentView.swift并注釋掉.onAppear閉包。 它應(yīng)該如下所示:

.onAppear {
//DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
//  withAnimation() {
//    self.showSplash = false
//  }
//}
}

通過(guò)這種方式,您不會(huì)被閃屏分散注意力,在X秒后淡出顯示MapView。 別擔(dān)心,當(dāng)你完成并準(zhǔn)備時(shí),你會(huì)取消注釋。

您現(xiàn)在可以專注于動(dòng)畫(huà)。 首先打開(kāi)SplashScreen.swift,然后在SplashScreen的結(jié)束括號(hào)下面添加一個(gè)名為FuberU的新結(jié)構(gòu):

struct FuberU: Shape {
  var percent: Double
  
  // 1
  func path(in rect: CGRect) -> Path {
    let end = percent * 360
    var p = Path()

    // 2
    p.addArc(center: CGPoint(x: rect.size.width/2, y: rect.size.width/2),
             radius: rect.size.width/2,
             startAngle: Angle(degrees: 0),
             endAngle: Angle(degrees: end),
             clockwise: false)
    
    return p
  }  
  // 3
  var animatableData: Double {
    get { return percent }
    set { percent = newValue }
  }
}

以下是您使用此代碼所做的事情:

  • 1) 根據(jù)Shape協(xié)議的要求實(shí)現(xiàn)path(in:)
  • 2) 使用路徑繪制從0開(kāi)始到360結(jié)束的弧,即一個(gè)完整的圓。
  • 3) 添加額外的屬性,以便SwiftUI知道如何為你的形狀設(shè)置動(dòng)畫(huà)。

為了看到你的新類(lèi)型,你將設(shè)置一些變量和一個(gè)動(dòng)畫(huà),然后在body上用一些修飾符聲明它。

首先在SplashScreen結(jié)構(gòu)中的body元素之前添加這些變量:

@State var percent = 0.0
let uLineWidth: CGFloat = 5

在啟動(dòng)和修改FuberU時(shí),您將使用這些變量。

然后,在SplashScreenstruct結(jié)束括號(hào)后添加以下代碼:

extension SplashScreen {
  var uAnimationDuration: Double { return 1.0 }
    
  func handleAnimations() {
    runAnimationPart1()
  }

  func runAnimationPart1() {
    withAnimation(.easeIn(duration: uAnimationDuration)) {
      percent = 1
    }
  }
}

handleAnimations()將成為初始屏幕復(fù)雜動(dòng)畫(huà)的所有不同部分的基礎(chǔ)。 它基于magic numbers,你可以玩,并調(diào)整,以配合你以后的確切口味。

最后,在body中,在現(xiàn)有的TextSpacer元素之間添加以下代碼。

FuberU(percent: percent)
 .stroke(Color.white, lineWidth: uLineWidth)
 .onAppear() {
   self.handleAnimations()
 }
 .frame(width: 45, height: 45, alignment: .center)

在這里,您將新圓圈(最終將代表Fuber'U'的一部分)添加到特定位置的堆棧。 此外,在視圖出現(xiàn)時(shí)調(diào)用handleAnimations()

構(gòu)建并運(yùn)行您的應(yīng)用:

你可以看到正在發(fā)生的事情,但這并不是你所期望的。 你的代碼確實(shí)畫(huà)了一個(gè)圓圈,但只有一次,圓圈的邊界太薄了。 你希望它填滿整個(gè)圈子。 你馬上解決這些問(wèn)題。


Improving the Circle Animation

首先在runAnimationPart1()之后添加此代碼:

func restartAnimation() {
  let deadline: DispatchTime = .now() + uAnimationDuration
  DispatchQueue.main.asyncAfter(deadline: deadline) {
    self.percent = 0
    self.handleAnimations()
  }
}

要調(diào)用此方法,請(qǐng)?jiān)?code>handleAnimations()的末尾添加以下行:

restartAnimation()

此代碼通過(guò)等待其持續(xù)時(shí)間重置percent然后再次調(diào)用它來(lái)循環(huán)動(dòng)畫(huà)。

現(xiàn)在圓圈動(dòng)畫(huà)重復(fù)出現(xiàn),您可以向FuberU添加修飾符,使其看起來(lái)與您想要的完全一樣。 首先,在body之前添加這些新變量:

@State var uScale: CGFloat = 1
let uZoomFactor: CGFloat = 1.4

現(xiàn)在,在FuberU上的stroke(_:lineWidth :)onAppear()修飾符之間添加以下三個(gè)新修飾符:

.rotationEffect(.degrees(-90))
.aspectRatio(1, contentMode: .fit)
.padding(20)

最后,在frame(width:height:alignment :)之前添加一個(gè)scaleEffect(_:anchor :)

.scaleEffect(uScale * uZoomFactor)

您的FuberU聲明現(xiàn)在看起來(lái)像這樣:

FuberU(percent: percent)
  .stroke(Color.white, lineWidth: uLineWidth)
  .rotationEffect(.degrees(-90))
  .aspectRatio(1, contentMode: .fit)
  .padding(20)
  .onAppear() {
    self.handleAnimations()
  }
  .scaleEffect(uScale * uZoomFactor)
  .frame(width: 45, height: 45, alignment: .center)

此代碼使線條更寬,添加了一個(gè)旋轉(zhuǎn),以便繪圖從頂部開(kāi)始并添加縮放效果,以便圓圈在動(dòng)畫(huà)時(shí)生長(zhǎng)。

在將percent更新為1后立即通過(guò)在動(dòng)畫(huà)塊內(nèi)的runAnimationPart1()中添加以下行來(lái)完成此部分:

uScale = 5

使用此代碼,您將uScale狀態(tài)從1更改為5。

構(gòu)建并運(yùn)行您的應(yīng)用:

現(xiàn)在圓圈的行為與您預(yù)期的一樣 - 您的應(yīng)用程序從0360度繪制一個(gè)完整的白色圓圈,在此過(guò)程中會(huì)有所增長(zhǎng)。

你可能會(huì)注意到圓圈在第一個(gè)繪制周期中的尺寸只會(huì)增加。 那是因?yàn)槟銖奈粗匦鲁跏蓟?code>uScale。 別擔(dān)心,您將在動(dòng)畫(huà)的下一步中解決這個(gè)問(wèn)題。

注意:嘗試使用FuberU修飾符 - 刪除一些,添加新的,更改值等等。 當(dāng)您觀察視圖更改時(shí),您將更好地了解每個(gè)修改符的作用。


Adding the Square

隨著Fuber'U'的動(dòng)畫(huà)完成,是時(shí)候添加square了。

首先,在body之前添加這些新的狀態(tài)和屬性:

@State var squareColor = Color.white
@State var squareScale: CGFloat = 1

let uSquareLength: CGFloat = 12

ZStackFuberU之后,為中心方塊添加一個(gè)Rectangle視圖:

Rectangle()
  .fill(squareColor)
  .scaleEffect(squareScale * uZoomFactor)
  .frame(width: uSquareLength, height: uSquareLength, alignment: .center)
  .onAppear() {
      self.squareColor = self.fuberBlue
  }

您添加了一個(gè)正方形,其大小和填充顏色將在整個(gè)動(dòng)畫(huà)中發(fā)生變化。

構(gòu)建并運(yùn)行您的應(yīng)用:

如您所見(jiàn),圓圈以預(yù)期大小顯示在正方形后面,但沒(méi)有動(dòng)畫(huà)。 您仍需要添加所有準(zhǔn)備工作,然后按正確的順序處理動(dòng)畫(huà)。

接下來(lái),線!


Adding the Line

現(xiàn)在,你需要添加一行,使你的'U'看起來(lái)更像字母'U',而不像一個(gè)圓圈,上面有一個(gè)正方形。

body之前添加以下屬性和狀態(tài):

@State var lineScale: CGFloat = 1

let lineWidth:  CGFloat = 4
let lineHeight: CGFloat = 28

然后在Spacer之前的ZStack末尾添加一個(gè)Rectangle視圖。

Rectangle()
  .fill(fuberBlue)
  .scaleEffect(lineScale, anchor: .bottom)
  .frame(width: lineWidth, height: lineHeight, alignment: .center)
  .offset(x: 0, y: -22)

構(gòu)建并運(yùn)行您的應(yīng)用:

現(xiàn)在您擁有了Fuber'U'的所有元素,您可以使動(dòng)畫(huà)更復(fù)雜一些。 你準(zhǔn)備好迎接挑戰(zhàn)了嗎?


Completing the U Animation

你想制作的'U'動(dòng)畫(huà)有三個(gè)階段:

  • 圓圈在繪制時(shí)放大。
  • 圓圈迅速縮小成正方形。
  • 方形消失了。

在擴(kuò)展現(xiàn)有的handleAnimations()時(shí),您將使用這三個(gè)階段。 首先在uAnimationDuration之后添加這些新屬性:

var uAnimationDelay: Double { return  0.2 }
var uExitAnimationDuration: Double{ return 0.3 }
var finalAnimationDuration: Double { return 0.4 }
var minAnimationInterval: Double { return 0.1 }
var fadeAnimationDuration: Double { return 0.4 }

這些神奇的數(shù)字是反復(fù)試驗(yàn)的結(jié)果。 隨意嘗試它們,看看你是否感覺(jué)它們改進(jìn)了動(dòng)畫(huà),或者只是為了讓你更容易理解它們是如何工作的。

uScale = 5之后,再添加一行到runAnimationPart1()的末尾:

lineScale = 1

將以下代碼添加到runAnimationPart1()的末尾,緊跟動(dòng)畫(huà)塊的右括號(hào)后:

//TODO: Add code #1 for text here

let deadline: DispatchTime = .now() + uAnimationDuration + uAnimationDelay
DispatchQueue.main.asyncAfter(deadline: deadline) {
  withAnimation(.easeOut(duration: self.uExitAnimationDuration)) {
    self.uScale = 0
    self.lineScale = 0
  }
  withAnimation(.easeOut(duration: self.minAnimationInterval)) {
    self.squareScale = 0
  }
    
  //TODO: Add code #2 for text here
}   

在這里,您使用帶有截止時(shí)間的異步調(diào)用來(lái)在第一個(gè)動(dòng)畫(huà)運(yùn)行后運(yùn)行代碼。 請(qǐng)注意,您有一些文本動(dòng)畫(huà)占位符,你很快就會(huì)解決這些問(wèn)題。

現(xiàn)在是動(dòng)畫(huà)第二部分的時(shí)候了。 在runAnimationPart1()的結(jié)束括號(hào)后添加:

func runAnimationPart2() {
  let deadline: DispatchTime = .now() + uAnimationDuration + 
    uAnimationDelay + minAnimationInterval
  DispatchQueue.main.asyncAfter(deadline: deadline) {
    self.squareColor = Color.white
    self.squareScale = 1
  }
}   

確保在handleAnimations()中的runAnimationPart1()之后立即添加對(duì)新函數(shù)的調(diào)用:

runAnimationPart2()

現(xiàn)在,在runAnimationPart2()之后添加動(dòng)畫(huà)的第三部分:

func runAnimationPart3() {
  DispatchQueue.main.asyncAfter(deadline: .now() + 2 * uAnimationDuration) {
  withAnimation(.easeIn(duration: self.finalAnimationDuration)) {
    //TODO: Add code #3 for text here
    self.squareColor = self.fuberBlue
  }
  }
}

請(qǐng)注意,代碼中包含TODO,以顯示本教程后面將為文本設(shè)置動(dòng)畫(huà)的確切位置。

現(xiàn)在,在runAnimationPart2()之后立即在handleAnimations()中添加新動(dòng)畫(huà):

runAnimationPart3()

要完成此階段,請(qǐng)使用以下新實(shí)現(xiàn)替換restartAnimation()

func restartAnimation() {
    let deadline: DispatchTime = .now() + 2 * uAnimationDuration + 
      finalAnimationDuration
    DispatchQueue.main.asyncAfter(deadline: deadline) {
      self.percent = 0
      //TODO: Add code #4 for text here
      self.handleAnimations()
    }
}

請(qǐng)注意,您根據(jù)為特定步驟定義的magic numbers計(jì)劃動(dòng)畫(huà)的每個(gè)步驟從某個(gè)點(diǎn)開(kāi)始。

構(gòu)建并運(yùn)行您的應(yīng)用程序,然后觀察美觀!

如果您查看完成的動(dòng)畫(huà),您將看到文本開(kāi)始透明和小,然后淡入,用彈簧放大,最后消失。 現(xiàn)在是時(shí)候把所有這些放到位了。


Animating the Text

“F ber”文本從一開(kāi)始就存在,但它有點(diǎn)無(wú)聊,因?yàn)樗鼪](méi)有與'U'一起動(dòng)畫(huà)。 要解決這個(gè)問(wèn)題,您需要為Text添加兩個(gè)新修飾符。 首先,在body之前添加兩個(gè)新?tīng)顟B(tài):

@State var textAlpha = 0.0
@State var textScale: CGFloat = 1

現(xiàn)在,是時(shí)候用實(shí)際動(dòng)畫(huà)替換這些占位符了。

替換//TODO: Add code #1 for text here:

withAnimation(Animation.easeIn(duration: uAnimationDuration).delay(0.5)) {
  textAlpha = 1.0
}

其次,替換//TODO: Add code #2 for text here為:

withAnimation(Animation.spring()) {
  self.textScale = self.uZoomFactor
}

接下來(lái),替換//TODO: Add code #3 for text here為:

self.textAlpha = 0

最后,替換//TODO: Add code #4 for text here為:

self.textScale = 1

現(xiàn)在,將Text替換為以下內(nèi)容:

Text("F           BER")
  .font(.largeTitle)
  .foregroundColor(.white)
  .opacity(textAlpha)
  .offset(x: 20, y: 0)
  .scaleEffect(textScale)

構(gòu)建并運(yùn)行您的應(yīng)用:

文本視圖通過(guò)動(dòng)畫(huà)響應(yīng)兩個(gè)新?tīng)顟B(tài)變量的變化! 多么酷啊?

現(xiàn)在,剩下的就是添加背景,你的真棒啟動(dòng)畫(huà)面動(dòng)畫(huà)將完成。 深吸一口氣,跳進(jìn)動(dòng)畫(huà)的最后一部分。


Animating the Background

您將首先添加ZStack的背景。 由于它是背景,它應(yīng)該是堆棧背面的視圖,因此它必須首先出現(xiàn)在代碼中。 為此,添加一個(gè)新的Image視圖作為SplashScreenZStack的第一個(gè)元素:

Image("Chimes")
  .resizable(resizingMode: .tile)
  .opacity(textAlpha)
  .scaleEffect(textScale)

這使用Chimes資源制作填滿整個(gè)屏幕的圖塊。 請(qǐng)注意,您使用textAlphatextScale作為狀態(tài)變量,因此只要這些狀態(tài)變量發(fā)生更改,視圖就會(huì)更改其不透明度和比例。 由于它們已經(jīng)更改為“F ber”文本的動(dòng)畫(huà),因此您無(wú)需執(zhí)行任何其他操作即可激活它們。

構(gòu)建并運(yùn)行應(yīng)用程序,您將看到背景動(dòng)畫(huà)以及文本:

你現(xiàn)在需要添加漣漪效果,當(dāng)Fuber'U'收縮成正方形時(shí),它會(huì)淡化背景。 您可以通過(guò)在背景視圖正上方添加一個(gè)半透明圓圈,在所有其他視圖下方來(lái)實(shí)現(xiàn)。 該圓圈將從Fuber'U'的中間動(dòng)畫(huà),以覆蓋整個(gè)屏幕并隱藏背景。 聽(tīng)起來(lái)很容易,對(duì)吧?

添加圓圈動(dòng)畫(huà)所需的這兩個(gè)新?tīng)顟B(tài)變量:

@State var coverCircleScale: CGFloat = 1
@State var coverCircleAlpha = 0.0

然后在背景圖像視圖后面的ZStack中添加這個(gè)新視圖:

Circle()
  .fill(fuberBlue) 
  .frame(width: 1, height: 1, alignment: .center)
  .scaleEffect(coverCircleScale)
  .opacity(coverCircleAlpha)

現(xiàn)在,您需要在恰當(dāng)?shù)臅r(shí)刻更改這些狀態(tài)變量的值以啟動(dòng)動(dòng)畫(huà)。 將此子句添加到runAnimationPart2(),在self.squareScale = 1的下面:

withAnimation(.easeOut(duration: self.fadeAnimationDuration)) {
  self.coverCircleAlpha = 1
  self.coverCircleScale = 1000
}

最后,不要忘記在動(dòng)畫(huà)完成并準(zhǔn)備重新啟動(dòng)時(shí)初始化圓的大小和不透明度。 在重新調(diào)用handleAnimations()之前將其添加到restartAnimation()

self.coverCircleAlpha = 0
self.coverCircleScale = 1

現(xiàn)在構(gòu)建并運(yùn)行您的應(yīng)用程序! 你已經(jīng)實(shí)現(xiàn)了你想要實(shí)現(xiàn)的完整,復(fù)雜,非常棒的動(dòng)畫(huà)。 給自己一個(gè)輕拍。 這不是微不足道的,但是你一直都是這樣做的。

現(xiàn)在坐下來(lái),放松一下,繼續(xù)完成一些重要的記憶,特別是在真正的應(yīng)用程序中。


Finishing Touches

你制作的動(dòng)畫(huà)非???,但是你現(xiàn)在實(shí)現(xiàn)它的方式,動(dòng)畫(huà)將在閃屏逐漸消失后不斷重復(fù)。

這還沒(méi)完成。 在啟動(dòng)屏幕消失后,您需要阻止動(dòng)畫(huà)重新啟動(dòng),因?yàn)樗^續(xù)超過(guò)該點(diǎn)是沒(méi)有意義的。 用戶無(wú)論如何都不會(huì)看到它,并且它使用不必要的資源。

要阻止動(dòng)畫(huà)顯示超出其需要的時(shí)間,請(qǐng)向SplashScreen添加一個(gè)新的靜態(tài)變量:

static var shouldAnimate = true

handleAnimations()中,使用if語(yǔ)句包裝restartAnimation(),這樣一旦新的Booleanfalse,就會(huì)阻止它重新開(kāi)始。 它應(yīng)該如下所示:

if SplashScreen.shouldAnimate {
  restartAnimation()
}

現(xiàn)在,返回ContentView.swift,取消注釋您在開(kāi)頭注釋掉的.onAppear閉包,并將shouldAnimate設(shè)置為false。 然后,只是為了好玩,還將第二個(gè)常量更改為10,這樣您就有機(jī)會(huì)享受您創(chuàng)建的漂亮的閃屏動(dòng)畫(huà)。 它現(xiàn)在應(yīng)該是這樣的:

.onAppear {
    DispatchQueue.main.asyncAfter(deadline: .now() + 10) {
      SplashScreen.shouldAnimate = false
      withAnimation() {
        self.showSplash = false
      }
    }
}

構(gòu)建并運(yùn)行您的應(yīng)用:

你應(yīng)該看到你的炫酷閃屏顯示10秒,然后是應(yīng)用程序的主地圖視圖。 關(guān)于它的最好的部分是,一旦啟動(dòng)屏幕消失,它就不再在后臺(tái)動(dòng)畫(huà),因此您的用戶可以自由地體驗(yàn)應(yīng)用程序的所有榮耀,而無(wú)需任何后臺(tái)動(dòng)畫(huà)減慢它們的速度。

后記

本篇主要講述了基于SwiftUI的閃屏頁(yè)的創(chuàng)建,感興趣的給個(gè)贊或者關(guān)注~~~

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容