溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

Flutter手游操縱桿移動(dòng)的原理與實(shí)現(xiàn)方法

發(fā)布時(shí)間:2022-07-11 09:22:36 來源:億速云 閱讀:241 作者:iii 欄目:開發(fā)技術(shù)

這篇文章主要介紹“Flutter手游操縱桿移動(dòng)的原理與實(shí)現(xiàn)方法”,在日常操作中,相信很多人在Flutter手游操縱桿移動(dòng)的原理與實(shí)現(xiàn)方法問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”Flutter手游操縱桿移動(dòng)的原理與實(shí)現(xiàn)方法”的疑惑有所幫助!接下來,請跟著小編一起來學(xué)習(xí)吧!

繪制

繪制操縱桿的靜態(tài)圖形,玩過手游應(yīng)該知道操縱桿基本構(gòu)成由底部圓和手指移動(dòng)圓球組成,手指移動(dòng)的圓球圍繞底圓進(jìn)行360°旋轉(zhuǎn)從而控制角色朝不同方向移動(dòng)。

靜態(tài)效果

操縱桿的核心是由兩個(gè)圓形組成,代碼也非常簡單。

Flutter手游操縱桿移動(dòng)的原理與實(shí)現(xiàn)方法

繪制代碼:

// 底圓
canvas.drawCircle(
    Offset(0,0),
    bgR,
    _paint
      ..style = PaintingStyle.fill
      ..color = Colors.blue.withOpacity(0.2));
_paint.color = color;
_paint.style = PaintingStyle.stroke;


/// 手勢小圓
canvas.drawCircle(
    Offset(0,0),
    bgR / 3,
    _paint
      ..style = PaintingStyle.fill
      ..color = Colors.blue.withOpacity(0.9));

添加手勢交互 GestureDetector

大概思路:

當(dāng)點(diǎn)擊可觸控區(qū)域,將操縱桿移動(dòng)到當(dāng)前手指按下的位置,移動(dòng)手指,根據(jù)手指位置坐標(biāo)和按下時(shí)圓心位置坐標(biāo)計(jì)算偏移角度得出手指相對于底圓的坐標(biāo)點(diǎn),松開手指,操縱桿進(jìn)行復(fù)位回到初始位置。

手勢組件

return GestureDetector(
  child: CustomPaint(
    size: size,
    painter: JoyStickPainter(
        offset: _offset,
        offsetCenter: _offsetCenter,
        listenable: Listenable.merge([_offset, _offsetCenter])),
  ),

    // 按下
  onPanDown: down,
  // 移動(dòng)
  onPanUpdate: update,
  // 抬起
  onPanEnd: reset,
);

備注:手指觸控屏幕的坐標(biāo)點(diǎn)永遠(yuǎn)都是以左上角為原點(diǎn)的,為了方便理解和計(jì)算,我們同樣也需要將手指的坐標(biāo)的原點(diǎn)進(jìn)行偏移到畫布中央和畫布保持一致,所以這里我們通過手勢獲取的坐標(biāo)點(diǎn)之后需要進(jìn)行偏移。

不管手指點(diǎn)擊、移動(dòng)、還是抬起都要通知畫布進(jìn)行更新,這里使用ValueNotifier<Offset>通知坐標(biāo)點(diǎn)更新。

ValueNotifier<Offset> _offset = ValueNotifier(Offset.zero);

點(diǎn)擊交互 down: 當(dāng)用戶點(diǎn)擊可觸控區(qū)域,將大圓和小圓移動(dòng)至手指點(diǎn)擊的位置。

因?yàn)榈讏A在點(diǎn)擊之后抬起之前都是處于靜止?fàn)顟B(tài),當(dāng)移動(dòng)手指只有小圓移動(dòng),所以這里用兩個(gè)坐標(biāo)來保存底圓的圓心,和小圓的圓心,當(dāng)點(diǎn)擊時(shí),底圓和小圓的中心是一致的,所以這里當(dāng)點(diǎn)擊時(shí)同時(shí)更新兩個(gè)圓心位置。

