從0開始學自定義View -1

PS:好久沒有寫部落格了,之前的東西有所忘記,百度一下竟然查到了自己的寫過的部落格,訪問量還可以,一開始的寫部落格的初衷是把自己不會的記錄下來,現在沒想到也有博友會關注我,這就給了我動力,工作之餘把零零碎碎的東西總結一下,供大家參考。下面的博文是我自己的總結再加上博友的一些見解整合而成,共同進步。

初識View

在Android中所有的樣式都可以說是一個視圖,TextView,Button,ImageView…這些官方已經給出的view已經無法滿足我們的日常生活所需了,這個時候,我們就可以自定義View,隨心所欲創建屬於我們自己的View。那麼我們應該怎麼去做呢,首先要幹嘛,其次又幹嘛,最後幹嘛呢,這都是過程中的一個節點。下面呢我們就從第一步開始。

一張圖認識View

在這張圖中坐標系和我們數學中的不一樣,這裡的Y軸下方是正數,X軸右方是正數,其中的View(淺藍色背景)為我們自定義的View,MotionEvent是手指點擊的位置,我們對View進行移動,也是根據MotionEvent返回的xy坐標點進行繪製的。

View的坐標系

注意:View的坐標系統是相對於父控制項而言的.
getTop();       //獲取子View左上角距父View頂部的距離
getLeft();      //獲取子View左上角距父View左側的距離
getBottom();    //獲取子View右下角距父View頂部的距離
getRight();     //獲取子View右下角距父View左側的距離

MotionEvent中 get 和 getRaw 的區別

event.getX();       //觸摸點相對於其所在組件坐標系的坐標
event.getY();

event.getRawX();    //觸摸點相對於螢幕默認坐標系的坐標
event.getRawY();

這裡只是講解了一些View的獨有關於坐標的方法,因為在平常自定義的時候了解這些方法就已經可以對View進行操作了,比如說拖拽,拉伸,收縮等。

自定義View實戰

自定義View如何做,怎麼做,往往都是第一步比較難,之後對View美化就相對來說比較簡單了。下面我寫了幾個步驟

  • 繼承View
    • MyView extends View重寫裡面重要的3-4個構造方法
  • onMeasure測量控制項
    • onMeasure(int widthMeasureSpec, int heightMeasureSpec) 獲取螢幕,自定義View父組件尺寸等。
  • onDraw繪製View
    • onDraw(Canvas canvas)使用canvas去繪製View,並展示出來

我們就根據上面步驟一一解答

繼承View

我這裡寫了三個構造方法,也可以寫四個,但如果只寫一個會出現問題,比如說在XML文件中使用會報錯

public MyView(Context context) {
        super(context);
        init();
    }

    public MyView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        this.context = context;
        init();
    }

    public MyView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

onMeasure測量控制項

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    //以下大小均為獲取父布局尺寸
    measureWidth = MeasureSpec.getSize(widthMeasureSpec);
    measureHeight = MeasureSpec.getSize(heightMeasureSpec);
    maxWidth = getMaxWidth(context);
    maxHeight = getMaxHeight(context);
    Log.e("measure", maxWidth + " -- " + maxHeight);
    measureWidthMode = MeasureSpec.getMode(widthMeasureSpec);
    measureHeightMode = MeasureSpec.getMode(heightMeasureSpec);
    Log.e("measure", "measureWidth:" + measureWidth + "measureHeight:" + measureHeight + "measureWidthMode:" + measureWidthMode + "measureHeightMode:" + measureHeightMode);
}

// 獲取最大寬度
public int getMaxWidth(Context context) {
    WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
    DisplayMetrics dm = new DisplayMetrics();
    wm.getDefaultDisplay().getMetrics(dm);
    return dm.widthPixels;
}

// 獲取最大高度
public int getMaxHeight(Context context) {
    WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
    DisplayMetrics dm = new DisplayMetrics();
    wm.getDefaultDisplay().getMetrics(dm);
    return dm.heightPixels;
}

widthMeasureSpec,heightMeasureSpec 這兩個參數不是簡單的整數類型,而是2位整數(模式類型)和30位整數(實際數值) 的組合,所以我們要通過MeasureSpec 獲取模式int值 和 獲取數值int值。這個時候才是我們想要的尺寸,比如說螢幕是1080 * 1920,獲取的值也就是1080 * 1920。

onDraw繪製View

這個方法就厲害了,所有的繪製工作都是裡面的canvas去完成,canvas翻譯過來是帆布的意思,對我們來說就是畫布,畫布有了,還差畫筆,有筆有布有多彩墨水才能畫出大好河山嘛,這裡先介紹畫筆Paint

1:初始畫筆 —– Paint的使用

