2022-04-29 ImageKnife組件,讓小白也能輕松搞定圖片開發(fā)

# 開發(fā)者說 #

【開發(fā)者說】欄目是為HarmonyOS開發(fā)者提供的展示和分享平臺默穴,在這里怔檩,大家可以發(fā)表自己的技術(shù)洞察和見解,也可以展示自己的開發(fā)心得和成果蓄诽。

本期我們給大家?guī)淼氖情_發(fā)者周黎生的分享薛训,希望能給你的HarmonyOS開發(fā)之旅帶來啟發(fā)~

圖片是UI界面的重要元素之一,?圖片加載速度及效果直接影響應(yīng)用體驗(yàn)仑氛。ArkUI開發(fā)框架提供了豐富的圖像處理能力乙埃,如圖像解碼、圖像編碼锯岖、圖像編輯及基本的位圖操作等介袜,滿足了開發(fā)者日常開發(fā)所需。

但隨著產(chǎn)品需求的日益增長出吹,基本的圖像處理能力已不能勝任某些比較復(fù)雜的應(yīng)用場景遇伞,如無法直接獲取緩存圖片、無法配置占位圖捶牢、無法進(jìn)行自定義PixelMap圖片變換等鸠珠。

為增強(qiáng)ArkUI開發(fā)框架的圖像處理能力巍耗,ImageKnife組件應(yīng)運(yùn)而生。本期我們將為大家?guī)鞩mageKnife的介紹渐排。

一炬太、ImageKnife簡介

ImageKnife是一個(gè)參考Glide框架進(jìn)行設(shè)計(jì),并基于eTS語言實(shí)現(xiàn)的圖片處理組件驯耻。它可以讓開發(fā)者能輕松且高效地進(jìn)行圖片開發(fā)亲族。

注:Glide是一個(gè)快速高效的圖片加載庫,注重于平滑的滾動(dòng)可缚,提供了易用的API霎迫,高性能、可擴(kuò)展的圖片解碼管道城看,以及自動(dòng)的資源池技術(shù)女气。

功能方面杏慰,ImageKnife提供了自定義圖片變換测柠、占位圖等圖片處理能力,幾乎滿足了開發(fā)者進(jìn)行圖片處理的一切需求缘滥。

性能方面轰胁,ImageKnife采用LRU策略實(shí)現(xiàn)二級緩存,可靈活配置朝扼,有效減少內(nèi)存消耗赃阀,提升了應(yīng)用性能。

使用方面擎颖,ImageKnife封裝了一套完整的圖片加載流程榛斯,開發(fā)者只需根據(jù)ImageKnifeOption配置相關(guān)信息即可完成圖片的開發(fā),降低了開發(fā)難度搂捧,提升了開發(fā)效率驮俗。如圖1所示,是ImageKnife加載圖片的整體流程允跑。

圖1 ImageKnife加載圖片整體流程

二王凑、ImageKnife實(shí)現(xiàn)原理

下面我們將為大家介紹ImageKnife加載圖片過程中每個(gè)環(huán)節(jié)的實(shí)現(xiàn)原理,讓大家更深刻地認(rèn)識ImageKnife組件聋丝。圖2是ImageKnife加載圖片的時(shí)序圖:

圖2 ImageKnife加載圖片的時(shí)序圖

1. 用戶配置信息

在加載圖片前索烹,用戶需根據(jù)自身需求配置相應(yīng)的參數(shù),包括圖片路徑弱睦、圖片大小百姓、占位圖及緩存策略等。ImageKnife提供了RequestOption類况木,用于封裝用戶配置信息的接口瓣戚,如圖3所示列舉了部分接口供大家參考:

圖3 用戶配置參數(shù)

通過ImageKnifeExecute()方法獲取用戶配置信息端圈,然后執(zhí)行ImageKnife.call(request),正式啟動(dòng)圖片加載任務(wù)子库。相關(guān)實(shí)現(xiàn)代碼如下:

imageKnifeExecute() {

// 首先需要確保獲取ImageKnife單例對象

if(ImageKnife){

}else{

ImageKnife = globalThis.exports.default.data.imageKnife;

}

// 生成配置信息requestOption

let request = new RequestOption();

// 配置必要信息和回調(diào)

this.configNecessary(request);

// 配置緩存相關(guān)信息

this.configCacheStrategy(request);

// 配置顯示信息和回調(diào)

this.configDisplay(request);

// 啟動(dòng)ImageKnife執(zhí)行請求

ImageKnife.call(request);

}


2. 加載圖片

加載圖片過程是ImageKnife組件的核心部分舱权,如圖4所示,包含占位圖填充仑嗅、緩存實(shí)現(xiàn)及圖片解碼三個(gè)環(huán)節(jié)宴倍。下面我們將為大家分別介紹每個(gè)環(huán)節(jié)的實(shí)現(xiàn)。


圖4 圖片加載過程

(1) 占位圖填充

占位圖就是圖片加載過程中頁面上的過渡效果仓技,通常表現(xiàn)形式是在頁面上待加載區(qū)域填充灰色的占位圖鸵贬,可以使得頁面框架不會(huì)因?yàn)榧虞d失敗而變形。ImageKnife提供了占位圖功能脖捻,開發(fā)者可在RequestOption中配置是否啟動(dòng)占位圖任務(wù)阔逼。

如圖5所示是占位圖工作流程,執(zhí)行圖片加載任務(wù)后地沮,占位圖會(huì)填充加載頁面嗜浮。如果圖片解析成功則將頁面上填充的占位圖替換為待加載的圖片。如果圖片解析失敗摩疑,則將頁面上填充的占位圖替換為“圖片解析失敗占位圖”危融。

圖5 占位圖工作流程

相關(guān)實(shí)現(xiàn)代碼如下:

// 占位圖解析成功

placeholderOnComplete(imageKnifeData: ImageKnifeData) {

// 主圖未加載成功,并且未加載失敗 顯示占位圖 主圖加載成功或者加載失敗后=>不展示占位圖

if (!this.loadMainReady && !this.loadErrorReady && !this.loadThumbnailReady) {

this.placeholderFunc(imageKnifeData)

}

}

// 加載失敗 占位圖解析成功

errorholderOnComplete(imageKnifeData: ImageKnifeData) {

// 如果有錯(cuò)誤占位圖 先解析并保存在RequestOption中 等到加載失敗時(shí)候進(jìn)行調(diào)用

this.errorholderData = imageKnifeData;

if (this.loadErrorReady) {

this.errorholderFunc(imageKnifeData)

}

}


(2) 緩存實(shí)現(xiàn)

緩存是圖片加載過程中最關(guān)鍵的環(huán)節(jié),緩存機(jī)制直接影響了圖片加載速度及圖片滾動(dòng)效果雷袋。開發(fā)者可通過以下方法來靈活配置緩存策略吉殃,

圖6 緩存策略API

為了保障圖片的加載速度,ImageKnife通過使用Least Recently Used(最近最少使用)清空策略來實(shí)現(xiàn)內(nèi)存緩存及磁盤緩存楷怒。

如圖7所示蛋勺,在圖片加載過程中,CPU會(huì)首先讀取內(nèi)存緩存中的數(shù)據(jù)鸠删,如果讀取到圖片資源則直接顯示圖片抱完,否則讀取磁盤緩存數(shù)據(jù)。如果在磁盤緩存上仍然沒有讀取到數(shù)據(jù)冶共,則可判定為該圖片為網(wǎng)絡(luò)圖片乾蛤,這時(shí)需要將網(wǎng)絡(luò)圖片解碼后再進(jìn)行顯示(后面章節(jié)會(huì)詳細(xì)介紹),并將解碼后的圖片文件緩存至磁盤捅僵。

圖7 圖片緩存過程

下面我們將分別介紹兩種緩存機(jī)制的具體實(shí)現(xiàn):

① 內(nèi)存緩存

內(nèi)存緩存家卖,就是指當(dāng)前程序運(yùn)行內(nèi)存分配的臨時(shí)存儲(chǔ)器,當(dāng)我們使用ImageKnife加載圖片時(shí)庙楚,這張圖片會(huì)被緩存到內(nèi)存當(dāng)中上荡,只要在它還沒從內(nèi)存中被清除之前,下次再加載這張圖片都會(huì)直接從內(nèi)存中讀取,而不用重新從網(wǎng)絡(luò)或硬盤上讀取酪捡,大幅度提升圖片的加載效率叁征。

ImageKnife內(nèi)存緩存的實(shí)現(xiàn),需控制最大空間(maxsize)逛薇,以及目前占用空間(size)捺疼,相關(guān)實(shí)現(xiàn)代碼如下:?

// 移除較少使用的緩存數(shù)據(jù)

trimToSize(tempsize: number) {

while (true) {

if (tempsize < 0) {

this.map.clear()

this.size = 0

break

}

if (this.size <= tempsize || this.map.isEmpty()) {

break

}

var delkey = this.map.getFirstKey()

this.map.remove(delkey)

this.size--

}

}

// 緩存數(shù)據(jù)最大值

maxSize(): number{

return this.maxsize

}

// 設(shè)置緩存數(shù)據(jù)量最大值

resize(maxsize: number) {

if (maxsize < 0) {

throw new Error('maxsize <0 & maxsize invalid');

}

this.maxsize = maxsize

this.trimToSize(maxsize)

}

// 清除緩存

evicAll() {

this.trimToSize(-1)

}


② 磁盤緩存

默認(rèn)情況下,磁盤緩存的是解碼后的圖片文件永罚,需防止應(yīng)用重復(fù)從網(wǎng)絡(luò)或其他地方下載和讀取數(shù)據(jù)啤呼。ImageKnife磁盤緩存的實(shí)現(xiàn),主要依靠journal文件對緩存數(shù)據(jù)進(jìn)行保存呢袱,保證程序磁盤緩存內(nèi)容的持久化問題官扣。

相關(guān)實(shí)現(xiàn)代碼如下:?

//讀取journal文件的緩存數(shù)據(jù)

readJournal(path: string) {

var fileReader = new FileReader(path)

var line: string = ''

while (!fileReader.isEnd()) {

line = fileReader.readLine()

line = line.replace('\n', '').replace('\r', '')

this.dealwithJournal(line)

}

this.fileUtils.deleteFile(this.journalPathTemp)

this.trimToSize()

}

//根據(jù)LRU算法刪除多余緩存數(shù)據(jù)

private trimToSize() {

while (this.size > this.maxSize) {

var tempkey: string = this.cacheMap.getFirstKey()

var fileSize = this.fileUtils.getFileSize(this.dirPath + tempkey)

if (fileSize > 0) {

this.size = this.size - fileSize

}

this.fileUtils.deleteFile(this.dirPath + tempkey)

this.cacheMap.remove(tempkey)

this.fileUtils.writeData(this.journalPath, 'remove ' + tempkey + '\n')

}

}

//清除所有disk緩存數(shù)據(jù)

cleanCacheData() {

var length = this.cacheMap.size()

for (var index = 0; index < length; index++) {

this.fileUtils.deleteFile(this.dirPath + this.cacheMap[index])

}

this.fileUtils.deleteFile(this.journalPath)

this.cacheMap.clear() this.size = 0

}


(3) 圖片解碼

當(dāng)我們使用ImageKnife去加載一張圖片的時(shí)候,并不是將原始圖片直接顯示出來羞福,而是會(huì)進(jìn)行圖片解碼后再顯示到頁面惕蹄。圖片解碼就是將不同格式的圖片(包括JPEG、PNG治专、GIF卖陵、WebP、BMP)解碼成統(tǒng)一格式的PixelMap圖片文件看靠。

ImageKnife的圖片解碼能力依賴的是ArkUI開發(fā)框架提供的ImageSource解碼能力赶促。通過import image from '@ohos.multimedia.image'導(dǎo)入ArkUI開發(fā)框架的圖片能力液肌,并調(diào)用createImageSource()方法獲取挟炬,實(shí)現(xiàn)代碼如下:

import?image?from?'@ohos.multimedia.image'

export class TransformUtils {

static centerCrop(buf: ArrayBuffer, outWidth: number,outHeihgt: number,

callback?: AsyncTransform<Promise<PixelMap>>){

// 創(chuàng)建媒體解碼imageSource

var imageSource = image.createImageSource(buf as any);

// 獲取圖片信息

imageSource.getImageInfo()

.then((p) => {

var sw;

var sh;

var scale;

var pw = p.size.width;

var ph = p.size.height;

// 根據(jù)centerCrop規(guī)則控制縮放比例

if (pw == outWidth && ph == outHeihgt) {

sw = outWidth;

sh = outHeihgt;

} else {

if (pw * outHeihgt > outWidth * ph) {

scale = outHeihgt / ph;

} else {

scale = outWidth / pw;

} sw = pw * scale;

sh = ph * scale;

}

var options = {

editable: true,

rotate: 0,

desiredRegion: { size: { width: sw, height: sh },

x: pw / 2 - sw / 2,

y: ph / 2 - sh / 2,

},

}

if (callback) {

// 回調(diào),創(chuàng)建相關(guān)配置pixelmap

callback('', imageSource.createPixelMap(options));

}

})

.catch((error) => {

callback(error, null);

})

}

}


3. 顯示圖片

獲取到PixelMap解碼文件后嗦哆,接下來就是將它渲染到應(yīng)用界面上谤祖。ImageKnife的圖片渲染能力依賴的是ArkUI開發(fā)框架提供的Image組件的渲染能力。由于eTS是聲明式的老速,我們無法直接獲得Image組件的對象粥喜,需要依賴ArkUI開發(fā)框架的@State能力綁定輸入?yún)?shù),在改變屬性對象之后橘券,通知UI組件重新渲染额湘,達(dá)到圖片顯示的效果。

相關(guān)代碼如下:

@Component

export struct ImageKnifeComponent {

@Watch('watchImageKnifeOption')

@Link imageKnifeOption: ImageKnifeOption;

@State imageKnifePixelMapPack: PixelMapPack = new PixelMapPack();

@State imageKnifeResource: Resource = $r('app.media.icon_loading')

@State imageKnifeString: string = ''

@State normalPixelMap: boolean = false;

@State normalResource: boolean = true;

previousData: ImageKnifeData = null;

??nowData:?ImageKnifeData?=?null

; build() {

Stack() {

//Image組件配置

Image(this.normalPixelMap ? this.imageKnifePixelMapPack.pixelMap : (this.normalResource ? this.imageKnifeResource : this.imageKnifeString))

.objectFit(this.imageKnifeOption.imageFit ? this.imageKnifeOption.imageFit : ImageFit.Fill)

.visibility(this.imageVisible)

.width(this.imageWidth)

.height(this.imageHeight)

}

}

//必要的用戶配置和回調(diào)方法

configNecessary(request: RequestOption){

request.load(this.imageKnifeOption.loadSrc)

.addListener((err, data) => {

console.log('request.load callback')

this.imageKnifeChangeSource(data)

this.animateTo('image');

return false;?

?????})

if (this.imageKnifeOption.size) {

request.setImageViewSize(this.imageKnifeOption.size)

}

}

// imageknife 第一次啟動(dòng)和數(shù)據(jù)刷新后重新發(fā)送請求

imageKnifeExecute() {

let request = new RequestOption();

this.configNecessary(request);

this.configCacheStrategy(request);

this.configDisplay(request);

ImageKnife.call(request);

}

//返回?cái)?shù)據(jù)Image渲染展示圖片

imageKnifeSpecialFixed(data:ImageKnifeData) {

if (data.isPixelMap()) {

this.displayPixelMap(data);

}

else if (data.isString()) {

this.displayString(data);

} else if (data.isResource()) {

this.displayResource(data);

} else {

}

}

}

注:@State裝飾的變量是組件內(nèi)部的狀態(tài)數(shù)據(jù)旁舰,當(dāng)這些狀態(tài)數(shù)據(jù)被修改時(shí)锋华,將會(huì)調(diào)用所在組件的build方法進(jìn)行UI刷新。

三箭窜、ImageKnife實(shí)戰(zhàn)

通過上文的介紹毯焕,相信大家對ImageKnife組件有了深刻的了解。下面我們將創(chuàng)建一個(gè)ImageKnife_Test項(xiàng)目磺樱,為大家展示ArkUI開發(fā)框架中ImageKnife組件的使用纳猫。通過將ImageKnife組件下載至項(xiàng)目中婆咸,然后根據(jù)ImageKnifeOption配置相關(guān)信息,即可完成GIF圖片的加載芜辕。

1. 創(chuàng)建項(xiàng)目

如圖8所示尚骄,在DevEco Studio中新建ImageKnife_Test項(xiàng)目,項(xiàng)目類型選擇Application侵续,語言選擇eTS乖仇,點(diǎn)擊Finish完成創(chuàng)建。

圖8 創(chuàng)建項(xiàng)目

2. 添加依賴

成功創(chuàng)建項(xiàng)目后询兴,接下來就是將ImageKnife組件下載至項(xiàng)目中乃沙。

首先,我們需找到.npmrc 配置文件诗舰,并在文件中添加 @ohos 的scope倉庫地址:@ohos:registry=https://repo.harmonyos.com/npm/警儒,如圖9所示:

圖9 添加 scope倉庫地址

配置好npm倉庫地址后,如圖10所示眶根,在DevEco Studio的底部導(dǎo)航欄蜀铲,點(diǎn)擊“Terminal”(快捷鍵Alt+F12),鍵入命令:npm install @ohos/imageknife并回車属百,此時(shí)ImageKnife組件會(huì)被自動(dòng)下載至項(xiàng)目中记劝。下載完成后工程根目錄下會(huì)生成node_modules/@ohos/imageknife目錄。

圖10 下載至項(xiàng)目

3. 編寫邏輯代碼

ImageKnife組件成功下載至項(xiàng)目中后族扰,接下來就是邏輯代碼編寫厌丑,這里我們將為大家介紹兩種使用方式:

方式一:首先初始化全局ImageKnife實(shí)例,然后在app.ets中調(diào)用ImageKnife.with()進(jìn)行初始化渔呵,相關(guān)代碼如下:

