Android仿華為天氣繪制刻度盤(pán)
效果圖
可以看到這個(gè)自定義控件結(jié)合了顏色漸變、動(dòng)態(tài)繪制刻度、動(dòng)態(tài)水球效果。接下來(lái)我們就來(lái)看看這個(gè)效果是如何一步一步實(shí)現(xiàn)的。
開(kāi)始自定義控件
和很多自定義控件方式一樣需要去基礎(chǔ)某種View或者某種ViewGroup
我這里選擇的是View,如下所示:
- public class HuaWeiView extends View {
- /**
- * 用來(lái)初始化畫(huà)筆等
- * @param context
- * @param attrs
- */
- public HuaWeiView(Context context, @Nullable AttributeSet attrs) {
- super(context, attrs);
- }
- /**
- * 用來(lái)測(cè)量限制view為正方形
- * @param widthMeasureSpec
- * @param heightMeasureSpec
- */
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- }
- /**
- * 實(shí)現(xiàn)各種繪制功能
- * @param canvas
- */
- @Override
- protected void onDraw(Canvas canvas) {
- super.onDraw(canvas);
- }
- }
其中構(gòu)造方法用來(lái)布局中使用。
onMeasure()方法用來(lái)測(cè)量和限定view大小
onDraw()方法用來(lái)進(jìn)行具體的繪制功能
1.使用onMeasure()方法將View限制為一個(gè)正方形
只有確定了一個(gè)矩形才能夠去畫(huà)橢圓,如果這個(gè)矩形是正方形,橢圓也就隨之變成了圓形。
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- int width=MeasureSpec.getSize(widthMeasureSpec);
- int height = MeasureSpec.getSize(heightMeasureSpec);
- //以最小值為正方形的長(zhǎng)
- len=Math.min(width,height);
- //設(shè)置測(cè)量高度和寬度(必須要調(diào)用,不然無(wú)效果)
- setMeasuredDimension(len,len);
- }
分別通過(guò)MeasureSpec取得用戶設(shè)置的寬和高,然后取出最小值,設(shè)置給我們的view,這樣我們就做好了一個(gè)矩形
現(xiàn)在使用在布局中:
- <?xml version="1.0" encoding="utf-8"?>
- <LinearLayout 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"
- android:orientation="vertical"
- android:background="@color/colorPrimary"
- android:padding="20dp"
- tools:context="com.example.huaweiview.MainActivity">
- <com.example.huaweiview.HuaWeiView
- android:layout_gravity="center"
- android:background="@color/colorAccent"
- android:layout_width="200dp"
- android:layout_height="300dp"
- />
- </LinearLayout>
父布局背景為藍(lán)色背景,控件背景為粉色背景,而且設(shè)置的寬高不同,但是控件的顯示效果還是一個(gè)正方形,而且以小值為準(zhǔn)。我們的onMeasure()生效了
接下來(lái)就是如何在確定一個(gè)圓形區(qū)域了
2.onDraw()繪制圓形區(qū)域
繪制之前我們需要對(duì)Android中的坐標(biāo)系有個(gè)了解
我們都知道手機(jī)屏幕左上角為坐標(biāo)原點(diǎn),往右為X正軸,往下為Y正軸。其實(shí)手機(jī)頁(yè)面就是activity的展示界面,也是一個(gè)View。那可不可以說(shuō)所有的View在繪制圖形的時(shí)候都有自己的這么一個(gè)坐標(biāo)系呢(個(gè)人想法。。。)
也就是所每個(gè)View都有自己的一個(gè)坐標(biāo)系,比如現(xiàn)在的自定義View:
現(xiàn)在我們需要在我們自定義的view中繪制一個(gè)圓弧,那么這個(gè)圓弧的半徑就是我們自定義view的長(zhǎng)度的一半,即:
radius=len/2;
那么圓心的坐標(biāo)剛好是(radius,radius)
接下來(lái)開(kāi)始繪制
- @Override
- protected void onDraw(Canvas canvas) {
- super.onDraw(canvas);
- //畫(huà)圓弧的方法
- canvas.drawArc(oval, startAngle, sweepAngle, useCenter,paint);
- }
介紹一下繪制圓弧的方法:
- 參數(shù)一oval是一個(gè)RectF對(duì)象為一個(gè)矩形
- 參數(shù)二startAngle為圓弧的起始角度
- 參數(shù)三sweepAngle為圓弧的經(jīng)過(guò)角度(掃過(guò)角度)
- 參數(shù)四useCenter為圓弧是一個(gè)boolean值,為true時(shí)畫(huà)的是圓弧,為false時(shí)畫(huà)的是割弧
- 參數(shù)五paint為一個(gè)畫(huà)筆對(duì)象
也就是說(shuō)只要確定了一個(gè)矩形,在確定他起始和經(jīng)過(guò)的角度就能夠畫(huà)出一個(gè)圓弧(這點(diǎn)大家可以用畫(huà)板測(cè)試)
下來(lái)就是初始化這些參數(shù)
初始化矩形
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- int width = MeasureSpec.getSize(widthMeasureSpec);
- int height = MeasureSpec.getSize(heightMeasureSpec);
- //以最小值為正方形的長(zhǎng)
- len = Math.min(width, height);
- //實(shí)例化矩形
- oval=new RectF(0,0,len,len);
- //設(shè)置測(cè)量高度和寬度(必須要調(diào)用,不然無(wú)效果)
- setMeasuredDimension(len, len);
- }
畫(huà)矩形需要確定左上角和右下角的坐標(biāo)(通過(guò)畫(huà)板可以測(cè)試),通過(guò)上面的分析坐標(biāo)原點(diǎn)就是我們view的左上角,右下角的坐標(biāo)當(dāng)然就是len了。
接下來(lái)就是初始化起始和經(jīng)過(guò)角度
- private float startAngle=120;
- private float sweepAngle=300;
需要搞清楚往下為Y軸正軸,剛好和上學(xué)時(shí)候?qū)W的相反,也就是說(shuō)90度在下方,-90度在上方
初始化畫(huà)筆
- public HuaWeiView(Context context, @Nullable AttributeSet attrs) {
- super(context, attrs);
- paint =new Paint();
- //設(shè)置畫(huà)筆顏色
- paint.setColor(Color.WHITE);
- //設(shè)置畫(huà)筆抗鋸齒
- paint.setAntiAlias(true);
- //讓畫(huà)出的圖形是空心的(不填充)
- paint.setStyle(Paint.Style.STROKE);
- }useCenter=false
到這里真不容易呀,然而發(fā)現(xiàn)只畫(huà)個(gè)圓弧沒(méi)用呀,我要的是刻度線呀,canvas里面又沒(méi)用給我們提供畫(huà)刻度線的方法,這個(gè)時(shí)候就需要我們自己去寫(xiě)一個(gè)畫(huà)刻度線的方法了。
通過(guò)觀察圖片我們可以看出,所有的線都是從圓弧上的點(diǎn)為起點(diǎn)向某個(gè)方向畫(huà)一條直線,那么該如何確定這兩個(gè)點(diǎn)呢,需要我們做兩件事:
移動(dòng)坐標(biāo)系
- @Override
- protected void onDraw(Canvas canvas) {
- super.onDraw(canvas);
- //畫(huà)圓弧的方法
- canvas.drawArc(oval, startAngle, sweepAngle, useCenter,paint);
- //畫(huà)刻度線的方法
- drawViewLine(canvas);
- }
- private void drawViewLine(Canvas canvas) {
- //先保存之前canvas的內(nèi)容
- canvas.save();
- //移動(dòng)canvas(X軸移動(dòng)距離,Y軸移動(dòng)距離)
- canvas.translate(radius,radius);
- //操作完成后恢復(fù)狀態(tài)
- canvas.restore();
- }
我們自己寫(xiě)了一個(gè)繪制刻度線的方法并在onDraw()方法中調(diào)用。移動(dòng)坐標(biāo)系之前需要保存之前的canvas狀態(tài),然后X和Y軸分別移動(dòng)圓弧半徑的距離,如下圖:
canvas.translate(radius,radius);方法移動(dòng)的是坐標(biāo)系(通過(guò)實(shí)際效果和查資料所得)
canvas.save()和canvas.restore()要成對(duì)出現(xiàn),就好像流用完要關(guān)閉一樣。
***件事情完成后,開(kāi)始第二件事情,旋轉(zhuǎn)坐標(biāo)系
只通過(guò)移動(dòng)坐標(biāo)系,仍然很難確定圓弧點(diǎn)上的坐標(biāo),和另外一點(diǎn)的坐標(biāo),如果這兩個(gè)點(diǎn)都在坐標(biāo)軸上該多好呀,下面實(shí)現(xiàn):
- private void drawViewLine(Canvas canvas) {
- //先保存之前canvas的內(nèi)容
- canvas.save();
- //移動(dòng)canvas(X軸移動(dòng)距離,Y軸移動(dòng)距離)
- canvas.translate(radius,radius);
- //旋轉(zhuǎn)坐標(biāo)系
- canvas.rotate(30);
- //操作完成后恢復(fù)狀態(tài)
- canvas.restore();
畫(huà)刻度線的方法了增加了一個(gè)旋轉(zhuǎn)30度的代碼,旋轉(zhuǎn)后的坐標(biāo)系應(yīng)該怎么樣呢;
因?yàn)槠鹗键c(diǎn)和90度相差30,旋轉(zhuǎn)之后,起始點(diǎn)剛好落在了Y軸上,那么這個(gè)點(diǎn)的坐標(biāo)就很好確定了吧,沒(méi)錯(cuò)就是(0,radius);如果我們?cè)赮軸上在找一點(diǎn)不就可以畫(huà)出一條刻度線了嗎,那么它的坐標(biāo)是多少呢?對(duì),應(yīng)該是(0,radius-y),因?yàn)槲覀円鶅?nèi)部化刻度線,因此是減去一個(gè)值,趕快去試試吧,代碼如下:
- private void drawViewLine(Canvas canvas) {
- //先保存之前canvas的內(nèi)容
- canvas.save();
- //移動(dòng)canvas(X軸移動(dòng)距離,Y軸移動(dòng)距離)
- canvas.translate(radius,radius);
- //旋轉(zhuǎn)坐標(biāo)系
- canvas.rotate(30);
- Paint linePatin=new Paint();
- //設(shè)置畫(huà)筆顏色
- linePatin.setColor(Color.WHITE);
- //線寬
- linePatin.setStrokeWidth(2);
- //設(shè)置畫(huà)筆抗鋸齒
- linePatin.setAntiAlias(true);
- //畫(huà)一條刻度線
- canvas.drawLine(0,radius,0,radius-40,linePatin);
- //操作完成后恢復(fù)狀態(tài)
- canvas.restore();
- }
根據(jù)得到的兩個(gè)點(diǎn)的坐標(biāo),畫(huà)出來(lái)一條白線,如圖:
當(dāng)然這些點(diǎn)都是移動(dòng)后的坐標(biāo)系在旋轉(zhuǎn)30度得到的,這里畫(huà)好了一條線,如果畫(huà)多條呢,還是剛才的思路每次都讓它旋轉(zhuǎn)一個(gè)小角度然后畫(huà)條直線不就好了嗎,那么旋轉(zhuǎn)多少度呢,比如這里:總共掃過(guò)的角度sweepAngle=300;需要100條刻度,那么每次需要旋轉(zhuǎn)的角度rotateAngle=sweepAngle/100,具體代碼如下:
- private void drawViewLine(Canvas canvas) {
- //先保存之前canvas的內(nèi)容
- canvas.save();
- //移動(dòng)canvas(X軸移動(dòng)距離,Y軸移動(dòng)距離)
- canvas.translate(radius,radius);
- //旋轉(zhuǎn)坐標(biāo)系
- canvas.rotate(30);
- Paint linePatin=new Paint();
- //設(shè)置畫(huà)筆顏色
- linePatin.setColor(Color.WHITE);
- //線寬
- linePatin.setStrokeWidth(2);
- //設(shè)置畫(huà)筆抗鋸齒
- linePatin.setAntiAlias(true);
- //確定每次旋轉(zhuǎn)的角度
- float rotateAngle=sweepAngle/99;
- for(int i=0;i<100;i++){
- //畫(huà)一條刻度線
- canvas.drawLine(0,radius,0,radius-40,linePatin);
- canvas.rotate(rotateAngle);
- }
- //操作完成后恢復(fù)狀態(tài)
- canvas.restore();
- }
100個(gè)刻度,需要101次循環(huán)畫(huà)線(請(qǐng)看你的手表),畫(huà)完線就旋轉(zhuǎn)。依次循環(huán),如圖
經(jīng)過(guò)這么久的時(shí)間總于完成了刻度盤(pán)了,接下來(lái)就是去確定不同角度顯示什么樣的顏色,***我們需要確定要繪制的范圍targetAngle:
繪制有色部分
- private void drawViewLine(Canvas canvas) {
- //先保存之前canvas的內(nèi)容
- canvas.save();
- //移動(dòng)canvas(X軸移動(dòng)距離,Y軸移動(dòng)距離)
- canvas.translate(radius,radius);
- //旋轉(zhuǎn)坐標(biāo)系
- canvas.rotate(30);
- Paint linePatin=new Paint();
- //設(shè)置畫(huà)筆顏色
- linePatin.setColor(Color.WHITE);
- //線寬
- linePatin.setStrokeWidth(2);
- //設(shè)置畫(huà)筆抗鋸齒
- linePatin.setAntiAlias(true);
- //確定每次旋轉(zhuǎn)的角度
- float rotateAngle=sweepAngle/100;
- //繪制有色部分的畫(huà)筆
- Paint targetLinePatin=new Paint();
- targetLinePatin.setColor(Color.GREEN);
- targetLinePatin.setStrokeWidth(2);
- targetLinePatin.setAntiAlias(true);
- //記錄已經(jīng)繪制過(guò)的有色部分范圍
- float hasDraw=0;
- for(int i=0;i<=100;i++){
- if(hasDraw<=targetAngle&&targetAngle!=0){//需要繪制有色部分的時(shí)候
- //畫(huà)一條刻度線
- canvas.drawLine(0,radius,0,radius-40,targetLinePatin);
- }else {//不需要繪制有色部分
- //畫(huà)一條刻度線
- canvas.drawLine(0,radius,0,radius-40,linePatin);
- }
- //累計(jì)繪制過(guò)的部分
- hasDraw+=rotateAngle;
- //旋轉(zhuǎn)
- canvas.rotate(rotateAngle);
- }
- //操作完成后恢復(fù)狀態(tài)
- canvas.restore();
- }
我們需要不斷的去記錄繪制過(guò)的有效部分,之外的部分畫(huà)白色。
根據(jù)角度的比例,顏色漸變
需要計(jì)算出已經(jīng)繪制過(guò)的角度占總角度(300)的比例
- for(int i=0;i<=100;i++){
- if(hasDraw<=targetAngle&&targetAngle!=0){//需要繪制有色部分的時(shí)候
- //計(jì)算已經(jīng)繪制的比例
- float percent=hasDraw/sweepAngle;
- int red= 255-(int) (255*percent);
- int green= (int) (255*percent);
- targetLinePatin.setARGB(255,red,green,0);
- //畫(huà)一條刻度線
- canvas.drawLine(0,radius,0,radius-40,targetLinePatin);
- }else {//不需要繪制有色部分
- //畫(huà)一條刻度線
- canvas.drawLine(0,radius,0,radius-40,linePatin);
- }
- hasDraw+=rotateAngle;
- canvas.rotate(rotateAngle);
- }
只是在繪制有色部分的時(shí)候,利用三元素來(lái)實(shí)現(xiàn)漸變。所占比例越低紅色值越大,反正綠色值越大。
實(shí)現(xiàn)動(dòng)態(tài)顯示
先想一下它的運(yùn)動(dòng)情況,分為前進(jìn)狀態(tài)和后退狀態(tài),如果正在運(yùn)動(dòng)(一次完整的后退和前進(jìn)沒(méi)用結(jié)束),就不能開(kāi)始下次運(yùn)動(dòng),需要兩個(gè)參數(shù),state和isRunning
- //判斷是否在動(dòng)
- private boolean isRunning;
- //判斷是回退的狀態(tài)還是前進(jìn)狀態(tài)
- private int state = 1;
- public void changeAngle(final float trueAngle) {
- if (isRunning){//如果在動(dòng)直接返回
- return;
- }
- final Timer timer = new Timer();
- timer.schedule(new TimerTask() {
- @Override
- public void run() {
- switch (state) {
- case 1://后退狀態(tài)
- isRunning=true;
- targetAngle -= 3;
- if (targetAngle <= 0) {//如果回退到0
- targetAngle = 0;
- //改為前進(jìn)狀態(tài)
- state = 2;
- }
- break;
- case 2://前進(jìn)狀態(tài)
- targetAngle += 3;
- if (targetAngle >= trueAngle) {//如果增加到指定角度
- targetAngle = trueAngle;
- //改為后退狀態(tài)
- state = 1;
- isRunning=false;
- //結(jié)束本次運(yùn)動(dòng)
- timer.cancel();
- }
- break;
- default:
- break;
- }
- //重新繪制(子線程中使用的方法)
- postInvalidate();
- }
- }, 500, 30);
- }
利用時(shí)間任務(wù),每個(gè)30毫秒去執(zhí)行一次run方法,每次都重新繪制圖片,然后在activity中調(diào)用此方法
- HuaWeiView hwv;
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
- hwv= (HuaWeiView) findViewById(R.id.hwv);
- hwv.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- //點(diǎn)擊事件中,調(diào)用動(dòng)的方法
- hwv.changeAngle(200);
- }
- });
- }
看到這里了,相信你對(duì)坐標(biāo)系和角度動(dòng)態(tài)變化,以及刻度盤(pán)的繪制有了個(gè)很好的認(rèn)識(shí),多多驗(yàn)證會(huì)有助于理解。
接下來(lái)要實(shí)現(xiàn)背景動(dòng)態(tài)漸變
想想咱們的view中哪里用了漸變呢?對(duì),在繪制有色部分的時(shí)候,如果我們能將顏色漸變的值不斷的傳到activity中該多好呀,下面就要用接口傳值實(shí)現(xiàn)這一功能了:
- ***在自定義View中聲明一個(gè)內(nèi)部接口:
- private OnAngleColorListener onAngleColorListener;
- public void setOnAngleColorListener(OnAngleColorListener onAngleColorListener) {
- this.onAngleColorListener = onAngleColorListener;
- }
- public interface OnAngleColorListener{
- void colorListener(int red,int green);
- }
我們?cè)谧远xView中聲明一個(gè)內(nèi)部接口,并聲明一個(gè)全局接口對(duì)象,提供一個(gè)set方法
接口內(nèi)有個(gè)方法用來(lái)獲取顏色值
接下來(lái)就是在合適的地方調(diào)用這個(gè)方法,那么哪里呢,就是我們繪制顏色刻度時(shí)調(diào)用:
- for (int i = 0; i <= 100; i++) {
- if (hasDraw <= targetAngle && targetAngle != 0) {//需要繪制有色部分的時(shí)候
- //計(jì)算已經(jīng)繪制的比例
- float percent = hasDraw / sweepAngle;
- int red = 255 - (int) (255 * percent);
- int green = (int) (255 * percent);
- //實(shí)現(xiàn)接口回調(diào),傳遞顏色值
- if (onAngleColorListener!=null){
- onAngleColorListener.colorListener(red,green);
- }
- targetLinePatin.setARGB(255, red, green, 0);
- //畫(huà)一條刻度線
- canvas.drawLine(0, radius, 0, radius - 40, targetLinePatin);
- } else {//不需要繪制有色部分
- //畫(huà)一條刻度線
- canvas.drawLine(0, radius, 0, radius - 40, linePatin);
- }
我們?cè)诶L制的時(shí)候?qū)崿F(xiàn)了接口回調(diào),接下來(lái)去activity中實(shí)現(xiàn)接口
- public class MainActivity extends AppCompatActivity {
- HuaWeiView hwv;
- LinearLayout ll_parent;
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
- hwv= (HuaWeiView) findViewById(R.id.hwv);
- //實(shí)例父布局
- ll_parent= (LinearLayout) findViewById(R.id.ll_parent);
- hwv.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- //點(diǎn)擊事件中,調(diào)用動(dòng)的方法
- hwv.changeAngle(200);
- }
- });
- //設(shè)置角度顏色變化監(jiān)聽(tīng)
- hwv.setOnAngleColorListener(new HuaWeiView.OnAngleColorListener() {
- @Override
- public void colorListener(int red, int green) {
- Color color=new Color();
- //通過(guò)Color對(duì)象將RGB值轉(zhuǎn)為int類(lèi)型
- int backColor=color.argb(100,red,green,0);
- //父布局設(shè)置背景
- ll_parent.setBackgroundColor(backColor);
- }
- });
- }
- }
給父布局一個(gè)id,然后實(shí)例化。給我們的自定義控件設(shè)置一個(gè)角度顏色變化監(jiān)聽(tīng),從而拿到回調(diào)中傳過(guò)來(lái)的值,然后借助Color對(duì)象將RGB值轉(zhuǎn)為int值,再設(shè)置給父布局背景,這里背景稍稍透明一些。效果圖:

