Android-自定義控件-繼承View與ViewGroup的初步理解

繼承View須要走的流程是:android

            1.構造實例化, public ChildView(Context context, @Nullable AttributeSet attrs)canvas

            2.測量自身的高和寬onMeasure-->setMeasuredDimension(寬,高)ide

            3.onDraw繪製,須要X軸,Y軸佈局

 

繼承ViewGroup須要走的流程是:測試

            1.構造實例化, public ChildView(Context context, @Nullable AttributeSet attrs)spa

            2.onMeasure測量子控件的高和寬,子控件.measure(寬,高),而本身的高和寬交給父控件去測量,由於我是父控件的子控件code

            3.onLayout給子控件排版指定位置xml

 

佈局文件,指定自定義類:blog

<!-- 繼承View 與 繼承ViewGroup的初步理解 -->
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:myswitch="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="400dp"
    android:layout_height="500dp">

    <!-- 爺爺類,爺爺類有一個孩子(爸爸類) -->
    <custom.view.upgrade.view_viewgroup_theory.GrandpaViewGroup
        android:layout_width="300dp"
        android:layout_height="300dp"
        android:background="#3300cc"
        android:layout_centerInParent="true"
        > <!--
            雖然android:layout_centerInParent="true"屬性能夠去居中
            但這是Android RelativeLayout 對爺爺類進行了居中的排版固定位置處理
         -->

        <!-- 爸爸類,爸爸類有一個孩子(孩子類) -->
        <custom.view.upgrade.view_viewgroup_theory.FatherViewGroup
            android:layout_width="200dp"
            android:layout_height="200dp"
            android:background="#00ccff">

            <!-- 孩子類目前不包含孩子 -->
            <custom.view.upgrade.view_viewgroup_theory.ChildView
                android:layout_width="100dp"
                android:layout_height="100dp"
                android:background="#cc3300"/>

        </custom.view.upgrade.view_viewgroup_theory.FatherViewGroup>

    </custom.view.upgrade.view_viewgroup_theory.GrandpaViewGroup>
    

</RelativeLayout>

 

爺爺類,GrandpaViewGroup:繼承

package custom.view.upgrade.view_viewgroup_theory;

import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;

/**
 * 爺爺類,爺爺類有本身的孩子(爸爸類) ----> 爸爸類有本身的孩子(孩子類)
 */
public class GrandpaViewGroup extends ViewGroup {

    private static final String TAG = GrandpaViewGroup.class.getSimpleName();

    /**
     * 此兩個參數的構造方法,用於在xml佈局指定初始化,並傳入xml中的全部屬性
     * @param context
     * @param attrs
     */
    public GrandpaViewGroup(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    // 爸爸類
    private View fatherViewGroup;

    /**
     * 當xml佈局完成加載後,調用此方法
     */
    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        // 獲取子控件(爸爸類)
        fatherViewGroup = getChildAt(0);
    }

    /**
     * 因爲當前是ViewGroup因此測量子控件的寬和高
     * ,若是當前是View就是此類本身的高和寬
     * @param widthMeasureSpec
     * @param heightMeasureSpec
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        // 測量子控件(爸爸類)的寬和高
        // 寬和高就是 爸爸類在xml佈局中設置的值
        fatherViewGroup.measure(fatherViewGroup.getLayoutParams().width, fatherViewGroup.getLayoutParams().height);

        // 想獲取測量後子控件(爸爸類)的高和寬,是沒法獲取到的,由於子控件(爸爸類)是ViewGroup,拿到測量後的高和寬須要 View-->setMeasuredDimension()
        // 測試下:子控件(爸爸類)的高和寬
        Log.d(TAG, "fatherViewGroup.getMeasuredWidth():" + fatherViewGroup.getMeasuredWidth() +
                         " fatherViewGroup.getMeasuredHeight():" + fatherViewGroup.getMeasuredHeight());
    }

    /**
     * 排版 指定位置,只給子控件指定位置的
     * @param changed 當發生改變
     * @param l 左邊線距離左邊距離,注意:值是由父控件Layout後,處理了邏輯後傳遞過來的
     * @param t 上邊線距離上邊距離,注意:值是由父控件Layout後,處理了邏輯後傳遞過來的
     * @param r 右邊線距離左邊距離,注意:值是由父控件Layout後,處理了邏輯後傳遞過來的
     * @param b 底邊線距離上邊距離,注意:值是由父控件Layout後,處理了邏輯後傳遞過來的
     *
     * 父控件必須排版了layout此類的位置,此onLayout方法纔會執行,不然不執行
     */
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        // 測試下:獲取自身當前GrandpaViewGroup測量後的寬和高
        int measuredWidth = getMeasuredWidth();
        int measuredHeight = getMeasuredHeight();
        Log.d(TAG, "measuredWidth:" + measuredWidth + " measuredHeight:" + measuredHeight);

        // 給子控件(爸爸類)指定位置
        // Y軸與X軸移動計算同樣,X計算:移動X軸方向,獲得自身(GrandpaViewGroup爺爺類)寬度的一半 減 子控件(爸爸類)的一半就居中了
        int fatherL = (measuredWidth / 2) - (fatherViewGroup.getLayoutParams().width / 2);
        int fatherT = (measuredHeight / 2) - (fatherViewGroup.getLayoutParams().height / 2);

        // L位置增長了,R位置也須要增長
        int fatherR = fatherViewGroup.getLayoutParams().width + fatherL;
        int fatherB = fatherViewGroup.getLayoutParams().height + fatherT;
        fatherViewGroup.layout(fatherL, fatherT, fatherR, fatherB);
    }

    /**
     * 爲何繼承了ViewGroup就不須要onDraw繪製了,由於繪製都是在View中處理
     * 繼承了ViewGroup須要作好測量-->排版固定位置就行了,繪製是交給View去處理
     */
}

 

