Android 自定義 View 之入門篇

KevenZheng發表於2019-04-18

讀前思考

學習一門技術或者看一篇文章最好的方式就是帶著問題去學習,這樣才能在過程中有茅塞頓開、燈火闌珊的感覺,記憶也會更深刻。

  1. 如何實現自定義 View?
  2. MeasureSpec 是什麼?有什麼作用?
  3. 如何自定義屬性值?
  4. 不同構造方法的作用?

1. 概述

什麼時候會用到自定義 View?在我們的日常開發中,可能會遇到一些介面、控制元件無法用 Android 系統內建的 View 來完成的,這時候就需要我們使用自定義 View 來進行繪製了。

自定義 View 這東西很多人會比較畏懼,如果你認為他比較難,關鍵還是缺少實踐寫得少;如果你認為很簡單,那可能是你沒有遇到過那些奇葩的效果,需要高等數學和各種演算法。

現在我們就一起敲開自定義 View 的大門,揭開它的神祕面紗,也許你就會發現,其實它並不可怕。

2. 瞭解自定義 View 的方法

本文主要做入門級別講解,固然不會太複雜,自定義 View 的時候,主要重寫兩個方法

onMeasure():用於測量,你的控制元件佔多大的地方由這個方法指定;

onMeasure( )方法中有兩個引數,widthMeasureSpec 和 heightMeasureSpec,可以通過如下程式碼獲取模式和大小

//獲取高度模式
int height_mode = MeasureSpec.getMode(heightMeasureSpec);
//獲取寬度模式
int with_mode = MeasureSpec.getMode(widthMeasureSpec);
//獲取高度尺寸
int height_size = MeasureSpec.getSize(heightMeasureSpec);
//獲取寬度尺寸
int width_size = MeasureSpec.getSize(widthMeasureSpec);
複製程式碼

測量模式的話,有下面三種

  • UNSPECIFIED:任意大小,想要多大就多大,儘可能大,一般我們不會遇到,如 ListView,RecyclerView,ScrollView 測量子 View 的時候給的就是 UNSPECIFIED ,一般開發中不需要關注它;

  • EXACTLY:一個確定的值,比如在佈局中你是這樣寫的 layout_width="100dp","match_parent","fill_parent";

  • AT_MOST:包裹內容,比如在佈局中你是這樣寫的 layout_width="wrap_content"。

onDarw():用於繪製,你的控制元件呈現給使用者長什麼樣子由這個方法決定;

onDarw( ) 方法中有個引數 Canvas,Canvas 就是我們要在上面繪製的畫布,我們可以使用我們的畫筆在上面進行繪製,最後呈現給使用者。

3. 實現自定義 View

接下來我們就動手實現一個簡單的自定義 View,重在講解實現自定義 View 的過程。

  1. 新建類繼承 View,不需要指定 style 的話,建立兩個建構函式即可,重寫 onMeasure( ) 和 onDraw( ) 方法。
public class ViewProperty extends View {
    /**
     *在java程式碼建立檢視的時候被呼叫,
     *如果是從xml填充的檢視,就不會呼叫這個
     */
    public ViewProperty(Context context) {
        super(context);
    }
    /**
     * 這個是在xml建立但是沒有指定style的時候被呼叫
     */
    public ViewProperty(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
    }

}
複製程式碼
  1. 新增自定義屬性。在 res/values/styles 檔案下新增自定義屬性,如果沒有此檔案,則新建即可。
<!-- 這裡自定義了兩個屬性,一個是預設大小,一個是背景顏色-->
<declare-styleable name="ViewProperty">
    <attr name="default_size" format="dimension"/>
    <attr name="backgroundColor" format="color"/>
</declare-styleable>
複製程式碼
  1. 在兩個引數的建構函式中獲取屬性,並進行畫筆初始化
public ViewProperty(Context context, @Nullable AttributeSet attrs) {
    super(context, attrs);
    
    //通過 TypedArray 獲取自定義屬性,使用完後記得及時回收
    TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.ViewProperty);
    mSize = array.getDimensionPixelSize(R.styleable.ViewProperty_default_size, 10);
    mColor = array.getColor(R.styleable.ViewProperty_backgroundColor, Color.RED);
    array.recycle();

    //初始化畫筆
    mPaint = new Paint();
    mPaint.setColor(mColor);
    }
複製程式碼
  1. onMeasure( )方法中做具體測量操作
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);

    int height = 100;
    int width = 100;
    //獲取寬高的測量模式及具體尺寸
    int height_mode = MeasureSpec.getMode(heightMeasureSpec);
    int with_mode = MeasureSpec.getMode(widthMeasureSpec);
    int height_size = MeasureSpec.getSize(heightMeasureSpec);
    int width_size = MeasureSpec.getSize(widthMeasureSpec);
    //根據具體測量模式來給定不同的尺寸
    if (height_mode == MeasureSpec.AT_MOST) {
        height = Dp2Px(getContext(),100);
    } else if (height_mode == MeasureSpec.EXACTLY) {
        height = height_size;
    }
    if (with_mode == MeasureSpec.AT_MOST) {
        width = Dp2Px(getContext(),100);
    } else if (with_mode == MeasureSpec.EXACTLY) {
        width = width_size;
    }
    //呼叫setMeasuredDimension()方法,傳入測量後的尺寸值
    setMeasuredDimension(width, height);

    }
複製程式碼
  1. 在 onDraw( ) 方法中做具體繪製工作
@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    //這裡繪製一個矩形
    //傳入左、上、右、下座標及畫筆
    canvas.drawRect(0,0,getWidth(),getHeight(),mPaint);
}

/**
*下面是兩個工具類,用於 dp 和 px 的轉換,
* 由於程式碼中使用的是 px,佈局中尺寸一般使用 dp,
* 所以需要做個轉換
*/
public static int Px2Dp(Context context, int px) {
    return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX, px, context.getResources().getDisplayMetrics());
}
public static int Dp2Px(Context context, int dpi) {
    return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dpi, context.getResources().getDisplayMetrics());
}
複製程式碼

4. 展示自定義 View

先 Rebuild 的一下專案,讓編譯器識別自定義 View,然後在佈局中引用

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
    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"
    tools:context="com.keven.jianshu.part3.ShowActivity">
    <com.keven.jianshu.part3.ViewProperty
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:backgroundColor="@color/colorAccent"/>
</android.support.constraint.ConstraintLayout>
複製程式碼

顯示效果

Android 自定義 View 之入門篇

全部程式碼見 GitHub 地址
github.com/keven0632/J…

文章已經讀到末尾了,不知道最初的幾個問題你都會了嗎?如果不會的話?可以再針對不會的問題進行精讀哦!答案都在文中,相信你肯定可以解決的!

相關文章