前言
上篇文章分享了Flutter仿微信/微博九宮格。已實現類似微信/微博展示圖片九宮格,拖拽排序,微信群組頭像,釘釘群組頭像。
本次分享Flutter實現仿QQ討論組頭像.
參考資料
計算圓的半徑,這裡直接參考的YiiGuxing的CompositionAvatar專案,文章。
View寬D,藍色小圓半徑r,紅色大圓半徑R,Y軸上偏移量dy,頭像數量n。
正文
一、計算每個小圓的中心座標
- 首先需要得到大圓中心座標。這裡頭像數量n為3為例。
//中心座標x。
double centerX = width / 2;
//中心座標y, 因為3和5在y軸上有偏移量,需要單獨計算。
double centerY = width / 2;
//小圓半徑。
double r;
//大圓中心到小圓中心的半徑。
double r1;
switch (n) {
case 3:
r = width / (2 + 4 * math.sin(math.pi * (n - 2) / (2 * n)));
r1 = r / math.cos(math.pi * (n - 2) / (2 * n));
double R = r * (1 + math.sin(math.pi / n)) / math.sin(math.pi / n);
double dy = 0.5 * (width - R - r * (1 + 1 / math.tan(math.pi / n)));
centerY = dy + r + r1;
break;
}
複製程式碼
- 圓心座標通用公式,當n為2和4的時候初始角度為-45度。
for (int i = 0; i < n; i++) {
double degree1 = (n == 2 || n == 4) ? (-math.pi / 4) : 0;
double x = centerX + r1 * math.sin(degree1 + i * 2 * math.pi / n);
double y = centerY - r1 * math.cos(degree1 + i * 2 * math.pi / n);
}
複製程式碼
二、如何實現圓弧缺口
使用ClipPath元件,自定義clipper。
class QQClipper extends CustomClipper<Path> {
QQClipper({
this.total,
this.index,
this.initIndex: 1,
this.previousX,
this.previousY,
this.degree,
this.arcAngle: 60,
}) : assert(arcAngle != null && arcAngle >= 0 && arcAngle <= 180);
//頭像數量。
final int total;
//頭像index。
final int index;
//頭像initIndex。
final int initIndex;
//上一個圓心座標x。
final double previousX;
//上一個圓心座標y。
final double previousY;
//圓弧中心角度。
final double degree;
//圓弧角度。
final double arcAngle;
@override
Path getClip(Size size) {
double r = size.width / 2;
Path path = Path();
List<Offset> points = List();
//頭像數量為2,頭像index為initIndex時,保持整圓。
if (total == 2 && index == initIndex) {
path.addOval(Rect.fromLTRB(0, 0, size.width, size.height));
} else {
//圓弧路徑座標。
double spaceA = arcAngle / 2;
double startA = degree + spaceA;
double endA = degree - spaceA;
for (double i = startA; i <= 360 + endA; i = i + 1) {
double x1 = r + r * math.sin(d2r(i));
double y1 = r - r * math.cos(d2r(i));
points.add(Offset(x1, y1));
}
//圓弧缺口路徑座標。
double spaceB = math.atan(
r * math.sin(d2r(spaceA)) / (2 * r - r * math.cos(d2r(spaceA)))) /
math.pi *
180;
double r1 = (2 * r - r * math.cos(d2r(spaceA))) / math.cos(d2r(spaceB));
double startB = degree - 180 - spaceB;
double endB = degree - 180 + spaceB;
List<Offset> pointsB = List();
for (double i = startB; i < endB; i = i + 1) {
double x1 = previousX + r1 * math.sin(d2r(i));
double y1 = previousY - r1 * math.cos(d2r(i));
pointsB.add(Offset(x1, y1));
}
points.addAll(pointsB.reversed);
path.addPolygon(points, true);
}
return path;
}
/// degree to radian.
double d2r(double degree) {
return degree / 180 * math.pi;
}
@override
bool shouldReclip(CustomClipper<Path> oldClipper) {
return this != oldClipper;
}
}
複製程式碼