自定義View全解,繪一個(gè)動(dòng)態(tài)“飛機(jī)大戰(zhàn)”

feiji.png
本文主要介紹自定義View繪圖的使用,上面是一個(gè)可拖動(dòng)的飛機(jī),并且不斷發(fā)射出子彈,完全使用自定義View繪圖實(shí)現(xiàn)的動(dòng)態(tài)效果,下面是一組動(dòng)態(tài)效果。
飛機(jī)大戰(zhàn).gif
下面我先介紹一下View中比較重要的幾個(gè)方法跟參數(shù):
  • onFinishlnflate(): 這是一個(gè)回調(diào)方法,當(dāng)應(yīng)用從XML布局文件加載該組件并利用它來構(gòu)造界面后,回調(diào)該方法。
  • onMeasure(int,int):調(diào)用改方法來檢測(cè)View組件以及所包含的所有子組件的大小。
  • omLayout(boolean,int,int,int,int):當(dāng)該組件需要分配其子組件的位置,大小時(shí)回調(diào)。
  • onSizeChanged(int,int,int,int):組件的大小發(fā)生改變的時(shí)候回調(diào)。
  • onDraw(Canvas):組件繪制內(nèi)容的時(shí)候調(diào)用該方法進(jìn)行繪制。
  • onKeyDown(int,KeyEvent):當(dāng)某個(gè)鍵被按下時(shí)觸發(fā)。
  • onKeyUp(int,KeyEvent):松開某個(gè)鍵時(shí)觸發(fā)。
  • onTrackballEvent(MotionEvent):發(fā)生軌跡球事件時(shí)觸發(fā)。
  • onTouchEvent(MotionEvent):發(fā)生觸摸屏事件時(shí)觸發(fā)。
  • onFocusChanged(boolean gainFocus,int direction,Rect previouslyFocusedRect):該組件焦點(diǎn)發(fā)生改變時(shí)觸發(fā)。
  • onWindowFocusChanged(boolean):包含改組件的窗口失去或者得到焦點(diǎn)時(shí)觸發(fā)。
  • onAttachedToWindow():把該組件放入某個(gè)窗口時(shí)觸發(fā)。
  • onDetachedFromWindow():把該組件從某個(gè)窗口上分離時(shí)觸發(fā)。
  • onWindowVisibilityChanged(int):包含該組件的窗口的可見性發(fā)生改變時(shí)觸發(fā)。
