日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

安卓自定义View进阶-Matrix Camera

發(fā)布時間:2025/4/16 编程问答 26 豆豆
生活随笔 收集整理的這篇文章主要介紹了 安卓自定义View进阶-Matrix Camera 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

原文出處:http://www.gcssloop.com/customview/matrix-3d-camera

本篇依舊屬于Matrix,主要講解Camera,Android下有很多相機應(yīng)用,其中的美顏相機更是不少,不過今天這個Camera可不是我們平時拍照的那個相機,而是graphic包下的Camera,專業(yè)給View拍照的相機,不過既然是相機,作用都是類似的,主要是將3D的內(nèi)容拍扁變成2D的內(nèi)容。

眾所周知,我們的手機屏幕是一個2D的平面,所以也沒辦法直接顯示3D的信息,因此我們看到的所有3D效果都是3D在2D平面的投影而已,而本文中的Camera主要作用就是這個,將3D信息轉(zhuǎn)換為2D平面上的投影,實際上這個類更像是一個操作Matrix的工具類,使用Camera和Matrix可以在不使用OpenGL的情況下制作出簡單的3D效果。

Camera常用方法表

Camera的方法并不是特別多,很多內(nèi)容與之前的講解的Canvas和Matrix類似,不過又稍有不同,之前的畫布操作和Matrix主要是作用于2D空間,而Camera則主要作用于3D空間。

一、基礎(chǔ)概念

在具體講解方法之前,先補充幾個基礎(chǔ)概念,以便于后面理解。

3D坐標系

我們Camera使用的3維坐標系是左手坐標系,即左手手臂指向x軸正方向,四指彎曲指向y軸正方向,此時展開大拇指指向的方向是z軸正方向。

至于為什么要用左手坐標系呢?大概是因為趕工的時候右手不方便比劃吧,大霧。實際上不同平臺上使用的坐標系也有不同,有的是左手,有的是右手,貌似并沒有統(tǒng)一的標準,只需要記住 Android 平臺上面使用的是左手坐標系即可。

2D 和 3D 坐標是通過Matrix關(guān)聯(lián)起來的,所以你可以認為兩者是同一個坐標系,但又有差別,重點就是y軸方向不同。

坐標系2D坐標系3D坐標系
原點默認位置左上角左上角
X 軸默認方向
Y 軸默認方向
Z 軸默認方向垂直屏幕向內(nèi)

3D坐標系在屏幕中各個坐標軸默認方向展示:

注意y軸默認方向是向上,而2D則是向下,另外本圖不代表3D坐標系實際位置。

三維投影

三維投影是將三維空間中的點映射到二維平面上的方法。由于目前絕大多數(shù)圖形數(shù)據(jù)的顯示方式仍是二維的,因此三維投影的應(yīng)用相當廣泛,尤其是在計算機圖形學(xué),工程學(xué)和工程制圖中。

三維投影一般有兩種,正交投影 和 透視投影。

正交投影就是我們數(shù)學(xué)上學(xué)過的 “正視圖、正視圖、側(cè)視圖、俯視圖” 這些東西。

透視投影則更像拍照片,符合近大遠小的關(guān)系,有立體感,我們此處使用的就是透視投影。

攝像機

如果你學(xué)過Unity,那么你對攝像機這一個概念應(yīng)該會有比較透徹的理解。在一個虛擬的3D的立體空間中,由于我們無法直接用眼睛去觀察這一個空間,所以要借助攝像機采集信息,制成2D影像供我們觀察。簡單來說,攝像機就是我們觀察虛擬3D空間的眼睛。

Android 上面觀察View的攝像機默認位置在屏幕左上角,而且是距屏幕有一段距離的,假設(shè)灰色部分是手機屏幕,白色是上面的一個View,攝像機位置看起來大致就是下面這樣子的(為了更好的展示攝像機的位置,做了一個空間轉(zhuǎn)換效果的動圖)。

攝像機的位置默認是 (0, 0, -576)。其中 -576= -8 x 72,雖然官方文檔說距離屏幕的距離是 -8, 但經(jīng)過測試實際距離是 -576 像素,當距離為 -10 的時候,實際距離為 -720 像素。不過這個數(shù)值72我也不明白是什么東西,我使用了3款手機測試,屏幕大小和像素密度均不同,但結(jié)果都是一樣的,知道的小伙伴可以告訴我一聲。

二、基本方法

基本方法就有兩個save 和restore,主要作用為保存當前狀態(tài)和恢復(fù)到上一次保存的狀態(tài),通常成對使用,常用格式如下:

camera.save(); // 保存狀態(tài) ... // 具體操作 camera.retore(); // 回滾狀態(tài)

三、常用方法

這兩個方法是Camera中最基礎(chǔ)也是最常用的方法。

getMatrix

void getMatrix (Matrix matrix)

計算當前狀態(tài)下矩陣對應(yīng)的狀態(tài),并將計算后的矩陣賦值給參數(shù)matrix。

applyToCanvas

void applyToCanvas (Canvas canvas)

計算當前狀態(tài)下單矩陣對應(yīng)的狀態(tài),并將計算后的矩陣應(yīng)用到指定的canvas上。

平移

聲明:以下示例中 Matrix 的平移均使用 postTranslate 來演示,實際情況中使用set、pre 或 post 需要視情況而定。

void translate (float x, float y, float z)

和2D平移類似,只不過是多出來了一個維度,從只能在2D平面上平移到在3D空間內(nèi)平移,不過,此處仍有幾個要點需要重點對待。

沿x軸平移

camera.translate(x, 0, 0);matrix.postTranslate(x, 0);

兩者x軸同向,所以 Camera 和 Matrix 在沿x軸平移上是一致的。

結(jié)論:

一致是指平移方向和平移距離一致,在默認情況下,上面兩種均可以讓坐標系向右移動x個單位。

沿y軸平移

這個就有點意思了,兩個坐標系相互關(guān)聯(lián),但是兩者的y軸方向是相反的,很容易把人搞迷糊。你可以這么玩:

Camera camera = new Camera(); camera.translate(0, 100, 0); // camera - 沿y軸正方向平移100像素Matrix matrix = new Matrix(); camera.getMatrix(matrix); matrix.postTranslate(0,100); // matrix - 沿y軸正方向平移100像素Log.i(TAG, "Matrix: "+matrix.toShortString());

在上面這種寫法,雖然用了5行代碼,但是效果卻和 Matrix matrix = new Matrix(); 一樣,結(jié)果都是單位矩陣。而且看起來貌似沒有啥問題,畢竟兩次平移都是正向100。(如果遇見不懂技術(shù)的領(lǐng)導(dǎo)嫌你寫代碼量少,你可以這樣多寫幾遍,反正一般人是看不出問題的。)

Matrix: [1.0, 0.0, 0.0][0.0, 1.0, 0.0][0.0, 0.0, 1.0]

結(jié)論:

由于兩者y軸相反,所以 camera.translate(0, -y, 0); 與 matrix.postTranslate(0, y);平移方向和距離一致,在默認情況下,這兩種方法均可以讓坐標系向下移動y個單位。

沿z軸平移

這個不僅有趣,還容易蒙逼,上面兩種情況再怎么鬧騰也只是在2D平面上,而z軸的出現(xiàn)則讓其有了空間感。

當View和攝像機在同一條直線上時: 此時沿z軸平移相當于縮放的效果,縮放中心為攝像機所在(x, y)坐標,當View接近攝像機時,看起來會變大,遠離攝像機時,看起來會變小,近大遠小。

當View和攝像機不在同一條直線上時: 當View遠離攝像機的時候,View在縮小的同時也在不斷接近攝像機在屏幕投影位置(通常情況下為Z軸,在平面上表現(xiàn)為接近坐標原點)。相反,當View接近攝像機的時候,View在放大的同時會遠離攝像機在屏幕投影位置。

我知道,這樣說你們肯定是蒙逼的,話說為啥遠離攝像機的時候會接近攝像機在屏幕投影位置(′・_・`),肯定覺得我在逗你們玩,完全是前后矛盾,邏輯都不通,不過這個在這里的確是不矛盾的,因為遠離是在3D空間里的情況,而接近只是在2D空間的投影,看下圖。

假設(shè)大矩形是手機屏幕,白色小矩形是View,攝像機位于屏幕左上角,請注意上面View與攝像機的距離以及下方View的大小以及距離左上角(攝像機在屏幕投影位置)的距離。


至于為什么會這樣,因為我們?nèi)搜垡曈X就是這樣的,當我們看向遠方的時候,視線最終都會消失在視平線上,如果你站在兩條平行線中間,看起來它們會在遠方(視平線上)相交,雖然在3D空間上兩者距離不變,但在2D投影上卻是越來越接近,如下圖(圖片來自網(wǎng)絡(luò)):

結(jié)論:

關(guān)于3D效果的平移說起來比較麻煩,但你可以自己實際的體驗一下,畢竟我們是生活在3D空間的,拿一張紙片來模擬View,用眼睛當做攝像機,在眼前來回移動紙片,多試幾次大致就明白是怎么回事了。

平移 重點內(nèi)容
x軸 2D 和 3D 相同。
y軸 2D 和 3D 相反。
z軸 近大遠小、視線相交。
旋轉(zhuǎn)
旋轉(zhuǎn)是Camera制作3D效果的核心,不過它制作出來的并不能算是真正的3D,而是偽3D,因為View是沒有厚度的。

// (API 12) 可以控制View同時繞x,y,z軸旋轉(zhuǎn),可以由下面幾種方法復(fù)合而來。 void rotate (float x, float y, float z);// 控制View繞單個坐標軸旋轉(zhuǎn) void rotateX (float deg); void rotateY (float deg); void rotateZ (float deg);

這個東西瞎扯理論也不好理解,直接上圖:

以上三張圖分別為,繞x軸,y軸,z軸旋轉(zhuǎn)的情況,至于為什么沒有顯示z軸,是因為z軸是垂直于手機屏幕的,在屏幕上的投影就是一個點。

關(guān)于旋轉(zhuǎn),有以下幾點需要注意:

旋轉(zhuǎn)中心

旋轉(zhuǎn)中心默認是坐標原點,對于圖片來說就是左上角位置。

我們都知道,在2D中,不論是旋轉(zhuǎn),錯切還是縮放都是能夠指定操作中心點位置的,但是在3D中卻沒有默認的方法,如果我們想要讓圖片圍繞中心點旋轉(zhuǎn)怎么辦? 這就要使用到我們在Matrix原理提到過的方法:

Matrix temp = new Matrix(); // 臨時Matrix變量 this.getMatrix(temp); // 獲取Matrix temp.preTranslate(-centerX, -centerY); // 使用pre將旋轉(zhuǎn)中心移動到和Camera位置相同。 temp.postTranslate(centerX, centerY); // 使用post將圖片(View)移動到原來的位置

官方示例-Rotate3dAnimation

說到3D旋轉(zhuǎn),最經(jīng)典的應(yīng)該就是ApiDemo里面的 Rotate3dAnimation 了,見過不少博文都是根據(jù)Rotate3dAnimation修改的效果,這是一個非常經(jīng)典的例子,鑒于代碼也不長,就貼在這里和大家一起品鑒一下。

public class Rotate3dAnimation extends Animation {private final float mFromDegrees;private final float mToDegrees;private final float mCenterX;private final float mCenterY;private final float mDepthZ;private final boolean mReverse;private Camera mCamera;/*** 創(chuàng)建一個繞y軸旋轉(zhuǎn)的3D動畫效果,旋轉(zhuǎn)過程中具有深度調(diào)節(jié),可以指定旋轉(zhuǎn)中心。* * @param fromDegrees 起始時角度* @param toDegrees 結(jié)束時角度* @param centerX 旋轉(zhuǎn)中心x坐標* @param centerY 旋轉(zhuǎn)中心y坐標* @param depthZ 最遠到達的z軸坐標* @param reverse true 表示由從0到depthZ,false相反*/public Rotate3dAnimation(float fromDegrees, float toDegrees,float centerX, float centerY, float depthZ, boolean reverse) {mFromDegrees = fromDegrees;mToDegrees = toDegrees;mCenterX = centerX;mCenterY = centerY;mDepthZ = depthZ;mReverse = reverse;}@Overridepublic void initialize(int width, int height, int parentWidth, int parentHeight) {super.initialize(width, height, parentWidth, parentHeight);mCamera = new Camera();}@Overrideprotected void applyTransformation(float interpolatedTime, Transformation t) {final float fromDegrees = mFromDegrees;float degrees = fromDegrees + ((mToDegrees - fromDegrees) * interpolatedTime);final float centerX = mCenterX;final float centerY = mCenterY;final Camera camera = mCamera;final Matrix matrix = t.getMatrix();camera.save();// 調(diào)節(jié)深度if (mReverse) {camera.translate(0.0f, 0.0f, mDepthZ * interpolatedTime);} else {camera.translate(0.0f, 0.0f, mDepthZ * (1.0f - interpolatedTime));}// 繞y軸旋轉(zhuǎn)camera.rotateY(degrees);camera.getMatrix(matrix);camera.restore();// 調(diào)節(jié)中心點matrix.preTranslate(-centerX, -centerY);matrix.postTranslate(centerX, centerY);} }

可以看到,短短的幾十行代碼就完成了,而核心代碼(有注釋部分)僅僅幾行而已,簡潔易懂。不過呢,這一份代碼依舊是一份未完成的代碼(不然怎么叫ApiDemo呢?),并且很多人不知道怎么修改。

不知諸位在使用的時候可否發(fā)現(xiàn)了一個問題,同一份代碼在不同手機上顯示效果也是不同的,在像素密度較低的手機上,旋轉(zhuǎn)效果比較正常,但是在像素密度較高的手機上顯示效果則會很夸張,具體會怎樣的,下面就來看一下具體效果。

可以看到,圖片不僅因為形變失真,而且在中間一段因為形變過大導(dǎo)致圖片無法顯示,當然了,單個手機失真,你可以用depthZ忽悠過去,當 depthZ 設(shè)置的數(shù)值比較大大時候,圖像在翻轉(zhuǎn)同時會遠離攝像頭,距離比較遠,失真就不會顯得很嚴重,但這仍掩蓋不了在不同手機上顯示效果不同。

如何解決這一問題呢?

想要解決其實也不難,只要修改兩個數(shù)值就可以了,這兩個數(shù)值就是在Matrix中一直被眾多開發(fā)者忽略的 MPERSP_0 和 MPERSP_1

下面是修改后的代碼(重點部分都已經(jīng)標注出來了):

public class Rotate3dAnimation extends Animation {private final float mFromDegrees;private final float mToDegrees;private final float mCenterX;private final float mCenterY;private final float mDepthZ;private final boolean mReverse;private Camera mCamera;float scale = 1; // <------- 像素密度/*** 創(chuàng)建一個繞y軸旋轉(zhuǎn)的3D動畫效果,旋轉(zhuǎn)過程中具有深度調(diào)節(jié),可以指定旋轉(zhuǎn)中心。* @param context <------- 添加上下文,為獲取像素密度準備* @param fromDegrees 起始時角度* @param toDegrees 結(jié)束時角度* @param centerX 旋轉(zhuǎn)中心x坐標* @param centerY 旋轉(zhuǎn)中心y坐標* @param depthZ 最遠到達的z軸坐標* @param reverse true 表示由從0到depthZ,false相反*/public Rotate3dAnimation(Context context, float fromDegrees, float toDegrees,float centerX, float centerY, float depthZ, boolean reverse) {mFromDegrees = fromDegrees;mToDegrees = toDegrees;mCenterX = centerX;mCenterY = centerY;mDepthZ = depthZ;mReverse = reverse;// 獲取手機像素密度 (即dp與px的比例)scale = context.getResources().getDisplayMetrics().density;}@Overridepublic void initialize(int width, int height, int parentWidth, int parentHeight) {super.initialize(width, height, parentWidth, parentHeight);mCamera = new Camera();}@Overrideprotected void applyTransformation(float interpolatedTime, Transformation t) {final float fromDegrees = mFromDegrees;float degrees = fromDegrees + ((mToDegrees - fromDegrees) * interpolatedTime);final float centerX = mCenterX;final float centerY = mCenterY;final Camera camera = mCamera;final Matrix matrix = t.getMatrix();camera.save();// 調(diào)節(jié)深度if (mReverse) {camera.translate(0.0f, 0.0f, mDepthZ * interpolatedTime);} else {camera.translate(0.0f, 0.0f, mDepthZ * (1.0f - interpolatedTime));}// 繞y軸旋轉(zhuǎn)camera.rotateY(degrees);camera.getMatrix(matrix);camera.restore();// 修正失真,主要修改 MPERSP_0 和 MPERSP_1float[] mValues = new float[9];matrix.getValues(mValues); //獲取數(shù)值mValues[6] = mValues[6]/scale; //數(shù)值修正mValues[7] = mValues[7]/scale; //數(shù)值修正matrix.setValues(mValues); //重新賦值// 調(diào)節(jié)中心點matrix.preTranslate(-centerX, -centerY);matrix.postTranslate(centerX, centerY);} }

修改后效果:

上下對比差別還是很大的,順便附上測試代碼吧,layout文件就不寫了,隨便放一個ImageView就行了。

setContentView(R.layout.activity_test_camera_rotate2); ImageView view = (ImageView) findViewById(R.id.img); assert view != null; view.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {// 計算中心點(這里是使用view的中心作為旋轉(zhuǎn)的中心點)final float centerX = v.getWidth() / 2.0f;final float centerY = v.getHeight() / 2.0f;//括號內(nèi)參數(shù)分別為(上下文,開始角度,結(jié)束角度,x軸中心點,y軸中心點,深度,是否扭曲)final Rotate3dAnimation rotation = new Rotate3dAnimation(MainActivity.this, 0, 180, centerX, centerY, 0f, true, 2);rotation.setDuration(3000); //設(shè)置動畫時長rotation.setFillAfter(true); //保持旋轉(zhuǎn)后效果rotation.setInterpolator(new LinearInterpolator()); //設(shè)置插值器v.startAnimation(rotation);} });

相機位置
我們可以使用translate和rotate來控制拍攝對象,也可以移動相機自身的位置,不過這些方法并不常用(看添加時間就知道啦)。

void setLocation (float x, float y, float z); // (API 12) 設(shè)置相機位置,默認位置是(0, 0, -8)float getLocationX (); // (API 16) 獲取相機位置的x坐標,下同 float getLocationY (); float getLocationZ ();

我們知道近大遠小,而物體之間的距離是相對的,讓物體遠離相機和讓相機遠離物體結(jié)果是一樣的,實際上設(shè)置相機位置基本可以使用translate替代。

雖然設(shè)置相機位置用處并不大,但還是要提幾點注意事項:

相機和View的z軸距離不能為0

這個比較容易理解,當你把一個物體和相機放在同一個位置的時候,相機是拍攝不到這個物體的,正如你拿一張卡片放在手機側(cè)面,攝像頭是拍攝不到的。

虛擬相機前后均可以拍攝

當View不斷接近攝像機并越過攝像機位置時,仍能看到View,并且View大小會隨著距離攝像機的位置越來越遠而逐漸變小,你可以理解為它有前置攝像頭和后置攝像頭。

攝像機右移等于View左移

View的狀態(tài)只取決于View和攝像機之間的相對位置,不過由于單位不同,攝像機平移一個單位等于View平移72個像素。下面兩段代碼是等價的:

Camera camera = new Camera(); camera.setLocation(1,0,-8); // 攝像機默認位置是(0, 0, -8) Matrix matrix = new Matrix(); camera.getMatrix(matrix); Log.e(TAG, "location: "+matrix.toShortString() );Camera camera2 = new Camera(); camera2.translate(-72,0,0); Matrix matrix2 = new Matrix(); camera2.getMatrix(matrix2); Log.e(TAG, "translate: "+matrix2.toShortString() );

結(jié)果:

location: [1.0, 0.0, -72.0][0.0, 1.0, 0.0][0.0, 0.0, 1.0] translate: [1.0, 0.0, -72.0][0.0, 1.0, 0.0][0.0, 0.0, 1.0]

要點

View顯示狀態(tài)取決于View和攝像機之間的相對位置
View和相機的Z軸距離不能為0
小技巧:關(guān)于攝像機和View的位置,你可以打開手機后置攝像頭,拿一張卡片來回的轉(zhuǎn)動平移或者移動手機位置,觀察卡片在屏幕上的變化,

總結(jié)
本篇主要講解了關(guān)于Camera和Matrix的一些基礎(chǔ)知識,Camera運用得當?shù)脑捠悄軌蛑圃斐龊芏囔趴岬男Ч?#xff0c;我這里算是拋磚引玉,推薦一些比較炫酷的控件。

FlipShare

從零開始打造一個Android 3D立體旋轉(zhuǎn)容器

參考資料
Camera
FlipShare
從零開始打造一個Android 3D立體旋轉(zhuǎn)容器

總結(jié)

以上是生活随笔為你收集整理的安卓自定义View进阶-Matrix Camera的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。

主站蜘蛛池模板: 国产欧美日韩91 | 国产亚洲小视频 | 亚洲精品粉嫩小泬20p | 手机av网址 | 亚洲第一黄色片 | 91搞| 一级大片网站 | 无码成人一区二区 | 日本肉体xxxⅹ裸体交 | 国产天堂av | 色婷婷久久五月综合成人 | 欧美精品亚洲精品日韩精品 | 午夜剧场黄色 | 日本在线加勒比 | 在线观看日本一区二区 | 国产精品aⅴ | 大又大又粗又硬又爽少妇毛片 | 九九热播视频 | 日本欧美亚洲 | 婷婷视频在线观看 | 国产 日韩 欧美 成人 | 干爹你真棒插曲mv在线观看 | 国产精品久久久不卡 | 强开乳罩摸双乳吃奶羞羞www | 国产成人自拍网 | 99精品一区二区三区无码吞精 | 亚洲欧美日韩中文字幕在线观看 | 91精品国产综合久久香蕉922 | 欧美激情国产精品免费 | 四虎影库| 一级a性色生活片久久毛片 爱爱高潮视频 | 色狠狠综合 | 人人爽人人干 | 国产成人免费片在线观看 | 福利在线一区 | 亚洲精品天堂成人片av在线播放 | 新狠狠干 | 久精品在线 | 牛牛视频在线观看 | 国产精品男人的天堂 | 国产精品九九九 | 久久99国产综合精品免费 | 99riav国产在线观看 | 日本成人精品视频 | 四虎在线精品 | 亚洲久久一区 | 五十路六十路七十路熟婆 | 一级视频免费观看 | 国产精品成av人在线视午夜片 | 中文字幕永久在线观看 | 亚洲一区二区三区日韩 | 久久久久一区二区 | 久久久新 | 人人妻人人爽欧美成人一区 | 二区中文字幕 | 在线免费av播放 | 影音先锋成人在线 | 绿帽av| 日韩精品国产AV | 精品国产一区二区三 | 日韩精品你懂的 | 成人公开视频 | 欧美黄色性生活 | 91综合精品 | 国产美女黄网站 | 国产最新精品视频 | 肉色丝袜脚交一区二区 | 国产精品入口a级 | 国产精品一区二区三区不卡 | 日本黄色大片免费看 | 美女扒开尿口来摸 | 亚洲av永久无码精品一百度影院 | 午夜诱惑痒痒网 | 成人在线欧美 | 青青草在线免费 | wwwwxxxx欧美| 美女靠逼app | 97超碰人人模人人人爽人人爱 | 欧美福利在线视频 | 素人一区| 免费成人av网址 | 91大神在线看 | 超碰日韩在线 | 99精品免费视频 | 色噜噜狠狠一区二区三区牛牛影视 | 精品少妇av | 男人操女人动态图 | 亚洲第一狼人区 | 9999免费视频| 国产亚洲色婷婷久久99精品 | 亚洲免费黄网 | 四虎综合网 | 欧美日韩一二三区 | 精品一区二区三区久久 | 毛片999 | 国模私拍在线观看 | 国产免费高清视频 | 丁香婷婷六月 | 激情综合网婷婷 |