import {ImageKnife} from '@ohos/imageknife'

export default {

data: {

imageKnife: {} // ImageKnife

},

onCreate() {

this.data.imageKnife = ImageKnife.with();

},

onDestroy() {

},

}


然后在頁面index.ets中使用ImageKnife怒竿,相關(guān)代碼如下:

@Entry

@Component

struct Index {

??build()?{

??}

? // 頁面初始化完成,生命周期回調(diào)函數(shù)中 進(jìn)行調(diào)用ImageKnife

aboutToAppear() {

let requestOption = new RequestOption();

requestOptin.load($r('app.media.IceCream'))

.addListener((err,data) => {

//加載成功/失敗回調(diào)監(jiān)聽

})

...

ImageKnife.call(requestOption)

}

}

var ImageKnife;

var defaultTemp = globalThis.exports.default

if(defaultTemp != undefined) {

ImageKnife = defaultTemp.data.imageKnife;

}


方式二:?在index.ets中扩氢,直接使用ImageKnifeOption作為入?yún)⒏郏⑴浜献远x組件ImageKnifeComponent使用,相關(guān)代碼如下:

import?{ImageKnifeOption}?from?'@ohos/imageknife'

@Entry

@Component

struct Index {

@State imageKnifeOption1: ImageKnifeOption =

{

loadSrc: $r('app.media.gifSample'),

size: { width: 300, height: 300 },

placeholderSrc: $r('app.media.icon_loading'),

errorholderSrc: $r('app.media.icon_failed')

};

build() {

Scroll() {

Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) {

ImageKnifeComponent({ imageKnifeOption: $imageKnifeOption1 })

}

}

.width('100%')

.height('100%')

}

}


以上就是本期全部內(nèi)容录豺,恭喜大家花幾分鐘時(shí)間收獲了一個(gè)實(shí)用的組件朦肘。希望廣大開發(fā)者能利用這個(gè)強(qiáng)大的開源組件開發(fā)出更多精美的應(yīng)用。

END

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末双饥,一起剝皮案震驚了整個(gè)濱河市媒抠,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌兢哭,老刑警劉巖领舰,帶你破解...
    沈念sama閱讀 221,198評論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡冲秽,警方通過查閱死者的電腦和手機(jī)舍咖,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評論 3 398
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來锉桑,“玉大人排霉,你說我怎么就攤上這事∶裰幔” “怎么了攻柠?”我有些...
    開封第一講書人閱讀 167,643評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長后裸。 經(jīng)常有香客問我瑰钮,道長,這世上最難降的妖魔是什么微驶? 我笑而不...
    開封第一講書人閱讀 59,495評論 1 296
  • 正文 為了忘掉前任浪谴,我火速辦了婚禮,結(jié)果婚禮上因苹,老公的妹妹穿的比我還像新娘苟耻。我一直安慰自己,他們只是感情好扶檐,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,502評論 6 397
  • 文/花漫 我一把揭開白布凶杖。 她就那樣靜靜地躺著,像睡著了一般款筑。 火紅的嫁衣襯著肌膚如雪智蝠。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,156評論 1 308
  • 那天醋虏,我揣著相機(jī)與錄音寻咒,去河邊找鬼哮翘。 笑死颈嚼,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的饭寺。 我是一名探鬼主播阻课,決...
    沈念sama閱讀 40,743評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼艰匙!你這毒婦竟也來了限煞?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,659評論 0 276
  • 序言:老撾萬榮一對情侶失蹤员凝,失蹤者是張志新(化名)和其女友劉穎署驻,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,200評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡旺上,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,282評論 3 340
  • 正文 我和宋清朗相戀三年瓶蚂,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片宣吱。...
    茶點(diǎn)故事閱讀 40,424評論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡窃这,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出征候,到底是詐尸還是另有隱情杭攻,我是刑警寧澤,帶...
    沈念sama閱讀 36,107評論 5 349
  • 正文 年R本政府宣布疤坝,位于F島的核電站兆解,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏跑揉。R本人自食惡果不足惜痪宰,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,789評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望畔裕。 院中可真熱鬧衣撬,春花似錦、人聲如沸扮饶。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,264評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽甜无。三九已至扛点,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間岂丘,已是汗流浹背陵究。 一陣腳步聲響...
    開封第一講書人閱讀 33,390評論 1 271
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留奥帘,地道東北人铜邮。 一個(gè)月前我還...
    沈念sama閱讀 48,798評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像寨蹋,于是被迫代替她去往敵國和親松蒜。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,435評論 2 359

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