爸爸類,FatherViewGroup:

package custom.view.upgrade.view_viewgroup_theory;

import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;

/**
 * 爸爸類,爸爸類有本身的孩子
 */
public class FatherViewGroup extends ViewGroup {

    private static final String TAG = FatherViewGroup.class.getSimpleName();

    /**
     * 此兩個參數的構造方法,用於在xml佈局指定初始化,並傳入xml中的全部屬性
     * @param context
     * @param attrs
     */
    public FatherViewGroup(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    // 子控件(孩子類)
    private View childView;

    /**
     * 當xml佈局完成加載後,調用此方法
     */
    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();

        childView = getChildAt(0);
    }

    private int w;
    private int h;

    /**
     * 測量子控件(孩子類)的高和寬
     * @param widthMeasureSpec 能夠轉變爲模式,也能夠轉變爲父類給當前本身傳遞過來的寬
     * @param heightMeasureSpec 能夠轉變爲模式,也能夠轉變爲父類給當前本身傳遞過來的高
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        // 能夠獲取模式
        MeasureSpec.getMode(widthMeasureSpec);
        MeasureSpec.getMode(heightMeasureSpec);

        /**
         * 能夠獲取父類(爺爺類)在測量方法--->
         * fatherViewGroup.measure(fatherViewGroup.getLayoutParams()
         * .width, fatherViewGroup.getLayoutParams().height);
         * 傳遞過來的高和寬
         */
        w = MeasureSpec.getSize(widthMeasureSpec);
        h = MeasureSpec.getSize(heightMeasureSpec);
        Log.d(TAG, " w:" + w + " h:" + h);

        // 開始測量子控件(孩子類)
        // 寬高都是孩子類在xml佈局設置的寬高
        childView.measure(childView.getLayoutParams().width, childView.getLayoutParams().height);

        // 測試下:子控件(孩子類)的高和寬
        /**
         * 注意:爲何在這裏又能夠獲取到子控件的寬和高呢?
         *      由於子控件是View 而且這個View在測量方法中執行了 setMeasuredDimension(w, h);
         */
        Log.d(TAG, "childView.getMeasuredWidth():" + childView.getMeasuredWidth() +
                " childView.getMeasuredHeight():" + childView.getMeasuredHeight());
    }

    /**
     * 排版 指定位置,只給子控件指定位置的
     * @param changed 當發生改變
     * @param l 左邊線距離左邊距離
     * @param t 上邊線距離上邊距離
     * @param r 右邊線距離左邊距離
     * @param b 底邊線距離上邊距離
     */
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        // 測試下:獲取自身當前FatherViewGroup測量後的寬和高
        // 注意:在這裏沒法獲取,還不知道是什麼緣由!!!,
        // 爺爺類能夠獲取到是由於爺爺類的父類控件是系統的RelativeLayout
        int measuredWidth = getMeasuredWidth();
        int measuredHeight = getMeasuredHeight();
        Log.d(TAG, "爸爸類 >>> measuredWidth:" + measuredWidth + " measuredHeight:" + measuredHeight);

        // 給子控件(爸爸類)指定位置
        // Y軸與X軸移動計算同樣,X計算:移動X軸方向,獲得自身(FatherViewGroup爸爸類)寬度的一半 減 子控件(孩子類)的一本就居中了
        int childL = (w / 2) - (childView.getMeasuredWidth() / 2);
        int childT = (h / 2) - (childView.getMeasuredHeight() / 2);

        // L位置增長了,R位置也須要增長
        int childR = childView.getMeasuredWidth() + childL;
        int childB = childView.getMeasuredHeight() + childT;

        childView.layout(childL, childT, childR, childB);
    }

    /**
     * 爲何繼承了ViewGroup就不須要onDraw繪製了,由於繪製都是在View中處理
     * 繼承了ViewGroup須要作好測量-->排版固定位置就行了,繪製是交給View去處理
     */
}

 

孩子類,ChildView:

package custom.view.upgrade.view_viewgroup_theory;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.View;

/**
 * 孩子類,孩子類暫時尚未本身的孩子,全部是繼承View
 */
public class ChildView extends View {

    private static final String CONTEXT = "child";

    // 建立畫筆把文字畫到畫布中去
    private Paint mPaint;

    private Rect rect;

    /**
     * 此兩個參數的構造方法,用於在xml佈局指定初始化,並傳入xml中的全部屬性
     * @param context
     * @param attrs
     */
    public ChildView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);

        mPaint =  new Paint();
        // 畫筆防鋸齒
        mPaint.setAntiAlias(true);
        // 畫筆白色
        mPaint.setColor(Color.WHITE);
        mPaint.setTextSize(40);

        rect = new Rect();
    }

    /**
     * 此高寬是父控件(爸爸類)傳遞過來的高和寬
     */
    private int w;
    private int h;

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

        w = MeasureSpec.getSize(widthMeasureSpec);
        h = MeasureSpec.getSize(heightMeasureSpec);
        setMeasuredDimension(w, h);
    }

    /**
     * 繪製的方法
     * @param canvas 畫布
     */
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        // 拿到自身寬度的一半 減 文字寬度的一半

        mPaint.getTextBounds(CONTEXT, 0, CONTEXT.length(), rect);

        int x = (w / 2) - (rect.width() / 2);
        int y = (h / 2) - (rect.height() / 2) + rect.height();
        canvas.drawText(CONTEXT, x, y , mPaint);
    }
}

 

效果圖:

相關文章
相關標籤/搜索