到了這里是不是感覺(jué)炫酷了不少呢,其實(shí)功能已經(jīng)實(shí)現(xiàn)的差不多了,接下來(lái)就是去繪制里面的內(nèi)容吧
繪制文字
當(dāng)然不去繪制文字也是可以的,你可以直接在布局中添加textview等。好話不多說(shuō),先分析一下繪制的過(guò)程吧,在刻度盤(pán)的內(nèi)部有一個(gè)小圓,然后這些文字就在小圓內(nèi),繪制小圓只需要讓它的半徑小點(diǎn)就OK了。
- /**
- * 繪制小圓和文本的方法,小圓顏色同樣漸變
- * @param canvas
- */
- private void drawScoreText(Canvas canvas) {
- //先繪制一個(gè)小圓
- Paint smallPaint = new Paint();
- smallPaint.setARGB(100,red,green,0);
- // 畫(huà)小圓指定圓心坐標(biāo),半徑,畫(huà)筆即可
- int smallRadius=radius-60;
- canvas.drawCircle(radius, radius, radius - 60, smallPaint);
- //繪制文本
- Paint textPaint=new Paint();
- //設(shè)置文本居中對(duì)齊
- textPaint.setTextAlign(Paint.Align.CENTER);
- textPaint.setColor(Color.WHITE);
- textPaint.setTextSize(smallRadius/2);
- //score需要通過(guò)計(jì)算得到
- canvas.drawText(""+score,radius,radius,textPaint);
- //繪制分,在分?jǐn)?shù)的右上方
- textPaint.setTextSize(smallRadius/6);
- canvas.drawText("分",radius+smallRadius/2,radius-smallRadius/4,textPaint);
- //繪制點(diǎn)擊優(yōu)化在分?jǐn)?shù)的下方
- textPaint.setTextSize(smallRadius/6);
- canvas.drawText("點(diǎn)擊優(yōu)化",radius,radius+smallRadius/2,textPaint);
- }
這里將之前漸變的red和green提為全局變量,先繪制一個(gè)小圓,畫(huà)筆顏色漸變。然后繪制文字分?jǐn)?shù)score需要通過(guò)計(jì)算的到
- //計(jì)算得到的分?jǐn)?shù)
- score=(int)(targetAngle/sweepAngle*100);
- //重新繪制(子線程中使用的方法)
- postInvalidate();
在時(shí)間任務(wù)中,每次繪制之前計(jì)算得到分?jǐn)?shù),然后在右上方畫(huà)一個(gè)固定值分,再在下方一個(gè)固定內(nèi)容點(diǎn)擊優(yōu)化(這個(gè)時(shí)候的坐標(biāo)已經(jīng)回到最初的模樣)
到此為止功能已經(jīng)寫(xiě)的差不多了,還有一個(gè)水波加速球效果,下篇博客中寫(xiě)吧。