前言
在鴻蒙OS的廣闊天地中击敌,開(kāi)發(fā)者們有機(jī)會(huì)創(chuàng)造出令人驚嘆的用戶(hù)體驗(yàn)谓松。最近涡匀,我著手設(shè)計(jì)一款具有獨(dú)特滑動(dòng)效果的Swiper組件盯腌,它在滑動(dòng)時(shí)能夠迅速進(jìn)入視野,同時(shí)巧妙地將舊的cell隱藏到視線之外陨瘩。本文將分享如何利用鴻蒙的Swiper組件腕够,實(shí)現(xiàn)這一引人入勝的動(dòng)態(tài)效果。
一舌劳、設(shè)計(jì)與構(gòu)思
Swiper的設(shè)計(jì)理念是簡(jiǎn)潔而富有動(dòng)感帚湘。每個(gè)cell在滑動(dòng)時(shí)不僅會(huì)逐漸縮小至原始大小的70%,還會(huì)被前一個(gè)cell覆蓋甚淡,創(chuàng)造出一種流暢且連續(xù)的視覺(jué)效果大诸。這種效果的實(shí)現(xiàn),依賴(lài)于精確的動(dòng)畫(huà)控制和布局調(diào)整贯卦。
二资柔、代碼設(shè)計(jì)與實(shí)現(xiàn)思路
實(shí)現(xiàn)這一效果,我們需要對(duì)Swiper組件進(jìn)行深度定制脸侥。這包括對(duì)cell的尺寸建邓、位置和層級(jí)進(jìn)行動(dòng)態(tài)調(diào)整,以及利用貝塞爾曲線來(lái)實(shí)現(xiàn)平滑的動(dòng)畫(huà)效果睁枕。
三官边、控件采用與代碼說(shuō)明
3.1 Swiper組件定制
Swiper組件提供了豐富的API沸手,允許我們對(duì)其行為進(jìn)行精細(xì)控制。以下是一些關(guān)鍵的配置項(xiàng)和它們的作用:
-
itemSpace
: 控制cell之間的間距注簿。 -
indicator
: 是否顯示指示器契吉。 -
displayCount
: 設(shè)置同時(shí)展示的cell數(shù)量。 -
onAreaChange
: 當(dāng)Swiper區(qū)域大小變化時(shí)的回調(diào)诡渴。 -
customContentTransition
: 自定義內(nèi)容轉(zhuǎn)換動(dòng)畫(huà)捐晶。
Swiper組件基礎(chǔ)配置代碼:
Swiper()
.itemSpace(12)
.indicator(false)
.displayCount(this.DISPLAY_COUNT)
.padding({left:10, right:10})
.onAreaChange((oldValue, newValue) => {
// 處理區(qū)域變化邏輯
})
.customContentTransition({
transition: (proxy) => {
// 自定義轉(zhuǎn)換邏輯
}
});
3.2 Item組件設(shè)置
每個(gè)Item需要根據(jù)其在Swiper中的位置進(jìn)行尺寸、位置和層級(jí)的調(diào)整妄辩。這涉及到初始化相關(guān)變量惑灵,并在aboutToAppear
生命周期方法中進(jìn)行設(shè)置。
初始化寬高眼耀,初始化組件數(shù)據(jù):
@State cw: number = 0;
@State ch: number = 0;
aboutToAppear(): void {
initSwipe(...)
}
initSwipe(num:number){
this.translateList = []
for (let i = 0; i < num; i++) {
this.scaleList.push(0.8)
this.translateList.push(0.0)
this.zIndexList.push(0)
}
}
private MIN_SCALE: number = 0.70
private DISPLAY_COUNT: number = 4
private DISPLAY_WIDTH: number = 200
@State scaleList: number[] = []
@State translateList: number[] = []
@State zIndexList: number[] = []
Item尺寸和位置設(shè)置代碼:
LifeStyleItem({lifeStyleResponse: item})
.scale({ x: this.scaleList[index], y: this.scaleList[index] })
.translate({ x: this.translateList[index] })
.zIndex(this.zIndexList[index]);
在 customContentTransition的transition 屬性中設(shè)置屬性:
//scaleList 需要進(jìn)行線性變化
//translateList 位移需要進(jìn)行 數(shù)據(jù)偏移處理和貝塞爾曲線處理
//zIndexList 需要進(jìn)行位置層級(jí)設(shè)置
this.scaleList[proxy.index] = 線性函數(shù)
this.translateList[proxy.index] = - proxy.position * proxy.mainAxisLength + 貝塞爾曲線函數(shù)
this.zIndexList[proxy.index] = proxy.position
3.3 自定義動(dòng)畫(huà)效果
為了實(shí)現(xiàn)平滑的動(dòng)畫(huà)效果英支,我們定義了三次貝塞爾曲線函數(shù)和線性函數(shù)。這些函數(shù)將用于計(jì)算cell在滑動(dòng)過(guò)程中的尺寸哮伟、位置和層級(jí)變化干花。
三次貝塞爾曲線函數(shù):
function cubicBezier8(t, a1, b1, a2, b2) {
// 計(jì)算三次貝塞爾曲線的值
const k1 = 3 * a1;
const k2 = 3 * (a2 - b1) - k1;
const k3 = 1 - k1 - k2;
return k3 * Math.pow(t, 3) + k2 * Math.pow(t, 2) + k1 * t;
}
線性函數(shù):
function chazhi(startPosition, endPosition, startValue, endValue, position) {
// 計(jì)算線性插值的結(jié)果
const range = endPosition - startPosition;
const positionDifference = position - startPosition;
const fraction = positionDifference / range;
const valueRange = endValue - startValue;
const result = startValue + (valueRange * fraction);
return result;
}
3.4 計(jì)算函數(shù)實(shí)現(xiàn)
我們編寫(xiě)了計(jì)算函數(shù)來(lái)確定cell在Swiper中的最終表現(xiàn)。這包括根據(jù)位置計(jì)算尺寸楞黄、位置和層級(jí)池凄。
計(jì)算尺寸和位置的函數(shù):
function calculateValue(width: number, position: number): number {
const minValue = 0;
const normalizedPosition = position / 4;
// 計(jì)算貝塞爾曲線的緩動(dòng)值
const easedPosition = cubicBezier(normalizedPosition, 0.3, 0.1, 1, 0.05);
// 根據(jù)緩動(dòng)值計(jì)算最終的變化值
const value = minValue + (width - minValue) * easedPosition;
return value;
}
function calculateValueScale(position) {
if (position >= 2.5) {
// 當(dāng)position大于2時(shí),值固定為0.8
return 0.8;
} else if (position < 2.5) {
const startPosition = 2.5;
const endPosition = -1;
// 定義返回值的起始值和結(jié)束值
const startValue = 0.8;
const endValue = 0.7;
return chazhi(startPosition,endPosition,startValue,endValue,position)
}
return 0.7;
}
四鬼廓、全部代碼整合
將上述所有代碼片段整合到一個(gè)組件中肿仑,確保Swiper和每個(gè)Item都能夠根據(jù)用戶(hù)的滑動(dòng)操作動(dòng)態(tài)調(diào)整。
代碼如下:
function calculateValue(width: number, position: number): number {
const minValue = 0;
const normalizedPosition = position / 4;
const easedPosition = cubicBezier8(normalizedPosition, 0.3, 0.1, 1, 0.05);
const value = minValue + (width - minValue) * easedPosition;
return value;
}
function cubicBezier(t: number, a1: number, b1: number, a2: number, b2: number): number {
const k1 = 3 * a1;
const k2 = 3 * (a2 - b1) - k1;
const k3 = 1 - k1 - k2;
return k3 * Math.pow(t, 3) + k2 * Math.pow(t, 2) + k1 * t;
}
function calculateValueScale(position: number): number {
if (position >= 2.5) {
return 0.8;
} else if (position < 2.5) {
const startPosition = 2.5;
const endPosition = -1;
const startValue = 0.8;
const endValue = 0.7;
return chazhi(startPosition,endPosition,startValue,endValue,position)
}
return 0.7;
}
function chazhi(startPosition:number,endPosition:number,startValue:number,endValue:number,position:number):number{
const range = endPosition - startPosition;
const positionDifference = position - startPosition;
const fraction = positionDifference / range;
const valueRange = endValue - startValue;
const result = startValue + (valueRange * fraction);
return result;
}
@Component
struct Banner {
@State cw: number = 0;
@State ch: number = 0;
aboutToAppear(): void {
initSwipe()
}
initSwipe(num:number){
this.translateList = []
for (let i = 0; i < num; i++) {
this.scaleList.push(0.8)
this.translateList.push(0.0)
this.zIndexList.push(0)
}
}
private MIN_SCALE: number = 0.70
private DISPLAY_COUNT: number = 4
private DISPLAY_WIDTH: number = 200
@State scaleList: number[] = []
@State translateList: number[] = []
@State zIndexList: number[] = []
build(){
Swiper() {
ForEach(this.lifeStyleList, (item: LifeStyleResponse|null,index) => {
LifeStyleItem({lifeStyleResponse:item})
.scale({ x: this.scaleList[index], y: this.scaleList[index] })
.translate({ x: this.translateList[index] })
.zIndex(this.zIndexList[index])
}
)
}
.itemSpace(12)
.indicator(false)
.displayCount(this.DISPLAY_COUNT)
.padding({left:10,right:10})
.onAreaChange((oldValue,newValue)=>{
this.cw = new Number(newValue.width).valueOf()
this.ch = new Number(newValue.height).valueOf()
})
.customContentTransition({
transition :(proxy: SwiperContentTransitionProxy)=>{
this.scaleList[proxy.index] = calculateValueScale(proxy.position)
this.translateList[proxy.index] = - proxy.position * proxy.mainAxisLength + calculateValue8(this.cw,proxy.position)
this.zIndexList[proxy.index] = proxy.position
}
})
}
五桑阶、總結(jié)
通過(guò)本文的深入解析柏副,我們不僅實(shí)現(xiàn)了一個(gè)具有個(gè)性化動(dòng)態(tài)效果的Swiper組件勾邦,還學(xué)習(xí)了如何利用鴻蒙OS的強(qiáng)大API來(lái)定制動(dòng)畫(huà)和布局蚣录。希望這篇文章能夠激發(fā)更多開(kāi)發(fā)者的創(chuàng)造力,共同探索鴻蒙OS的無(wú)限可能眷篇。