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


繼承View需要走的流程是:

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

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

            3.onDraw繪制,需要X軸,Y軸

 

繼承ViewGroup需要走的流程是:

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

            2.onMeasure測量子控件的高和寬,子控件.measure(寬,高),而自己的高和寬交給父控件去測量,因為我是父控件的子控件

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

 

布局文件,指定自定義類:

<!-- 繼承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);
    }
}

 

效果圖:

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM