1. 程式人生 > >Android自定義View系列:標籤LabelView實戰篇

Android自定義View系列:標籤LabelView實戰篇

前言部分

本文主要介紹如何自定義一個常見的labels標籤,功能上主要支援,單選、多選、點選三種模式。因為這個使用率很高,並且這個是比較典型學習自定義ViewGroup的例子,所以特意動手實踐,加深對Android的認識。這個專案主要是為了自己學習使用,所以並不是很完善,先上一個效果圖,瞭解一下:

在這裡插入圖片描述

內容部分

  1. ViewGroup的定義主要還是分佈在兩個部分,一個是測量,另一個是佈局。layout子view是作為容器最基本的工作。

  2. 測量的部分主要還是遵循view的三種測量模式來不同處理。介紹測量規則的部落格很多,這裡不多解釋。

    {@link android.
    view.View.MeasureSpec#UNSPECIFIED}, {@link android.view.View.MeasureSpec#AT_MOST} or {@link android.view.View.MeasureSpec#EXACTLY}
  3. 佈局部分主要是需要我們特殊處理的地方,因為我們的每個labels的大小是根據內容決定的,所以我們要自己根據view的尺寸進行擺放。

程式碼實現

首先介紹onMeasure方法中的實現,根絕子view的尺寸來決定容器view的測量情況。

private void measureMyChild(int widthMeasureSpec,
int heightMeasureSpec) { int widthMode = MeasureSpec.getMode(widthMeasureSpec); int widthSize = MeasureSpec.getSize(widthMeasureSpec); int heightMode = MeasureSpec.getMode(heightMeasureSpec); int heightSize = MeasureSpec.getSize(heightMeasureSpec); //寬度 maxWidth =
MeasureSpec.getSize(widthMeasureSpec); int count = getChildCount(); //總高度 int contentHeight = 0; //記錄最寬的行寬 int maxLineWidth = 0; // 每行寬度 int startLayoutWidth = 0; //一行中子控制元件最高的高度,用於決定下一行高度應該在目前基礎上累加多少 int maxChildHeight = 0; for (int i = 0; i < count; i++) { View child = getChildAt(i); LogUtil.i("onLayout--getPaddingRight:" + child.getPaddingRight() + "getPaddingLeft:" + child.getPaddingLeft()); measureChild(child, widthMeasureSpec, heightMeasureSpec); //測量的寬高 int childMeasureWidth = child.getMeasuredWidth(); int childMeasureHeight = child.getMeasuredHeight(); LogUtil.i("onLayout--width:" + maxWidth + "startLayoutWidth:"); if (startLayoutWidth + childMeasureWidth < maxWidth) { //如果一行沒有排滿,繼續往右排列 startLayoutWidth += childMeasureWidth + margiHorizontal; } else { // 初始化為0 maxChildHeight = 0; startLayoutWidth = 0; } if (childMeasureHeight > maxChildHeight) { maxChildHeight = childMeasureHeight; } //獲取總的高度 contentHeight += maxChildHeight + margiVertical; //獲取最長的行總的寬度 maxLineWidth = Math.max(maxLineWidth, startLayoutWidth); } //如果沒有子元素,就設定寬高都為0(簡化處理) if (getChildCount() == 0) { setMeasuredDimension(0, 0); } else //寬和高都是AT_MOST,則設定寬度最寬的字元素的寬度的和;高度設定為最高的元素的高度; if (widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.AT_MOST) { setMeasuredDimension(maxLineWidth, contentHeight); } //如果寬度是wrap_content,則寬度為最寬的一行的寬度 else if (widthMode == MeasureSpec.AT_MOST) { setMeasuredDimension(maxLineWidth, heightSize); } //如果高度是wrap_content,則高度為最高的字元素的高度 else if (heightMode == MeasureSpec.AT_MOST) { setMeasuredDimension(widthSize, contentHeight); } }

寬度的測量過程是,最大的寬度直接是取的容器view的尺寸。然後我們計算每一行的子view尺寸,通過求和來和容器view的最大寬度進行比較(公式:startLayoutWidth + childMeasureWidth < maxWidth)累加的寬度加上下一個字view的寬度和最大寬度比較來決定是否進行換行。

高度的測量過程是,先把所有的子view的高度進行相加求和,在根據容器view的mode來進行尺寸選擇。這裡內容都是常規的測量原則。主要區別還說在於寬高的取值。

寬高的取值我們的原則是,高度我們取值為最高的字元素;寬度我們取值為字元素相加,最寬的一行字元素。

測量部分的內容就是這些,主要還說需要我們對測量規則進行了解。然後根據我們自己的需求來進行選擇。


下面介紹佈局子view的部分程式碼

@Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        LogUtil.i("onLayout--width:" + maxWidth);
        final int count = getChildCount();
        int childMeasureWidth = 0;
        int childMeasureHeight = 0;
        // 開始的X位置
        int startLayoutWidth = getPaddingLeft();
        // 開始的Y位置
        int startLayoutHeight = getPaddingTop();
        //一行中子控制元件最高的高度,用於決定下一行高度應該在目前基礎上累加多少
        int maxChildHeight = 0;
        for (int i = 0; i < count; i++) {
            int position = i;
            TextView child = (TextView) getChildAt(i);
            //注意此處不能使用getWidth和getHeight,這兩個方法必須在onLayout執行完,才能正確獲取寬高
            childMeasureWidth = child.getMeasuredWidth() + child.getPaddingLeft() + child.getPaddingRight();
            childMeasureHeight = child.getMeasuredHeight() + child.getPaddingTop() + child.getPaddingBottom();
            LogUtil.i("onLayout--width:" + maxWidth + "startLayoutWidth:" + startLayoutWidth);
            if (startLayoutWidth + childMeasureWidth < maxWidth - getPaddingRight()) {
                //如果一行沒有排滿,繼續往右排列
                left = startLayoutWidth;
                right = left + childMeasureWidth;
                top = startLayoutHeight;
                bottom = top + childMeasureHeight;
            } else {
                //排滿後換行
                startLayoutWidth = getPaddingLeft();
                startLayoutHeight += maxChildHeight + margiVertical;
                maxChildHeight = 0;

                left = startLayoutWidth;
                right = left + childMeasureWidth;
                top = startLayoutHeight;
                bottom = top + childMeasureHeight;
            }
            //寬度累加
            startLayoutWidth += childMeasureWidth + margiHorizontal;
            if (childMeasureHeight > maxChildHeight) {
                maxChildHeight = childMeasureHeight;
            }
            //確定子控制元件的位置,四個引數分別代表(左上右下)點的座標值
            child.layout(left, top, right, bottom);
            initListener(child, position);
        }

    }

上面內容主要分為兩部分,一部分是寬度的佈局,一部分是高度的佈局。

寬度佈局:主要是根據子view的寬度和容器view的寬度的比較,來決定什麼時候進行換行。這裡需要注意的一些邊界值,如我們經常使用的margin和padding值。

高度佈局:主要是根據最高的子view的高度決定每一行的高度,這樣可以讓我們每一行都保持一樣的高度。


完成上面兩個大的步驟,基本上這個view也就完成的差不多了。

額外注意的點:

因為標籤是通過建立textview設定屬性新增到容器中,所以這裡設定文字顏色變化的方法和在xml中有些區別:

//設定每一個標籤
    private void drawTextView() {
        for (String text : textList) {
            TextView label = new TextView(context);
            label.setPadding(30, 30, 0, 0);
            label.setTextSize(TypedValue.COMPLEX_UNIT_PX, 40);
            label.setBackgroundResource(R.drawable.selector_text_bg);
            label.setText(text);
            label.setTextColor(createColorStateList("#ffffffff", "#ff44e6ff"));
            addView(label);
        }
    }

//工具方法
 private static ColorStateList createColorStateList(String selected, String normal) {
        int[] colors = new int[]{Color.parseColor(selected), Color.parseColor(normal)};
        int[][] states = new int[2][];
        states[0] = new int[]{android.R.attr.state_selected};
        states[1] = new int[]{};
        ColorStateList colorList = new ColorStateList(states, colors);
        return colorList;
    }

主要是介紹ColorStateList的使用,通過對映關係來進行顏色變化。

以上的步驟也可以通過填充xml中的textview來實現,這種實現方式你可以更輕鬆的設定你的textview。以前定義過的一個螞蟻森林能量球效果,就說這種方式實現。螞蟻森林效果

結束語

讀萬卷書,行萬里路。雖然這些東西早就有人實現了,我們也許也使用過,但是親自實踐的必要性還是在的。

你的鼓勵是我前進的動力。