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的先後順序 因為這兩個圓是一樣大小的 只是一個多一個邊框而已 所以先後是無所謂的.