在用導航軟件的時候,在導航過程中會有一個標誌指示你往那個方向走,因爲道路口多種多樣,出口也不一樣,如果都用圖片代替,會很佔應用大小,但是如果有一個自定義的轉向標,我們只需給他返回一個方向和出口的方向,自動顯示就好了。
這裏只定義了正常的轉向和環島的轉向標,自定義view用畫筆畫出來的,通過一系列的三角函數計算實現功能。
示例幾個典型的轉向標,灰色代表出口方向
右轉
左上轉彎
直行
環島的出口和方向
首先是要分析,這個圖標是怎麼實現的,我的思路是,直行的圖標分爲4個部分,分別爲三角形,第二段線段,圓弧,第一段線段
當然要去確定一箇中心點,這樣能確定出來這個圖標的位置和通過這個點去計算其他部分的座標的起始位置。我這裏定義了一些變量:
private Paint mLinePaint; // 畫直線的畫筆
private Paint mTrianglePaint; // 畫三角形的畫筆
private Paint mArcPaint; // 畫圓弧的畫筆
private Paint mOtherRoadPaint; // 畫其他路出口的畫筆
private float mRootWidth;
private float mRootHeight;
/**
* 轉向標基準點,假設控件寬爲w,高度爲h
* 右轉時,基準點爲 (1/3w,1/2h),如果出口方向爲13-15大角度的話,基準點爲(1/3w,1/3h)
* 左轉時,基準點爲 (2/3w,1/2h),如果出口方向爲1-3大角度的話,基準點爲(2/3w,1/3h)
* 直行時.基準點爲 (1/2w,1/2h)
*/
private float mBaseX;
private float mBaseY;
/**
* 普通icon的一些參數
*/
private float mMainRoadIconWidth; // 高亮圖標顯示的寬度 ------ 基準
private float mFirstLineHeight; // 第一段線的高度/長度 ------ 基準
private float mNormalCircleArcRadius; // 正常轉向標圓弧半徑
private float mSweepAngle; // 圓弧掃過的角度,即圓弧夾角
private float mNormalCircleArcCenterX; // 正常轉向標圓弧中心點X座標
private float mNormalCircleArcCenterY; // 正常轉向標圓弧中心點Y座標
private float mSecondLineStartX; // 第二段線段起點的x座標
private float mSecondLineStartY; // 第二段線段起點的y座標
private float mSecondLineEndX; // 第二段線段終點的x座標
private float mSecondLineEndY; // 第二段線段終點的y座標
/**
* 箭頭三角形的參數
*/
private float mTriangleLeftX; // 三角形左頂點x座標
private float mTriangleLeftY; // 三角形左頂點y座標
private float mTriangleRightX; // 三角形右頂點x座標
private float mTriangleRightY; // 三角形右頂點y座標
private float mTriangleTopX; // 三角形頂點x座標
private float mTriangleTopY; // 三角形頂點y座標
/**
* 環島icon的一些參數
*/
private float mRotaryRadius; // 環島圖標的圓半徑
private float mRotaryStartAngle; // 環島圓弧開始角度
private float mRotarySweepAngle; // 環島圓弧掃過的角度
private float mRotaryLinesHeight; // 環島線的高度
private float mRotaryAllExitLineHeight; // 環島其他出口顯示的長度
private float mRotaryMainRoadIconWidth; // 環島圖標高亮圖標顯示的寬度
/**
* 環島基準點
* 圓心位置控制圖標位置
*/
private float mRotaryCenterX; // 環島圖標圓的x座標
private float mRotaryCenterY; // 環島圖標圓的y座標
private boolean mIsTurnRight; // 是否是右轉
private boolean mIsBigAngleTurn; // 是否是大角度轉彎
以上變量的名字含義都有說明,重點說一下mBaseX,mBaseY這兩個,這是整個圖標的基準點,通過改變這個基準點會改變圖標的整體位置,上面有說明基準點的設置規則。
以右轉彎爲例,下面的這個圖形標識了每個點的座標計算,這樣求出每個點的座標就可以拼湊出我們想要的標誌了。
下面的這段代碼就是實現上面的運算各個點的座標,因爲需求不同,所以下面會有一些運算的個人規則,但是總體不變,因爲三角函數本來就是有正負的,比如sin90°和sin450°都等於1,sin90° = - sin270° 等等,這些都是三角函數的基本運算規則。所以我們沒有必要去分象限,分度數的正負來計算,只要座標系轉換正確穿進去的角度就可以算出正確的值。
private void calculateEverPoint() {
//當方向爲左邊的時候,轉變爲右邊對應的點,求出角度,用右邊對應的角度求座標後根據對稱軸對稱回來
int direction = mTurnIconModel.toDirection;
float secondLineHeight = direction == 0 ? 0 : mFirstLineHeight / 2;
if (direction < 8) {
direction = 16 - direction;// 如果是左轉,都以右轉計算後求對稱角度
}
float calculateAngle = direction * 22.5f - 270;
mSecondLineStartX = (float) (mNormalCircleArcCenterX + Math.sin(Math.toRadians(calculateAngle)) * mNormalCircleArcRadius);
mSecondLineStartY = (float) (mNormalCircleArcCenterY - Math.cos(Math.toRadians(calculateAngle)) * mNormalCircleArcRadius);
mSecondLineEndX = (float) (mSecondLineStartX + Math.cos(Math.toRadians(calculateAngle)) * secondLineHeight);
mSecondLineEndY = (float) (mSecondLineStartY + Math.sin(Math.toRadians(calculateAngle)) * secondLineHeight);
calculateTrianglePoint(calculateAngle, mMainRoadIconWidth * 3, mMainRoadIconWidth * 2);
//先計算一下右轉時候同樣角度的點的座標,如果是左轉的話,y座標不變,X左邊根據對稱軸求對稱的座標
if (!mIsTurnRight) {
mSecondLineStartX = calculateSymmetryPoint(mSecondLineStartX);
mSecondLineEndX = calculateSymmetryPoint(mSecondLineEndX);
mTriangleLeftX = calculateSymmetryPoint(mTriangleLeftX);
mTriangleRightX = calculateSymmetryPoint(mTriangleRightX);
mTriangleTopX = calculateSymmetryPoint(mTriangleTopX);
}
// 直行 畫圖有誤差
if (mTurnIconModel.toDirection == 8) {
mSecondLineStartY = mSecondLineStartY + 2f;
mSecondLineEndY = mSecondLineEndY - 2f;
}
}
上面示例的圖標三角形是類似於箭頭的,這種實現是用canvas的path繪製的
下面這段代碼是計算三角形的各個點座標,left和right座標變化是爲了拉長,這樣繪製的線就是類似於箭頭的
private void calculateTrianglePoint(float calculateAngle, float bottomLength, float height) {
mTriangleLeftX = (float) (mSecondLineEndX + Math.sin(Math.toRadians(calculateAngle)) * (bottomLength / 2));
mTriangleLeftY = (float) (mSecondLineEndY - Math.cos(Math.toRadians(calculateAngle)) * (bottomLength / 2));
mTriangleRightX = (float) (mSecondLineEndX - Math.sin(Math.toRadians(calculateAngle)) * (bottomLength / 2));
mTriangleRightY = (float) (mSecondLineEndY + Math.cos(Math.toRadians(calculateAngle)) * (bottomLength / 2));
mTriangleTopX = (float) (mSecondLineEndX + Math.cos(Math.toRadians(calculateAngle)) * height);
mTriangleTopY = (float) (mSecondLineEndY + Math.sin(Math.toRadians(calculateAngle)) * height);
//此計算是爲了讓三角形有凹角度
float offsetHeight = mTurnIconModel.type == TurnIconModel.Type.junction ? 8f : 4f;
mTriangleLeftX = (float) (mTriangleLeftX - Math.cos(Math.toRadians(calculateAngle)) * offsetHeight);
mTriangleLeftY = (float) (mTriangleLeftY - Math.sin(Math.toRadians(calculateAngle)) * offsetHeight);
mTriangleRightX = (float) (mTriangleRightX - Math.cos(Math.toRadians(calculateAngle)) * offsetHeight);
mTriangleRightY = (float) (mTriangleRightY - Math.sin(Math.toRadians(calculateAngle)) * offsetHeight);
}
而實現箭頭標誌連接的代碼爲:
private void drawTriangle(Canvas canvas) {
Path path = new Path();
path.moveTo(mTriangleTopX, mTriangleTopY);// 此點爲多邊形的起點
path.lineTo(mTriangleLeftX, mTriangleLeftY);
path.lineTo(mSecondLineEndX, mSecondLineEndY);
path.lineTo(mTriangleRightX, mTriangleRightY);
path.lineTo(mTriangleTopX, mTriangleTopY);
path.close(); // 使這些點構成封閉的多邊形
canvas.drawPath(path, mTrianglePaint);
}
哦對了,這是指右轉彎的代碼,左轉彎的時候,除了基礎點不一樣,剩下的都是根據圖形大小的中間進行x軸對稱處理的點,這樣一套計算就可以得到左右轉彎的代碼了。
畫環島的時候,其實計算方法和這個直行圖標一樣,都是根據三角函數計算的,下面已經給出圖了,計算的點也已經給出了,就不畫具體的步驟了,實現的思路都一樣