首先大家可以看一下這個魚的圖案
一.前期準備
1.建立一個項目
2.将MainActivity的XML檔案寫成
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<ImageView
android:id="@+id/iv_fish"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"/>
</RelativeLayout>
相當于我們後面隻是對
ImageView
進行塗畫
3.建立fishDrawable類,繼承Drawable
不用多想,就把
fishDrawable
類看作圖檔即可
自定義
View
和
Drawable
差別不大,主要是
Drawable
使用起來更簡單一些
public class fishDrawable extends Drawable {
@Override
public void draw(@NonNull Canvas canvas) {
}
@Override
public void setAlpha(int alpha) {
}
@Override
public void setColorFilter(@Nullable ColorFilter colorFilter) {
}
@Override
public int getOpacity() {
return 0;
}
}
4.在MainActivity中将ImageView控件與fishDrawable類進行關聯
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//建立關聯
ImageView fishView = findViewById(R.id.iv_fish);
fishView.setImageDrawable(new fishDrawable());
}
}
二.具體實操
參考比例
分解圖
注意:由于代碼添加并不是非常的規律,是以有時可能會忽略非常小一部分代碼。但是大家不用擔心,在這篇文章的最後我會把完整代碼展示,并且會将項目上傳到github,并給對外連結接
1.其餘三個方法進行套公式
除了
draw
,其餘三個方法都是比較固定的寫法
2.新增兩個方法,固定大小
細心的朋友們可能發現了,我們一開始在
XML
中設定
ImageView
的大小的時候都是
Wrap_Content
,那總得固定一個預設大小吧,是以這兩個方法就用上了
(
HEAD_RADIUS
為魚頭的圓的半徑)
3.進行一些初始化
public class fishDrawable extends Drawable {
//建立路徑和畫筆
private Path mPath;
private Paint mPaint;
//設定畫筆的透明度
private int OTHER_ALPHA = 110;
//設定魚頭的圓的大小
private float HEAD_RADIUS = 100;
fishDrawable(){
init();
}
//初始化
private void init() {
mPath = new Path();
mPaint = new Paint();
mPaint.setStyle(Paint.Style.FILL);
//抗鋸齒
mPaint.setAntiAlias(true);
//防抖動
mPaint.setDither(true);
//設定顔色
mPaint.setARGB(OTHER_ALPHA,244,92,71);
}
@Override
public void draw(@NonNull Canvas canvas) {
}
//下面三個方法一般為固定寫法
@Override
public void setAlpha(int alpha) {
mPaint.setAlpha(alpha);
}
@Override
public void setColorFilter(@Nullable ColorFilter colorFilter) {
mPaint.setColorFilter(colorFilter);
}
@Override
public int getOpacity() {
return PixelFormat.TRANSLUCENT;
}
@Override
public int getIntrinsicWidth() {
//設定寬度
return (int) (8.38 * HEAD_RADIUS);
}
@Override
public int getIntrinsicHeight() {
//設定高度
return (int) (8.38 * HEAD_RADIUS);
}
}
4.确定魚的重心(PointF)
這裡我們中心和重心就不進行區分了,就通俗了解為魚的中心點那個位置。魚頭的中心點是以魚中心點為參考進行繪制的。
//儲存魚的重心
private PointF middlePoint;
fishDrawable(){
init();
}
//初始化
private void init() {
mPath = new Path();
mPaint = new Paint();
mPaint.setStyle(Paint.Style.FILL);
//抗鋸齒
mPaint.setAntiAlias(true);
//防抖動
mPaint.setDither(true);
//設定顔色
mPaint.setARGB(OTHER_ALPHA,244,92,71);
//确定魚的重心
middlePoint = new PointF(4.19f * HEAD_RADIUS,4.19F * HEAD_RADIUS);
}
5.寫出核心求坐标算法
此算法為後面所有算法的基礎。利用數學知識,此算法的思想是,
- 根據一個點的坐标,以及另外一個點到這個點的連線與X軸夾角,以及這條連線的長度,求出另外一個點的坐标。(數學知識,三角函數)
可能光說就有些抽象,我們看下面的圖
比如我們知道a點,知道ab這條直線的長度,知道角α的大小,就可以求出b點的坐标。這樣說大家應該就了解了。
比如這裡面求b的坐标
b點的橫坐标就是
a.x+ab*cosα
b點的縱坐标就是
a.y+ab*sinα
OK,知道了原理,我們就開始寫這個算法
private PointF calculatePoint(PointF startPoint,float length,float angle){
//x坐标的一部分
float deltaX = (float) (Math.cos(Math.toRadians(angle))*length);
//y坐标的一部分
float deltaY = (float) Math.sin(Math.toRadians(angle - 180))*length;
return new PointF(startPoint.x + deltaX,startPoint.y + deltaY);
}
注意:
①
Math.toRadians
是把角度換算為弧度,因為
Math.sin/cos
的參數是接收弧度的。
②由于安卓坐标系和數學坐标系的y軸正好相反,是以這裡的求y坐标時要
-180
度,以達到正好相反的效果
6.利用魚中心坐标計算魚頭圓心坐标
@Override
public void draw(@NonNull Canvas canvas) {
//魚的朝向
float fishAngle = fishMainAngle;
//計算魚頭的圓心坐标
PointF headPoint = calculatePoint(middlePoint,BODY_RADIUS/2,fishAngle);
//畫魚頭的圓
canvas.drawCircle(headPoint.x,headPoint.y,HEAD_RADIUS,mPaint);
}
效果如下
7.畫魚鳍
//設定魚鳍邊界點和魚頭中心點的距離
private float FIND_FINS_LENGTH = 0.9f * HEAD_RADIUS;
//魚鳍長度
private float FINS_LENGTH = 1.3f * HEAD_RADIUS;
@Override
public void draw(@NonNull Canvas canvas) {
//畫右魚鳍
PointF rightFinsPoint = calculatePoint(headPoint,FIND_FINS_LENGTH,fishAngle - 100);
makeFins(canvas,rightFinsPoint,fishAngle,true);
//畫左魚鳍
PointF leftFinsPoint = calculatePoint(headPoint,FIND_FINS_LENGTH,fishAngle + 100);
makeFins(canvas,leftFinsPoint,fishAngle,false);
}
畫魚鳍的算法
private void makeFins(Canvas canvas, PointF startFinsPoint, float fishAngle,boolean isRight) {
//設定二階貝塞爾曲線控制點的角度
float controlAngle = 110;//通過看示意圖得知,它要比上面那個110要大
//求出右魚鳍終點的坐标點
PointF endFinsPoint = calculatePoint(startFinsPoint,FINS_LENGTH,fishAngle - 180);
//求出控制點的坐标
PointF controlPoint = calculatePoint(startFinsPoint,FINS_LENGTH * 1.8f
,isRight ? fishAngle - controlAngle : fishAngle + controlAngle);
//畫線
mPath.reset();//首先别忘了reset
mPath.moveTo(startFinsPoint.x,startFinsPoint.y);
mPath.quadTo(controlPoint.x,controlPoint.y,endFinsPoint.x,endFinsPoint.y);
canvas.drawPath(mPath,mPaint);
}
效果
8.畫魚節肢
//大圓的半徑
private float BIG_CIRCLE_RADIUS = 0.7f * HEAD_RADIUS;
//中圓的半徑
private float MIDDLE_CIRCLE_RADIUS = 0.6f * BIG_CIRCLE_RADIUS;
//小圓的半徑
private float SMALL_CIRCLE_RADIUS = 0.4f * MIDDLE_CIRCLE_RADIUS;
//尋找尾部中圓圓心的線長
private final float FIND_MIDDLE_CIRCLE_LENGTH = BIG_CIRCLE_RADIUS * (0.6f + 1);
//尋找尾部小圓圓心的線長
private final float FIND_SMALL_CIRCLE_LENGTH = MIDDLE_CIRCLE_RADIUS * (0.4f + 2.7f);
@Override
public void draw(@NonNull Canvas canvas) {
//畫節肢1
//首先找到魚身體底部的中心點
PointF bodyBottomCenterPoint = calculatePoint(headPoint,BODY_LENGTH,fishAngle-180);
PointF middleCirclePoint = makeSegment(canvas,bodyBottomCenterPoint,MIDDLE_CIRCLE_RADIUS,
BIG_CIRCLE_RADIUS,FIND_MIDDLE_CIRCLE_LENGTH,fishAngle,true);
//畫節肢2
makeSegment(canvas,middleCirclePoint,SMALL_CIRCLE_RADIUS,MIDDLE_CIRCLE_RADIUS,
FIND_SMALL_CIRCLE_LENGTH,fishAngle,false);
}
畫節肢算法
private PointF makeSegment(Canvas canvas, PointF bottomCenterPoint,float smallRadius
,float bigRadius,float findSmallCircleLength, float fishAngle,boolean hasBigCircle) {
//根據梯形下底中心點的坐标,求出上底中心點的坐标
PointF upperCenterPoint = calculatePoint(bottomCenterPoint,findSmallCircleLength,fishAngle-180);
//求出梯形四個頂點的坐标
PointF bottomLeftPoint = calculatePoint(bottomCenterPoint,bigRadius,fishAngle + 90);
PointF bottomRightPoint = calculatePoint(bottomCenterPoint,bigRadius,fishAngle - 90);
PointF upperLeftPoint = calculatePoint(upperCenterPoint,smallRadius,fishAngle + 90);
PointF upperRightPoint = calculatePoint(upperCenterPoint,smallRadius,fishAngle - 90);
//畫大圓和中圓
//大圓隻有在節肢1的時候才會畫
if(hasBigCircle){
canvas.drawCircle(bottomCenterPoint.x,bottomCenterPoint.y, bigRadius,mPaint);
}
canvas.drawCircle(upperCenterPoint.x,upperCenterPoint.y,smallRadius,mPaint);
//畫梯形
mPath.reset();
mPath.moveTo(bottomLeftPoint.x,bottomLeftPoint.y);
mPath.lineTo(bottomRightPoint.x,bottomRightPoint.y);
mPath.lineTo(upperRightPoint.x,upperRightPoint.y);
mPath.lineTo(upperLeftPoint.x,upperLeftPoint.y);
canvas.drawPath(mPath,mPaint);
//将中圓的圓心坐标傳回
return upperCenterPoint;
}
效果展示
9.畫底邊三角形
//尋找大三角形底邊中心點的線長
private final float FIND_TRIANGLE_LENGTH = MIDDLE_CIRCLE_RADIUS * 2.7f;
//畫底邊三角形
@Override
public void draw(@NonNull Canvas canvas) {
//畫底邊三角形
makeTriangle(canvas,middleCirclePoint,FIND_TRIANGLE_LENGTH,BIG_CIRCLE_RADIUS,fishAngle);
makeTriangle(canvas,middleCirclePoint,FIND_TRIANGLE_LENGTH-20,BIG_CIRCLE_RADIUS - 10,fishAngle);
}
畫三角形的算法
private void makeTriangle(Canvas canvas, PointF middleCirclePoint, float findCenterLength,
float halfFdge,float fishAngle) {
//首先得到三角形底邊中點坐标
PointF triangleBottomPoint = calculatePoint(middleCirclePoint,findCenterLength,fishAngle + 180);
//然後計算三角形各個頂點的坐标
PointF leftPoint = calculatePoint(triangleBottomPoint,halfFdge,fishAngle + 90);
PointF rightPoint = calculatePoint(triangleBottomPoint,halfFdge,fishAngle - 90);
//畫線
mPath.reset();
mPath.moveTo(middleCirclePoint.x,middleCirclePoint.y);
mPath.lineTo(leftPoint.x,leftPoint.y);
mPath.lineTo(rightPoint.x,rightPoint.y);
canvas.drawPath(mPath,mPaint);
}
效果展示
10.畫身體
@Override
public void draw(@NonNull Canvas canvas) {
//畫身體
makeBody(canvas,headPoint,bodyBottomCenterPoint,fishAngle);
}
畫身體的算法
private void makeBody(Canvas canvas, PointF headPoint, PointF bodyBottomCenterPoint, float fishAngle) {
//首先得到身體的四個頂點
PointF topLeftPoint = calculatePoint(headPoint,HEAD_RADIUS,fishAngle + 90);
PointF topRightPoint = calculatePoint(headPoint,HEAD_RADIUS,fishAngle - 90);
PointF bottomLeftPoint = calculatePoint(bodyBottomCenterPoint,BIG_CIRCLE_RADIUS,fishAngle + 90);
PointF bottomRightPoint = calculatePoint(bodyBottomCenterPoint,BIG_CIRCLE_RADIUS,fishAngle - 90);
//然後得到控制點的坐标
PointF controlLeft = calculatePoint(headPoint,BODY_LENGTH*0.56F,fishAngle + 130);
PointF controlRight = calculatePoint(headPoint,BODY_LENGTH*0.56F,fishAngle - 130);
mPath.reset();
mPath.moveTo(topLeftPoint.x,topLeftPoint.y);
mPath.lineTo(topRightPoint.x,topRightPoint.y);
mPath.quadTo(controlRight.x,controlRight.y,bottomRightPoint.x,bottomRightPoint.y);
mPath.lineTo(bottomLeftPoint.x,bottomLeftPoint.y);
mPath.quadTo(controlLeft.x,controlLeft.y,topLeftPoint.x,topLeftPoint.y);
canvas.drawPath(mPath,mPaint);
}
效果展示
全部代碼
public class fishDrawable extends Drawable {
//建立路徑和畫筆
private Path mPath;
private Paint mPaint;
//設定畫筆的透明度
private int OTHER_ALPHA = 110;
//儲存魚的重心
private PointF middlePoint;
//設定魚的主要角度,與X軸的夾角
private float fishMainAngle = 0;
/**
* 與魚的長度有關的所有值
*/
//設定魚頭的圓的大小
private float HEAD_RADIUS = 100f;
//設定魚身的大小
private float BODY_LENGTH = 3.2f*HEAD_RADIUS;
//設定魚鳍邊界點和魚頭中心點的距離
private float FIND_FINS_LENGTH = 0.9f * HEAD_RADIUS;
//魚鳍長度
private float FINS_LENGTH = 1.3f * HEAD_RADIUS;
//大圓的半徑
private float BIG_CIRCLE_RADIUS = 0.7f * HEAD_RADIUS;
//中圓的半徑
private float MIDDLE_CIRCLE_RADIUS = 0.6f * BIG_CIRCLE_RADIUS;
//小圓的半徑
private float SMALL_CIRCLE_RADIUS = 0.4f * MIDDLE_CIRCLE_RADIUS;
//尋找尾部中圓圓心的線長
private final float FIND_MIDDLE_CIRCLE_LENGTH = BIG_CIRCLE_RADIUS * (0.6f + 1);
//尋找尾部小圓圓心的線長
private final float FIND_SMALL_CIRCLE_LENGTH = MIDDLE_CIRCLE_RADIUS * (0.4f + 2.7f);
//尋找大三角形底邊中心點的線長
private final float FIND_TRIANGLE_LENGTH = MIDDLE_CIRCLE_RADIUS * 2.7f;
fishDrawable(){
init();
}
//初始化
private void init() {
mPath = new Path();
mPaint = new Paint();
mPaint.setStyle(Paint.Style.FILL);
//抗鋸齒
mPaint.setAntiAlias(true);
//防抖動
mPaint.setDither(true);
//設定顔色
mPaint.setARGB(OTHER_ALPHA,244,92,71);
middlePoint = new PointF(4.19f * HEAD_RADIUS,4.19F * HEAD_RADIUS);
}
@Override
public void draw(@NonNull Canvas canvas) {
//魚的朝向
float fishAngle = fishMainAngle;
//計算魚頭的圓心坐标
PointF headPoint = calculatePoint(middlePoint,BODY_LENGTH/2,fishAngle);
//畫魚頭的圓
canvas.drawCircle(headPoint.x,headPoint.y,HEAD_RADIUS,mPaint);
//畫右魚鳍
PointF rightFinsPoint = calculatePoint(headPoint,FIND_FINS_LENGTH,fishAngle - 100);
makeFins(canvas,rightFinsPoint,fishAngle,true);
//畫左魚鳍
PointF leftFinsPoint = calculatePoint(headPoint,FIND_FINS_LENGTH,fishAngle + 100);
makeFins(canvas,leftFinsPoint,fishAngle,false);
//畫節肢1
//首先找到魚身體底部的中心點
PointF bodyBottomCenterPoint = calculatePoint(headPoint,BODY_LENGTH,fishAngle-180);
PointF middleCirclePoint = makeSegment(canvas,bodyBottomCenterPoint,MIDDLE_CIRCLE_RADIUS,
BIG_CIRCLE_RADIUS,FIND_MIDDLE_CIRCLE_LENGTH,fishAngle,true);
//畫節肢2
makeSegment(canvas,middleCirclePoint,SMALL_CIRCLE_RADIUS,MIDDLE_CIRCLE_RADIUS,
FIND_SMALL_CIRCLE_LENGTH,fishAngle,false);
//畫底邊三角形
makeTriangle(canvas,middleCirclePoint,FIND_TRIANGLE_LENGTH,BIG_CIRCLE_RADIUS,fishAngle);
makeTriangle(canvas,middleCirclePoint,FIND_TRIANGLE_LENGTH-20,BIG_CIRCLE_RADIUS - 10,fishAngle);
//畫身體
makeBody(canvas,headPoint,bodyBottomCenterPoint,fishAngle);
}
private void makeBody(Canvas canvas, PointF headPoint, PointF bodyBottomCenterPoint, float fishAngle) {
//首先得到身體的四個頂點
PointF topLeftPoint = calculatePoint(headPoint,HEAD_RADIUS,fishAngle + 90);
PointF topRightPoint = calculatePoint(headPoint,HEAD_RADIUS,fishAngle - 90);
PointF bottomLeftPoint = calculatePoint(bodyBottomCenterPoint,BIG_CIRCLE_RADIUS,fishAngle + 90);
PointF bottomRightPoint = calculatePoint(bodyBottomCenterPoint,BIG_CIRCLE_RADIUS,fishAngle - 90);
//然後得到控制點的坐标
PointF controlLeft = calculatePoint(headPoint,BODY_LENGTH*0.56F,fishAngle + 130);
PointF controlRight = calculatePoint(headPoint,BODY_LENGTH*0.56F,fishAngle - 130);
mPath.reset();
mPath.moveTo(topLeftPoint.x,topLeftPoint.y);
mPath.lineTo(topRightPoint.x,topRightPoint.y);
mPath.quadTo(controlRight.x,controlRight.y,bottomRightPoint.x,bottomRightPoint.y);
mPath.lineTo(bottomLeftPoint.x,bottomLeftPoint.y);
mPath.quadTo(controlLeft.x,controlLeft.y,topLeftPoint.x,topLeftPoint.y);
canvas.drawPath(mPath,mPaint);
}
private void makeTriangle(Canvas canvas, PointF middleCirclePoint, float findCenterLength,
float halfFdge,float fishAngle) {
//首先得到三角形底邊中點坐标
PointF triangleBottomPoint = calculatePoint(middleCirclePoint,findCenterLength,fishAngle + 180);
//然後計算三角形各個頂點的坐标
PointF leftPoint = calculatePoint(triangleBottomPoint,halfFdge,fishAngle + 90);
PointF rightPoint = calculatePoint(triangleBottomPoint,halfFdge,fishAngle - 90);
//畫線
mPath.reset();
mPath.moveTo(middleCirclePoint.x,middleCirclePoint.y);
mPath.lineTo(leftPoint.x,leftPoint.y);
mPath.lineTo(rightPoint.x,rightPoint.y);
canvas.drawPath(mPath,mPaint);
}
private PointF makeSegment(Canvas canvas, PointF bottomCenterPoint,float smallRadius
,float bigRadius,float findSmallCircleLength, float fishAngle,boolean hasBigCircle) {
//根據梯形下底中心點的坐标,求出上底中心點的坐标
PointF upperCenterPoint = calculatePoint(bottomCenterPoint,findSmallCircleLength,fishAngle-180);
//求出梯形四個頂點的坐标
PointF bottomLeftPoint = calculatePoint(bottomCenterPoint,bigRadius,fishAngle + 90);
PointF bottomRightPoint = calculatePoint(bottomCenterPoint,bigRadius,fishAngle - 90);
PointF upperLeftPoint = calculatePoint(upperCenterPoint,smallRadius,fishAngle + 90);
PointF upperRightPoint = calculatePoint(upperCenterPoint,smallRadius,fishAngle - 90);
//畫大圓和中圓
//大圓隻有在節肢1的時候才會畫
if(hasBigCircle){
canvas.drawCircle(bottomCenterPoint.x,bottomCenterPoint.y, bigRadius,mPaint);
}
canvas.drawCircle(upperCenterPoint.x,upperCenterPoint.y,smallRadius,mPaint);
//畫梯形
mPath.reset();
mPath.moveTo(bottomLeftPoint.x,bottomLeftPoint.y);
mPath.lineTo(bottomRightPoint.x,bottomRightPoint.y);
mPath.lineTo(upperRightPoint.x,upperRightPoint.y);
mPath.lineTo(upperLeftPoint.x,upperLeftPoint.y);
canvas.drawPath(mPath,mPaint);
//将中圓的圓心坐标傳回
return upperCenterPoint;
}
private void makeFins(Canvas canvas, PointF startFinsPoint, float fishAngle,boolean isRight) {
//設定二階貝塞爾曲線控制點的角度
float controlAngle = 110;//通過看示意圖得知,它要比上面那個110要大
//求出右魚鳍終點的坐标點
PointF endFinsPoint = calculatePoint(startFinsPoint,FINS_LENGTH,fishAngle - 180);
//求出控制點的坐标
PointF controlPoint = calculatePoint(startFinsPoint,FINS_LENGTH * 1.8f
,isRight ? fishAngle - controlAngle : fishAngle + controlAngle);
//畫線
mPath.reset();//首先别忘了reset
mPath.moveTo(startFinsPoint.x,startFinsPoint.y);
mPath.quadTo(controlPoint.x,controlPoint.y,endFinsPoint.x,endFinsPoint.y);
canvas.drawPath(mPath,mPaint);
}
private PointF calculatePoint(PointF startPoint,float length,float angle){
//x坐标的一部分
float deltaX = (float) (Math.cos(Math.toRadians(angle))*length);
//y坐标的一部分
float deltaY = (float) Math.sin(Math.toRadians(angle - 180))*length;
return new PointF(startPoint.x + deltaX,startPoint.y + deltaY);
}
//下面三個方法一般為固定寫法
@Override
public void setAlpha(int alpha) {
mPaint.setAlpha(alpha);
}
@Override
public void setColorFilter(@Nullable ColorFilter colorFilter) {
mPaint.setColorFilter(colorFilter);
}
@Override
public int getOpacity() {
return PixelFormat.TRANSLUCENT;
}
//設定寬度和高度
@Override
public int getIntrinsicWidth() {
return (int) (8.38 * HEAD_RADIUS);
}
@Override
public int getIntrinsicHeight() {
return (int) (8.38 * HEAD_RADIUS);
}
}