下面是本文的重點(diǎn),繪制圖形所涉及的重要幾何圖形繪制方法以及實(shí)例:
  1.     Paint p = new Paint();  
    
  2.     p.setColor(Color.RED);// 設(shè)置紅色  
    
  3.     canvas.drawText("畫圓:", 10, 20, p);// 畫文本  
    
  4.     canvas.drawCircle(60, 20, 10, p);// 小圓  
    
  5.     p.setAntiAlias(true);// 設(shè)置畫筆的鋸齒效果。
    
  6.     canvas.drawCircle(120, 20, 20, p);// 大圓  
    
  7.     canvas.drawText("畫線及弧線:", 10, 60, p);  
    
  8.     p.setColor(Color.GREEN);// 設(shè)置綠色  
    
  9.     canvas.drawLine(60, 40, 100, 40, p);// 畫線  
    
  10.     canvas.drawLine(110, 40, 190, 80, p);// 斜線  
    
  11.     //畫笑臉弧線  
    
  12.     p.setStyle(Paint.Style.STROKE);//設(shè)置空心  
    
  13.     RectF oval1=new RectF(150,20,180,40);  
    
  14.     canvas.drawArc(oval1, 180, 180, false, p);//小弧形  
    
  15.     oval1.set(190, 20, 220, 40);  
    
  16.     canvas.drawArc(oval1, 180, 180, false, p);//小弧形  
    
  17.     oval1.set(160, 30, 210, 60);  
    
  18.     canvas.drawArc(oval1, 0, 180, false, p);//小弧形   
    
  19.     canvas.drawText("畫矩形:", 10, 80, p);  
    
  20.     p.setColor(Color.GRAY);// 設(shè)置灰色  
    
  21.     p.setStyle(Paint.Style.FILL);//設(shè)置填滿  
    
  22.     canvas.drawRect(60, 60, 80, 80, p);// 正方形  
    
  23.     canvas.drawRect(60, 90, 160, 100, p);// 長(zhǎng)方形  
    
  24.     canvas.drawText("畫扇形和橢圓:", 10, 120, p);  
    
  25.     /* 設(shè)置漸變色 這個(gè)正方形的顏色是改變的 */  
    
  26.     Shader mShader = new LinearGradient(0, 0, 100, 100,  
    
  27.             new int[] { Color.RED, Color.GREEN, Color.BLUE, Color.YELLOW,  
    
  28.                     Color.LTGRAY }, null, Shader.TileMode.REPEAT); // 一個(gè)材質(zhì),打造出一個(gè)線性梯度沿著一條線。  
    
  29.     p.setShader(mShader);  
    
  30.     // p.setColor(Color.BLUE);  
    
  31.     RectF oval2 = new RectF(60, 100, 200, 240);// 設(shè)置個(gè)新的長(zhǎng)方形,掃描測(cè)量  
    
  32.     canvas.drawArc(oval2, 200, 130, true, p);  
    
  33.     // 畫弧,第一個(gè)參數(shù)是RectF:該類是第二個(gè)參數(shù)是角度的開始,第三個(gè)參數(shù)是多少度,第四個(gè)參數(shù)是真的時(shí)候畫扇形,是假的時(shí)候畫弧線  
    
  34.     //畫橢圓,把oval改一下  
    
  35.     oval2.set(210,100,250,130);  
    
  36.     canvas.drawOval(oval2, p);  
    
  37.     canvas.drawText("畫三角形:", 10, 200, p);  
    
  38.     // 繪制這個(gè)三角形,你可以繪制任意多邊形  
    
  39.     Path path = new Path();  
    
  40.     path.moveTo(80, 200);// 此點(diǎn)為多邊形的起點(diǎn)  
    
  41.     path.lineTo(120, 250);  
    
  42.     path.lineTo(80, 250);  
    
  43.     path.close(); // 使這些點(diǎn)構(gòu)成封閉的多邊形  
    
  44.     canvas.drawPath(path, p);  
    
  45.     // 你可以繪制很多任意多邊形,比如下面畫六連形  
    
  46.     p.reset();//重置  
    
  47.     p.setColor(Color.LTGRAY);  
    
  48.     p.setStyle(Paint.Style.STROKE);//設(shè)置空心  
    
  49.     Path path1=new Path();  //Path類是連接路徑
    
  50.     path1.moveTo(180, 200);  
    
  51.     path1.lineTo(200, 200);  
    
  52.     path1.lineTo(210, 210);  
    
  53.     path1.lineTo(200, 220);  
    
  54.     path1.lineTo(180, 220);  
    
  55.     path1.lineTo(170, 210);  
    
  56.     path1.close();//封閉  
    
  57.     canvas.drawPath(path1, p);  
    
  58.     //畫圓角矩形  
    
  59.     p.setStyle(Paint.Style.FILL);//充滿  
    
  60.     p.setColor(Color.LTGRAY);  
    
  61.     p.setAntiAlias(true);// 設(shè)置畫筆的鋸齒效果  
    
  62.     canvas.drawText("畫圓角矩形:", 10, 260, p);  
    
  63.     RectF oval3 = new RectF(80, 260, 200, 300);// 設(shè)置個(gè)新的長(zhǎng)方形  
    
  64.     canvas.drawRoundRect(oval3, 20, 15, p);//第二個(gè)參數(shù)是x半徑,第三個(gè)參數(shù)是y半徑    
    
  65.     //畫貝塞爾曲線  
    
  66.     canvas.drawText("畫貝塞爾曲線:", 10, 310, p);  
    
  67.     p.reset();  
    
  68.     p.setStyle(Paint.Style.STROKE);  
    
  69.     p.setColor(Color.GREEN);  
    
  70.     Path path2=new Path();  
    
  71.     path2.moveTo(100, 320);//設(shè)置Path的起點(diǎn)  
    
  72.     path2.quadTo(150, 310, 170, 400); //設(shè)置貝塞爾曲線的控制點(diǎn)坐標(biāo)和終點(diǎn)坐標(biāo)  
    
  73.     canvas.drawPath(path2, p);//畫出貝塞爾曲線    
    
  74.     //畫點(diǎn)  
    
  75.     p.setStyle(Paint.Style.FILL);  
    
  76.     canvas.drawText("畫點(diǎn):", 10, 390, p);  
    
  77.     canvas.drawPoint(60, 390, p);//畫一個(gè)點(diǎn)  
    
  78.     canvas.drawPoints(new float[]{60,400,65,400,70,400}, p);//畫多個(gè)點(diǎn)   
    
  79.     //畫圖片
    
  80.     Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher);  
    
  81.     canvas.drawBitmap(bitmap, 250,360, p);  
    
除了以上繪制的方法,Canvas還提供了如下方法進(jìn)行坐標(biāo)變換:
  • rotate(float degrees,float px,float py):對(duì)Canvas執(zhí)行旋轉(zhuǎn)變換。
  • scale(float sx,float sy,float px,float py):對(duì)Canvas執(zhí)行縮放變換。
  • skew(float sx,float sy):對(duì)Canvas執(zhí)行傾斜變換。
  • trnslate(float dx,float dy):移動(dòng)Canvas.向右移動(dòng)dx距離(為負(fù)數(shù)相反方向移動(dòng)),向下移動(dòng)dy距離(為負(fù)數(shù)相反方向移動(dòng))。
Paint 代表了Canvas上的畫筆、畫刷、顏料等等,Paint類常用方法:
  • setARGB(int a, int r, int g, int b) // 設(shè)置 Paint對(duì)象顏色,參數(shù)一為alpha透明值
  • setAlpha(int a) // 設(shè)置alpha不透明度,范圍為0~255
  • setAntiAlias(boolean aa) // 是否抗鋸齒
  • setColor(int color) // 設(shè)置顏色,這里Android內(nèi)部定義的有Color類包含了一些常見顏色定義
  • setTextScaleX(float scaleX) // 設(shè)置文本縮放倍數(shù),1.0f為原始
  • setTextSize(float textSize) // 設(shè)置字體大小
  • setUnderlineText(booleanunderlineText) // 設(shè)置下劃線
  • setStyle(Paint.Style style) //設(shè)置填充的風(fēng)格
  • setStrokeLayer(float radius,float dx,float dy,int color) //設(shè)置陰影。
  • setStrokeWidth(float width) //設(shè)置畫筆的筆觸寬度
學(xué)習(xí)了上面的API知識(shí),我們可以開始繪制自己需要的View跟效果了,下面是我模擬飛機(jī)射擊,繪出來的一個(gè)View。主要還是注意算法的寫入。
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.util.Log;import android.view.MotionEvent;
import android.view.View;
import java.util.ArrayList;
/**
 * Created by Amin on 2016/12/1. 
*/
public class ImageDragView extends View { 
   SheXian sheXian;
    private float x1 = 500;
    private float y1 = 1000;
    private float ZHIDAN = 50;
    private Bitmap bitmap;
    public final ArrayList<SheXian> balls  = new ArrayList<SheXian>();    float time2 = 0;
    public ImageDragView(Context context) {
        super(context);
        iniData();
    }
    public ImageDragView(Context context, AttributeSet attrs) {
        super(context, attrs);
        iniData(); 
   } 
   public ImageDragView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        iniData();
    } 
   protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        // TODO Auto-generated method stub
        // 如果觸碰事件不是按下、移動(dòng)事件
        if (event.getAction() != MotionEvent.ACTION_DOWN                && event.getAction() != MotionEvent.ACTION_MOVE) {
            return false;
        }
        if (Math.abs(x1 - event.getX()) < 100 && Math.abs(y1 - event.getY()) < 100) {
            x1 = event.getX();
            y1 = event.getY();
//            if (balls.size() > 100) {
//                balls.clear();
//            }
//            sheXian.setY1(y1);
//            sheXian.setX1(x1);
//            balls.add(sheXian);
        } 
       if (Math.abs(getHeight() / 2 - x1) < 150 && Math.abs(getWidth() / 2 - y1) < 150) { 
           time2 = 0;
        }
        invalidate();
        return true;
    } 
   @Override
    protected void onDraw(Canvas canvas) {
        // TODO Auto-generated method stub
        super.onDraw(canvas);
        Paint paint = new Paint();
        paint.setColor(Color.RED);
        canvas.drawBitmap(bitmap, x1 - 72, y1 - 72, paint);
        DrawLine(canvas, paint, time2, x1, y1);
        paint.setColor(Color.BLUE);
        canvas.drawCircle(getHeight() / 2, getWidth() / 2, 100, paint);
        paint.setColor(Color.RED);
        paint.setTextSize(40);
        canvas.drawText("補(bǔ)充彈藥", getHeight() / 2 - 70, getWidth() / 2, paint);        drawMyLine(canvas, paint);

        if (true) {
            invalidate();
        }
        time2 = time2 + 20;
        if (time2 >= 7500) {
            time2 = 3750;
            drawMyLine(canvas, paint);
        }
    }
    private void drawMyLine(Canvas canvas, Paint paint) {
        Log.i("mytag", "balls.size()=" + balls.size());
        for (int i = 0; i < 50; i++) {
            DrawLine(canvas, paint, time2 - 150 * i, x1, y1);
        }
    }
//
//    private float X(int i) {
//        Log.i("mytag", "i=" + i);
//        if (balls.size() != 0) {
//            Log.i("mytag", "balls.get(0).getX1()=" + balls.get(0).getX1());
//            return balls.get(balls.size() - 1).getX1();
//
//        } else {
//            Log.i("mytag", "x1=" + x1);
//            return x1;
//        }
//
//    }
//
//    private float Y(int i) {
//
//        if (balls.size() != 0) {
//            Log.i("mytag", "balls.get(0).getX1()=" + balls.get(0).getY1());
//            return balls.get(balls.size() - 1).getY1();
//        } else {
//            return y1;
//        }
//    } 
   private void DrawLine(Canvas canvas, Paint paint, float time, float x2, float y2) {
        if (time > 0) {
            canvas.drawLine(x2 + X3(time * (float) Math.sqrt(2) / 2), y2 + Y3(time * (float) Math.sqrt(2) / 2),
                    x2 + X3(time * (float) Math.sqrt(2) / 2 + ZHIDAN), y2 + Y3(time * (float) Math.sqrt(2) / 2 + ZHIDAN), paint);
            canvas.drawLine(x2 - time * (float) Math.sqrt(2) / 2, y2 - time * (float) Math.sqrt(2) / 2,
                    x2 - time * (float) Math.sqrt(2) / 2 - ZHIDAN, y2 - time * (float) Math.sqrt(2) / 2 - ZHIDAN, paint);
            canvas.drawLine(x2 + time * (float) Math.sqrt(2) / 2, y2 - time * (float) Math.sqrt(2) / 2,
                    x2 + time * (float) Math.sqrt(2) / 2 + ZHIDAN, y2 - time * (float) Math.sqrt(2) / 2 - ZHIDAN, paint);
            canvas.drawLine(x2 - X3(time * (float) Math.sqrt(2) / 2), y2 + Y3(time * (float) Math.sqrt(2) / 2),
                    x2 - X3(time * (float) Math.sqrt(2) / 2 - ZHIDAN), y2 + Y3(time * (float) Math.sqrt(2) / 2 - ZHIDAN), paint);
            canvas.drawLine(x2 - time, y2, x2 - time - ZHIDAN, y2, paint);
            canvas.drawLine(x2, y2 - time, x2, y2 - time - ZHIDAN, paint);
            canvas.drawLine(x2 + time, y2, x2 + time + ZHIDAN, y2, paint);
            // canvas.drawLine(x1, y1 + time, x1, y1 + time + 70, paint);
        } 
   } 
   private float X3(float x) {
        if (x < 300) { 
           return x;
        } else { 
           return 300;
        }
    } 
   private float Y3(float y) {
        if (y < 300) {
            return y;
        } else { 
           return -y + 620;
        } 
   }
    private void iniData() {
        Resources res = getResources();
        bitmap = BitmapFactory.decodeResource(res, R.drawable.feiji2);
        sheXian = new SheXian(); 
   }
}

代碼中我添加的監(jiān)聽事件是onTouchEvent,并且判斷了手勢(shì)的滑動(dòng),排除了非按下跟移動(dòng)事件。為了實(shí)現(xiàn)拖動(dòng)效果,在拖動(dòng)的范圍必須在飛機(jī)的正負(fù)100PX之內(nèi)。當(dāng)X1 Y1發(fā)生改變時(shí),界面也開始重繪。因?yàn)槲以诶L制方法內(nèi)寫入一個(gè)死循環(huán),不停地刷新界面繪制調(diào)用invalidate()方法。而子彈的射出,是循環(huán)繪制的50發(fā)子彈,并寫入算法,不停的改變x y 的坐標(biāo),成規(guī)律型增長(zhǎng)。就實(shí)現(xiàn)了移動(dòng)效果。在iniData()方法中,獲取資源圖片,就是飛機(jī)的圖片格式轉(zhuǎn)換為bitmap位圖。另外就是我直接手機(jī)測(cè)試完成,分辨率為1080*1920的,在代碼中并沒有進(jìn)行分辨率的適配,直接用的PX像素單位,不同的手機(jī)運(yùn)行起來肯定有差異了,注意更改適配。
完全使用刷新重繪完成的View,所以動(dòng)畫的效果更改不是那么完善,后面我會(huì)用ObjectAnimator來介紹自定義View跟自定義的屬性動(dòng)畫所結(jié)合,更靈活的實(shí)現(xiàn)子彈獨(dú)立化的移動(dòng)。本文就介紹到這里,不懂的地方和不足之處請(qǐng)留言,謝謝支持。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容