down(DragDownDetails details) {
  Offset offset = details.localPosition;
  _offsetCenter.value = offset.translate(-size.width / 2, -size.height / 2);
  _offset.value = offset.translate(-size.width / 2, -size.height / 2);
}

這里需要注意的是,當(dāng)我們的手指點(diǎn)擊在可觸控區(qū)域邊界距離小于底圓半徑時(shí),需要控制圓心位置的x軸和y軸距離可觸控區(qū)域邊界距離大于等于底圓半徑。
如果不控制邊界點(diǎn)擊時(shí),操縱桿會偏離出觸控區(qū)域

Flutter手游操縱桿移動(dòng)的原理與實(shí)現(xiàn)方法

所以這里最好在點(diǎn)擊時(shí)可以加一個(gè)邊界處理,上下左右加一個(gè)邊界控制。

if (offset.dx > size.width - bgR) {
  offset = Offset(size.width - bgR, offset.dy);
}
if (offset.dx < bgR) {
  offset = Offset(bgR, offset.dy);
}
if (offset.dy > size.height - bgR) {
  offset = Offset(offset.dx, size.height - bgR);
}
if (offset.dy < bgR) {
  offset = Offset(offset.dx, bgR);
}

之后再點(diǎn)擊邊界時(shí)就不會出界了。

Flutter手游操縱桿移動(dòng)的原理與實(shí)現(xiàn)方法

移動(dòng)交互 update: 當(dāng)用戶移動(dòng)手指時(shí),小圓根據(jù)手指在底圓內(nèi)部進(jìn)行移動(dòng)。

手指移動(dòng)是操縱桿的核心交互邏輯。

思路: 當(dāng)手指點(diǎn)擊之后移動(dòng)離開圓心,計(jì)算當(dāng)前坐標(biāo)點(diǎn)以當(dāng)前底圓圓心為原點(diǎn)的偏移弧度,通過反正切函數(shù)atan2(y,x)可以得出當(dāng)前坐標(biāo)針對x軸向右為正,y軸向下為正的偏移弧度&alpha;,默認(rèn)范圍 [-pi]-[pi], 為了方便理解計(jì)算,這里我們將得到的角度+pi轉(zhuǎn)換為 0-2pi,角度范圍:0-360&deg;。見下圖:

Flutter手游操縱桿移動(dòng)的原理與實(shí)現(xiàn)方法

Offset類里的direction(y,x)方法就是通過atan2方法計(jì)算當(dāng)前坐標(biāo)的偏移弧度。

/// The angle of this offset as radians clockwise from the positive x-axis, in
/// the range -[pi] to [pi], assuming positive values of the x-axis go to the
/// right and positive values of the y-axis go down.

double get direction => math.atan2(dy, dx);

角色移動(dòng)的關(guān)鍵就是通過得出的偏移弧度來進(jìn)行不同方向的移動(dòng)。

核心代碼:

/// 手指移動(dòng)坐標(biāo)
var offsetTranslate = offset.value;
/// 操縱桿圓心坐標(biāo)
var offsetTranslateCenter = offsetCenter.value;
/// 計(jì)算當(dāng)前位置坐標(biāo)點(diǎn) 左半?yún)^(qū)域 X為負(fù)數(shù)
double x = offsetTranslateCenter.dx - offsetTranslate.dx;
/// y軸 下半?yún)^(qū)域 Y為負(fù)數(shù)
double y = offsetTranslateCenter.dy - offsetTranslate.dy;
/// 反正切函數(shù) 通過此函數(shù)可以計(jì)算出此坐標(biāo)旋轉(zhuǎn)的弧度 為正 代表X軸逆時(shí)針旋轉(zhuǎn)的角度 為負(fù) 順時(shí)針旋轉(zhuǎn)角度
/// 范圍 [-pi] - [pi]
double ata = atan2(y, x);
/// 默認(rèn)坐標(biāo)系范圍為-pi - pi  順時(shí)針旋轉(zhuǎn)坐標(biāo)系180度 變?yōu)?nbsp;0 - 2*pi;
var thta = ata + pi;
print("angle ${(180 / pi * thta).toInt()}");