canvas.drawCircle(100, 100, 50, paint) 
這是一個要繪製圓形圖片的程式碼,兩個100分別是XY軸坐標,50是半徑,paint是畫筆,他的意思是說,我要畫一個圓點是(100,100)半徑是50的圓。樣式是paint,這個時候我們就要對paint去繪製,官方文檔上畫筆有 100 個左右的公開方法,常用方法標出顏色。下圖是網頁提供的表格。
返回值
簡介
int
getFlags()
獲取畫筆相關的一些設置(標誌)。
int
getFlags()
獲取畫筆相關的一些設置(標誌)。
void
setFlags(int flags)
設置畫筆的標誌位。
void
set(Paint src)
複製 src 的畫筆設置。
void
reset()
將畫筆恢復為默認設置。
int
getAlpha()
只返回顏色的alpha值。
void
setAlpha(int a)
設置透明度。
int
getColor()
返回畫筆的顏色。
void
setColor(int color)
設置顏色。
void
setARGB(int a, int r, int g, int b)
設置帶透明通道的顏色。
float
getStrokeWidth()
返回描邊的寬度。
void
setStrokeWidth(float width)
設置線條寬度。
Paint.Style
getStyle()
返回paint的樣式,用於控制如何解釋幾何元素(除了drawBitmap,它總是假定為FILL_STYLE)。
void
setStyle(Paint.Style style)
設置畫筆繪製模式(填充,描邊,或兩者均有)。
Paint.Cap
getStrokeCap()
返回paint的Cap,控制如何處理描邊線和路徑的開始和結束。
void
setStrokeCap(Paint.Cap cap)
設置線帽。
Paint.Join
getStrokeJoin()
返回畫筆的筆觸連接類型。
void
setStrokeJoin(Paint.Join join)
設置連接方式。
float
getStrokeMiter()
返回畫筆的筆觸斜接值。用於在連接角度銳利時控制斜接連接的行為。
void
setStrokeMiter(float miter)
設置畫筆的筆觸斜接值。用於在連接角度銳利時控制斜接連接的行為。
PathEffect
getPathEffect()
獲取畫筆的 patheffect 對象。
PathEffect
setPathEffect(PathEffect effect)
設置 Path 效果。
boolean
getFillPath(Path src, Path dst)
將任何/所有效果(patheffect,stroking)應用於src,並將結果返回到dst。
結果是使用此畫筆繪製繪製 src 將與使用默認畫筆繪製繪製 dst 相同(至少從幾何角度來說是這樣的)。
Style
簡介
Paint.Style.FILL
填充內容,也是畫筆的默認模式。
Paint.Style.STROKE
描邊,只繪製圖形輪廓。
Paint.Style.FILL_AND_STROKE
描邊+填充,同時繪製輪廓和填充內容。
下面我們只畫一個最簡單的
paint = new Paint();
paint.setColor(Color.BLACK);//畫筆顏色
paint.setStrokeWidth(5f);//邊的寬度
paint.setStyle(Paint.Style.STROKE);//描邊

 

 

 

上面canvas.drawCircle(100, 100, 50, paint)和paint的創建  都是寫在onDraw方法里的。上整體程式碼

@Override
        protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //樣式一
        canvas.drawCircle(200, 200, 100, paint);


        //樣式二
        Path arcPath = new Path();
        arcPath.addArc(new RectF(100, 100, 500, 500), 120, 300);

//        Paint paint = new Paint();
//        paint.setStyle(Paint.Style.STROKE);
//        canvas.drawPath(arcPath, paint);


        Path borderPath = new Path();
        Paint paint = new Paint();
        paint.setStyle(Paint.Style.STROKE);
        paint.setStrokeCap(Paint.Cap.ROUND);
        paint.setStrokeWidth(100);
        paint.getFillPath(arcPath, borderPath);    // getFillPath

// 測試畫筆,注意設置為 STROKE
        Paint testPaint = new Paint();
        testPaint.setStyle(Paint.Style.STROKE);
        testPaint.setStrokeWidth(2);
        testPaint.setAntiAlias(true);
// 繪製通過 getFillPath 獲取到的 Path
        canvas.drawPath(borderPath, testPaint);
    }

好了,到這裡對canvas畫簡單圖案是告一段落了,那麼我們之前獲取到的尺寸是幹嘛用的呢,下面我們對拖拽進行講解,拖拽其實就是down,move,up對著三者的一個解析,當我們手指按下的時候將會出發down,手指一動時觸發move,手指抬起時觸發up。那麼我們怎麼去監聽他的,有方法,那就是onTouchEvent,觸摸方法的事件分發機制我們下節講。這裡我們直接上程式碼

拖拽圖案

@Override
        public boolean onTouchEvent(MotionEvent event) {
        //手指按下
        int action = event.getAction();
        //獲取手機觸摸的坐標
        int x = (int) event.getX();
        int y = (int) event.getY();
        switch (action){
            case MotionEvent.ACTION_DOWN://按下,獲取小球初始的位置
                startLeft = getLeft();
                startRight = getRight();
                startTop = getTop();
                startBottom = getBottom();
                lastX = x;
                lastY = y;
                break;
            case MotionEvent.ACTION_MOVE://移動,小球跟隨手指的移動
                Log.e("event","getRawX():"+event.getRawX()+"  getRawY():"+event.getRawY());
                Log.e("event","getX():"+event.getX()+"  getY():"+event.getY());
                int offsetX = x - lastX;
                int offsetY = y - lastY;
                layout(getLeft()+offsetX,getTop()+offsetY,
                        getRight()+offsetX,getBottom()+offsetY);
                break;
            case MotionEvent.ACTION_UP://當手指抬起時,回到小球初始的位置
//                layout(startLeft, startTop, startRight, startBottom);
                break;
        }
        return true;
    }

好了,到這裡,就可以拖拽你畫的圖案了。下一篇講解View的事件分發機制。

 

Tags: