Android自定義控制元件之基本原理

總李寫程式碼發表於2016-07-27

前言:

     在日常的Android開發中會經常和控制元件打交道,有時Android提供的控制元件未必能滿足業務的需求,這個時候就需要我們實現自定義一些控制元件,今天先大致瞭解一下自定義控制元件的要求和實現的基本原理。

     自定義控制元件相關文章地址:

自定義控制元件要求:

     1. 應當遵守Android標準的規範(命名,可配置,事件處理等)。
     2. 在XML佈局中可配置控制元件的屬性。
     3. 對互動應當有合適的反饋,比如按下,點選等。
     4. 具有相容性, Android版本很多,應該具有廣泛的適用性。

自定義控制元件學習步驟:

  1 .View的工作原理 
  2 .編寫View類 
  3.為View類增加屬性 
  4 .繪製螢幕 
  5. 響應使用者訊息 
  6 .自定義回撥函式

自定義控制元件兩種方式:

  1. 繼承ViewGroup 

      例如:ViewGroup、LinearLayout、FrameLayout、RelativeLayout等。

  2. 繼承View

      例如:View、TextView、ImageView、Button等。

自定義控制元件基本繪製原理:

View的繪製基本上由measure()、layout()、draw()這個三個函式完成

1.)測量-Measure過程是計算檢視大小,View measure過程相關方法主要有三個:

public final void measure(int widthMeasureSpec, int heightMeasureSpec)  
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight)  
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)  

measure呼叫onMeasure,onMeasure測量寬度、高度然後呼叫setMeasureDimension儲存測量結果,measure,setMeasureDimension是final型別,view的子類不需要重寫,onMeasure在view的子類中重寫。

關於MeasureSpec:

(1) UPSPECIFIED :父容器對於子容器沒有任何限制,子容器想要多大就多大.

(2) EXACTLY父容器已經為子容器設定了尺寸,子容器應當服從這些邊界,不論子容器想要多大的空間.

(3) AT_MOST子容器可以是宣告大小內的任意大小.

2.)佈局-Layout過程用於設定檢視在螢幕中顯示的位置,View layout過程相關方法主要要三個:

public void layout(int l, int t, int r, int b)
protected boolean setFrame(int left, int top, int right, int bottom)
protected void onLayout(boolean changed, int left, int top, int right, int bottom)

layout通過呼叫setFrame(l,t,r,b),l,t,r,b即子檢視在父檢視中的具體位置,onLayout一般只會在自定義ViewGroup中才會使用

3.)繪製-draw過程主要用於利用前兩步得到的引數,將檢視顯示在螢幕上,到這裡也就完成了整個的檢視繪製工作。

public void draw(Canvas canvas)
protected void onDraw(Canvas canvas)

通過呼叫draw函式進行檢視繪製,在View類中onDraw函式是個空函式,最終的繪製需求需要在自定義的onDraw函式中進行實現,比如ImageView完成圖片的繪製,如果自定義ViewGroup這個函式則不需要過載。

自定義控制元件示例:

這裡先介紹繼承View的方式為例,其實ViewGroup最終的繼承的也是View。這裡 模擬一個需求場景,需要一個圓形顯示百分比。

public class PercentView extends View {
    private final static String TAG = PercentView.class.getSimpleName();
    private Paint mPaint;
    private  RectF oval;

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

    public PercentView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public PercentView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init(){
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        oval=new RectF();
    }
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        Log.e(TAG, "onMeasure--widthMode-->" + widthMode);
        switch (widthMode) {
            case MeasureSpec.EXACTLY:

                break;
            case MeasureSpec.AT_MOST:

                break;
            case MeasureSpec.UNSPECIFIED:

                break;
        }
        Log.e(TAG, "onMeasure--widthSize-->" + widthSize);
        Log.e(TAG, "onMeasure--heightMode-->" + heightMode);
        Log.e(TAG, "onMeasure--heightSize-->" + heightSize);
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        Log.e(TAG, "onLayout");
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        mPaint.setColor(Color.GRAY);
        // FILL填充, STROKE描邊,FILL_AND_STROKE填充和描邊
        mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
        int with = getWidth();
        int height = getHeight();
        Log.e(TAG, "onDraw---->" + with + "*" + height);
        float radius = with / 4;
        canvas.drawCircle(with / 2, with / 2, radius, mPaint);
        mPaint.setColor(Color.BLUE);
        oval.set(with / 2 - radius, with / 2 - radius, with / 2
                + radius, with / 2 + radius);//用於定義的圓弧的形狀和大小的界限
        canvas.drawArc(oval, 270, 120, true, mPaint);  //根據進度畫圓弧
    }
}

 

 在佈局中如何使用

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <com.whoislcj.views.PercentView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="10dp" />

</LinearLayout>

顯示效果:

如果佈局檔案改成

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <com.whoislcj.views.PercentView
        android:layout_width="150dp"
        android:layout_height="150dp"
        android:layout_margin="10dp" />

</LinearLayout>

顯示效果變成

總結:

本篇主要介紹Android自定義控制元件的基本繪製原理,會在下一篇中介紹如何自定義屬性。

 

相關文章