筆記51 | Android自定義View(二)

地址

筆記51 | Android自定義View(二)
http://www.jianshu.com/p/c84693096e41


自定義ViewGroup

自定義View的過程很簡單,就那幾步,可自定義ViewGroup可就沒那麼簡單啦~,因爲它不僅要管好自己的,還要兼顧它的子View。我們都知道ViewGroup是個View容器,它裝納child View並且負責把child View放入指定的位置。我們假象一下,如果是讓你負責設計ViewGroup,你會怎麼去設計呢?

  1. 首先,我們得知道各個子View的大小吧,只有先知道子View的大小,我們才知道當前的ViewGroup該設置爲多大去容納它們。

  2. 根據子View的大小,以及我們的ViewGroup要實現的功能,決定出ViewGroup的大小

  3. ViewGroup和子View的大小算出來了之後,接下來就是去擺放了吧,具體怎麼去擺放呢?這得根據你定製的需求去擺放了,比如,你想讓子View按照垂直順序一個挨着一個放,或者是按照先後順序一個疊一個去放,這是你自己決定的。

  4. 已經知道怎麼去擺放還不行啊,決定了怎麼擺放就是相當於把已有的空間”分割”成大大小小的空間,每個空間對應一個子View,我們接下來就是把子View對號入座了,把它們放進它們該放的地方去。

現在就完成了ViewGroup的設計了,我們來個具體的案例:將子View按從上到下垂直順序一個挨着一個擺放,即模仿實現LinearLayout的垂直佈局。

首先重寫onMeasure,實現測量子View大小以及設定ViewGroup的大小:


    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        //將所有的子View進行測量,這會觸發每個子View的onMeasure函數
        //注意要與measureChild區分,measureChild是對單個view進行測量
        measureChildren(widthMeasureSpec, heightMeasureSpec);

        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);

        int childCount = getChildCount();

        if (childCount == 0) {//如果沒有子View,當前ViewGroup沒有存在的意義,不用佔用空間
            setMeasuredDimension(0, 0);
        } else {
            //如果寬高都是包裹內容
            if (widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.AT_MOST) {
                //我們將高度設置爲所有子View的高度相加,寬度設爲子View中最大的寬度
                int height = getTotleHeight();
                int width = getMaxChildWidth();
                setMeasuredDimension(width, height);

            } else if (heightMode == MeasureSpec.AT_MOST) {//如果只有高度是包裹內容
                //寬度設置爲ViewGroup自己的測量寬度,高度設置爲所有子View的高度總和
                setMeasuredDimension(widthSize, getTotleHeight());
            } else if (widthMode == MeasureSpec.AT_MOST) {//如果只有寬度是包裹內容
                //寬度設置爲子View中寬度最大的值,高度設置爲ViewGroup自己的測量值
                setMeasuredDimension(getMaxChildWidth(), heightSize);

            }
        }
    }
    /***
     * 獲取子View中寬度最大的值
     */
    private int getMaxChildWidth() {
        int childCount = getChildCount();
        int maxWidth = 0;
        for (int i = 0; i < childCount; i++) {
            View childView = getChildAt(i);
            if (childView.getMeasuredWidth() > maxWidth)
                maxWidth = childView.getMeasuredWidth();

        }

        return maxWidth;
    }

    /***
     * 將所有子View的高度相加
     **/
    private int getTotleHeight() {
        int childCount = getChildCount();
        int height = 0;
        for (int i = 0; i < childCount; i++) {
            View childView = getChildAt(i);
            height += childView.getMeasuredHeight();

        }

        return height;
    }

代碼中的註釋我已經寫得很詳細,不再對每一行代碼進行講解。上面的onMeasure將子View測量好了,以及把自己的尺寸也設置好了,接下來我們去擺放子View吧~

 @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int count = getChildCount();
        //記錄當前的高度位置
        int curHeight = t;
        //將子View逐個擺放
        for (int i = 0; i < count; i++) {
            View child = getChildAt(i);
            int height = child.getMeasuredHeight();
            int width = child.getMeasuredWidth();
            //擺放子View,參數分別是子View矩形區域的左、上、右、下邊
            child.layout(l, curHeight, l + width, curHeight + height);
            curHeight += height;
        }
    }

我們測試一下,將我們自定義的ViewGroup裏面放3個Button ,將這3個Button的寬度設置不一樣,把我們的ViewGroup的寬高都設置爲包裹內容wrap_content,爲了看的效果明顯,我們給ViewGroup加個背景:

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/container"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.evan.view.MainActivity"
    tools:ignore="MergeRootFrame" >

    <com.evan.view.myView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@android:color/holo_blue_light" >

        <Button
            android:layout_width="100dp"
            android:layout_height="wrap_content"
            android:text="btn" />

        <Button
            android:layout_width="200dp"
            android:layout_height="wrap_content"
            android:text="btn" />

        <Button
            android:layout_width="50dp"
            android:layout_height="wrap_content"
            android:text="btn" />

        <Button
            android:layout_width="150dp"
            android:layout_height="wrap_content"
            android:text="btn" />

        <Button
            android:layout_width="250dp"
            android:layout_height="wrap_content"
            android:text="btn" />
    </com.evan.view.myView>

</FrameLayout>

看看最後的效果吧~
device-2017-12-14-161459.png

是不是很激動我們自己也可以實現LinearLayout的效果啦~~~

最後附上MyViewGroup的完整源碼:

package com.evan.view;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;

public class myView extends ViewGroup{

    private int defalutSize;
    public myView(Context context, AttributeSet attrs) {
        super(context, attrs);
        //第二個參數就是我們在styles.xml文件中的<declare-styleable>標籤
        //即屬性集合的標籤,在R文件中名稱爲R.styleable+name
        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.MyView);

        //第一個參數爲屬性集合裏面的屬性,R文件名稱:R.styleable+屬性集合名稱+下劃線+屬性名稱
        //第二個參數爲,如果沒有設置這個屬性,則設置的默認的值
        defalutSize = a.getDimensionPixelSize(R.styleable.MyView_default_size, 100);

        //最後記得將TypedArray對象回收
        a.recycle();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        // TODO Auto-generated method stub
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        measureChildren(widthMeasureSpec, heightMeasureSpec);

        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);

        int childCount = getChildCount();
        if (childCount == 0) {//如果沒有子View,當前ViewGroup沒有存在的意義,不用佔用空間
            setMeasuredDimension(0, 0);
        }else{
            //如果寬高都是包裹內容
            if (widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.AT_MOST) {
                int height = getTotleHeight();
                int width = getMaxChildWidth();
                setMeasuredDimension(width, height);
            }else if (heightMode == MeasureSpec.AT_MOST) {
                //寬度設置爲ViewGroup自己的測量寬度,高度設置爲所有子View的高度總和
                setMeasuredDimension(widthSize, getTotleHeight());
            }else if (widthMode == MeasureSpec.AT_MOST) {
                //寬度設置爲子View中寬度最大的值,高度設置爲ViewGroup自己的測量值
                setMeasuredDimension(getMaxChildWidth(), heightSize);
            }
        }
    }

    /*
     * (non-Javadoc)
     * 獲取子View中寬度的最大值
     */
    private int getMaxChildWidth(){
        int childCount = getChildCount();
        int maxWidth = 0;
        for (int i = 0; i < childCount; i++) {
            View childView = getChildAt(i);
            if (childView.getMeasuredWidth() > maxWidth) {
                maxWidth = childView.getMeasuredWidth()+5;
            }
        }
        return maxWidth;
    }

    /*
     * (non-Javadoc)
     * 將所有子View的高度相加
     * 
     */
    private int getTotleHeight(){
        int childCount = getChildCount();
        int height = 0;
        for (int i = 0; i < childCount; i++) {
            View childView = getChildAt(i);
            height+=(childView.getMeasuredHeight());
        }
        return height;
    }

    /*
     * (non-Javadoc)
     * 擺放
     */
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int count = getChildCount();
        //記錄當前的高度位置
        int curHeight = t;
        //將子View逐個擺放
        for (int i = 0; i < count; i++) {
            View child = getChildAt(i);
            int height = child.getMeasuredHeight();
            int width = child.getMeasuredWidth();
            //擺放子View,參數分別是子View矩形區域的左、上、右、下邊
            child.layout(l, curHeight, l+width, curHeight+height);
            curHeight += height;
        }
    }
}

好啦~自定義View的學習到此結束,是不是發現自定義View如此簡單呢?


我的Android征途

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章