這里手指移動(dòng)分為2種情況,手指在底圓內(nèi)部和手指在底圓外部。見下圖:

Flutter手游操縱桿移動(dòng)的原理與實(shí)現(xiàn)方法

Flutter手游操縱桿移動(dòng)的原理與實(shí)現(xiàn)方法

當(dāng)手指在底圓內(nèi)部,我們可以直接使用當(dāng)前手指傳遞的坐標(biāo)計(jì)算。

當(dāng)手指移動(dòng)到底圓外部,我們需要控制小圓的圓形坐標(biāo)不能跑到底圓的外部,控制小圓 不能超過底圓的的范圍,所以,這里需要進(jìn)行計(jì)算當(dāng)前手指的坐標(biāo)距離底圓圓心的距離有沒有超過底圓半徑,如果超出,需要計(jì)算小圓的臨界坐標(biāo)值。

有了偏移弧度&alpha;,我們就可以通過三角函數(shù)計(jì)算出上面x1,y1的坐標(biāo)點(diǎn),也就是當(dāng)前手指控制小圓圓心的臨界坐標(biāo)。

核心代碼:

/// 當(dāng)前手指坐標(biāo)距離底圓圓心長度
var r = sqrt(pow(x, 2) + pow(y, 2));
if (r > bgR) {
  var dx = bgR * cos(thta) + offsetTranslateCenter.dx; // x軸坐標(biāo)點(diǎn)
  var dy = bgR * sin(thta) + offsetTranslateCenter.dy; // y軸坐標(biāo)點(diǎn)
  offsetTranslate = Offset(dx, dy);
}

松開交互 reset: 當(dāng)用戶點(diǎn)擊可觸控區(qū)域,將大圓和小圓移動(dòng)至手指點(diǎn)擊的位置。

將兩個(gè)圓的圓心回歸坐標(biāo)系原點(diǎn)。

reset(DragEndDetails details) {
  _offset.value = Offset.zero;
  _offsetCenter.value = Offset.zero;
}

注意的是,當(dāng)點(diǎn)擊和松開時(shí),當(dāng)前角色都是不動(dòng)的,只有當(dāng)移動(dòng)時(shí)才傳遞角度值賦給角色進(jìn)行移動(dòng),所以當(dāng)這里需要判斷當(dāng)前手指觸控點(diǎn)和底圓圓心是否重合,如果重合表示當(dāng)前角色處于靜止?fàn)顟B(tài)。因?yàn)槟J(rèn)不作處理,弧度獲取的是pi,所以這里需要特殊處理一下。 這里我們需要將獲取的弧度值傳遞出去,如果當(dāng)前處于靜止?fàn)顟B(tài),將弧度設(shè)為負(fù)數(shù),因?yàn)槲覀兊幕《确秶?code>0-2pi,移動(dòng)狀態(tài)中不可能為負(fù)。

if (x == 0 && y == 0) {
  onAngle?.call(-1);
} else {
  onAngle?.call(thta);
}

為了方便展示效果,我加了坐標(biāo)軸輔助,這樣看起來更直觀一些。

最終效果:

通過當(dāng)前獲取的弧度值即可傳遞給角色進(jìn)行移動(dòng)。

Flutter手游操縱桿移動(dòng)的原理與實(shí)現(xiàn)方法

到此,關(guān)于“Flutter手游操縱桿移動(dòng)的原理與實(shí)現(xiàn)方法”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實(shí)踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識,請繼續(xù)關(guān)注億速云網(wǎng)站,小編會繼續(xù)努力為大家?guī)砀鄬?shí)用的文章!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI