1. 程式人生 > >Android自定義View 一

Android自定義View 一

為什麼要自定義View

android提供了很多控制元件供我們使用 但有些功能是系統所提供的實現不
了的 這時候我們就需要自定義一個View來實現我們所需要的效果.
在Android中所有的控制元件都直接或間接的繼承自View,分View和ViewGroup兩部分.
我們常用的一些View比如TextView,ImageView都是繼承自View並添加了一些各自的特性,ViewGroup也是繼承View但是它可以包含子View也就是我們經常用到的各種佈局比如LinearLayout,RelativeLayout等。

如何自定義佈局

所以我們要是實現一個自己的View那就整合View並新增一些自己需要的特性即可.
以下是一個最簡單的自定義View

public class MVIew extends View {
    /**
     * 建立一個繼承View的class
     *View一共有四個構造器 這裡先說兩個
     * 第一個構造器的引數就是context,這是在Activity例項化的時候所需的,比如Button button = new Button(context);
     * 第二個構造器有兩個引數 第一個同樣是context 第二個引數AttributeSet放入了一些屬性,這是我們在寫XML檔案時用到的比如
     * android:layout_width="match_parent"
     * android:layout_height="match_parent"如果不寫函式的話是無法通過XML新增View
     */
public MVIew(Context context) { super(context); } public MVIew(Context context, @Nullable AttributeSet attrs) { super(context, attrs); } //重寫onDraw方法 @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); canvas.drawColor(Color.RED); //設定canvas的背景色
float radius = 50; //給定半徑 //給定圓心的的座標 float cx = 50; float cy = 50; Paint paint = new Paint(); //例項化一個Paint物件 paint.setColor(Color.BLUE); //設定圓的顏色 //通過canvas的drawCircle方法畫一個圓圈. canvas.drawCircle(cx, cy, radius, paint); } }

這個自定義的View就是畫了一個圓圈,接下來就是讓這個view載入到activity上了.第一種就是直接在Activity上例項化一個並通過addView(View)來新增

 protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        MVIew mvIew = new MVIew(getApplicationContext());                                //例項化自定義的View
        RelativeLayout main_layout = (RelativeLayout) findViewById(R.id.main_layout);   //獲取佈局的物件
        main_layout.addView(mvIew);                                                     //向佈局檔案新增View
    }
}

第二種就是最常用的通過XML檔案去新增

<cn.lipengfei.myview.MVIew
    android:layout_width="wrap_content"
    android:layout_height="wrap_content" />

要注意的是標籤名要寫自定義的View的完整類名既包名.類名
以下是效果
這裡寫圖片描述
效果很簡單 就是一個紅色的畫板上有一個藍色的半徑為50的圓圈.
這裡有一個問題
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
明明width和height都是wrap_content為什麼出來的辣麼大.

測量

這涉及到View的測量.
Android由一個MeasureSpec的類,可以通過它來說實現測量.
MeasureSpec是一個int型的值,並且採用了位運算,高兩位為測量的模式,低30位是具體的值.
由三種測量模式

  • EXACTLY 精確模式 例如layout_height=”50dp”或是=”match_parent”
  • AT_MOST 最大值模式 就是warp_content
  • UNSPECIFIED 通過字面意思就是沒有指定尺寸

Android通過onMeasure()測量View的大小,預設情況下是EXACTLY模式,所以剛才的例子雖然寫了warp_concent依然沒有起作用,如果想實現AT_MOST模式那就需要我們重寫onMeasure()這個方法.

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        //先宣告兩個int值來表示最終的width和height並給定一個預設的大小
        int width_size  = 100;
        int height_size = 100;
        //使用MeasureSpec分別獲取width和height的MODE和SIZE
        int HEIGHT_MODE = MeasureSpec.getMode(heightMeasureSpec);
        int HEIGHT_SIZE = MeasureSpec.getSize(heightMeasureSpec);
        int WIDTH_MODE = MeasureSpec.getMode(widthMeasureSpec);
        int WIDTH_SIZE = MeasureSpec.getSize(widthMeasureSpec);
        if (HEIGHT_MODE == MeasureSpec.EXACTLY) {
            height_size = HEIGHT_SIZE;       //如果測量模式是精確的話 那麼就直接使用獲取到的值就好了
        }else if (HEIGHT_MODE == MeasureSpec.AT_MOST){  //如果是最大值模式的話 那麼久比較獲取的和設定的預設值那個小就使用那個.ps:Math.min(int a,int b)是比較數值大小的.
            height_size = Math.min(HEIGHT_SIZE,height_size);
        }
        if (WIDTH_MODE == MeasureSpec.EXACTLY){
            width_size = WIDTH_SIZE;
        }else if (WIDTH_MODE == MeasureSpec.AT_MOST){
            width_size = Math.min(WIDTH_SIZE,width_size);
        }
        //測量完畢後得到的了width和height通過setMeasuredDimension()設定width和height,真正決定具體大小的是setMeasuredDimension()的兩個引數.
        setMeasuredDimension(width_size,height_size);
    }

效果如下
這裡寫圖片描述
可以看到這個時候控制元件的大小已經變成所設定的預設值了.

onDraw(Canvas canvas)

測量完成後就有了大小接下來就是內容了,我們需要在view上顯示什麼就重寫onDraw來實現,以上例子是通過onDraw(Canvas canvas)繪製了一個圓圈.
首先要說的是Canvas,剛才的例子就是通過 canvas.drawCircle(cx, cy, radius, paint);這樣一個方法來畫了一個圓.這裡不對Canvas展開來說,只是說一些Canvas的簡單用法.
Canvas就是一個畫板,可以在這個畫板上話各種各樣的東西

Paint paint = new Paint();       //例項化一個Paint物件
      paint.setColor(Color.BLACK);
      paint.setStrokeWidth(10);//因為預設實在是太細了 設定了一個寬度值
      canvas.drawLine(0,0,100,100,paint);

這就是在畫了一條寬度為10的黑色的線drawLine()的前兩個引數為開始點的座標後兩個為結束點的座標,最後一個引數就是paint;
這裡寫圖片描述
還有很多方法可以調這裡就不一一列舉了,可以根據canvas.的提示來試試
晚些時候我會把我總結的一些方法寫出來方便初學者來看看.
除了Canvas外還有一個Paint的物件也總用到 這個是畫筆的意思,比如化園的例子就是通過這個畫筆來設定的園的顏色,還有畫線的例子也是通過畫筆來設定寬度值,如果我們吧之前的程式碼改一下

canvas.drawColor(Color.RED);      //設定canvas的背景色
        float radius = 50;                //給定半徑
        //給定圓心的的座標
        float cx = 50;
        float cy = 50;
        Paint paint = new Paint();       //例項化一個Paint物件
        paint.setColor(Color.BLACK);     //設定圓的顏色
        paint.setAntiAlias(true);        //設定抗鋸齒
        paint.setStyle(Paint.Style.STROKE);  //設定樣式
        paint.setStrokeWidth(3);          //設定寬度
        //通過canvas的drawCircle方法畫一個圓圈.
        canvas.drawCircle(cx, cy, radius, paint);

其他的都沒有變 只是添加了三個

        paint.setAntiAlias(true);        //設定抗鋸齒
        paint.setStyle(Paint.Style.STROKE);  //設定樣式
        paint.setStrokeWidth(3);          //設定寬度

這裡寫圖片描述

效果就是這個樣子 在樣式裡面設定了STROKE表示只描邊也就是空心效果與之相對的還有兩個屬性分別是FILL_AND_STROKE,FILL,剛開始有些人搞不清這倆有啥區別,譬如我.FILL就是填充上也就是一個實心的圓圈,FILL_AND_STROKE是不僅填充成一個實心圓而且還有邊框,Paint並沒有單獨給STROKE設定顏色的方法(至少我沒發現)
這倆的區別我舉個例子就很清楚了

paint.setStyle(Paint.Style.FILL);
        paint.setStrokeWidth(50); 

這是設定成FILL並新增邊框出來的效果

這裡寫圖片描述
大家可以試試無論setStrokeWidth(50)填入多少其效果是沒有任何區別的因為他就沒有邊框.

paint.setStyle(Paint.Style.FILL_AND_STROKE);
        paint.setStrokeWidth(50);  

改成FILL_AND_STROKE後
這裡寫圖片描述
這樣的效果圓已經超出去一部分了,設定了填充+邊框 不僅由填充效果還有邊框,給邊框設定了寬度值,再加上我設定的圓心座標正好是半徑值 所以邊框的部分就出去了.但如果我們不設setStrokeWidth的話 這兩者其實是沒有什麼區別的.另外要注意的是StrokeWidth的值是在園外面的,也就是說它並不佔用園的實際大小,比如園的半徑是100,這個半徑指的是填充的部分,當把StrokeWidth設定為100時 這個圓會變大
剛才我提到Paint並沒有設定STROKE的顏色的方法,所以我是通過兩個畫筆來實現的,通過多個畫筆多個畫布疊加圖層來實現我們想要的各種效果
比如我現在想要一個邊框為藍色的填充為黑色的圓圈.

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawColor(Color.RED);     
        float radius = 200;       
        float cx = 500;
        float cy = 500;
        Paint paint = new Paint();       //例項化Paint
        paint.setColor(Color.BLACK);     //設定圓的顏色
        paint.setAntiAlias(true);        //設定抗鋸齒
        paint.setStyle(Paint.Style.FILL_AND_STROKE);  //設定樣式
        Paint paint2 = new Paint();      //例項化第二個paint物件
        paint2.setColor(Color.BLUE);     //設定顏色為藍色
        paint2.setStyle(Paint.Style.STROKE);//設定樣式
        paint2.setStrokeWidth(30);          //設定邊框寬度
        //通過canvas的drawCircle方法畫一個圓圈.
        canvas.drawCircle(cx, cy, radius, paint);
        canvas.drawCircle(cx, cy, radius, paint2);
        }

這裡寫圖片描述
需要注意的是canvas.draw的先後順序 因為這兩個圓是一樣大小的 只是一個多一個邊框而已 所以先後是無所謂的.