自定義表格式布局FormLayout

自定義表格式布局FormLayout

項(xiàng)目中有這樣的表格式布局,如下圖

效果圖

如果用LinearLayout,RelativeLayout也能實(shí)現(xiàn)這樣的布局,但是比較麻煩阵苇,布局的層級(jí)也會(huì)比較多。所以就自己自定義了一個(gè)FormLayout來(lái)展示這些信息感论。

/**
 * 自定義表格布局
 * <p/>
 * author yyw
 * date 2017/7/12
 * version 1.0
 * desc
 */
public class FormLayout extends ViewGroup {
    private float[] mChildWeight;//表格的每列的權(quán)重绅项,用來(lái)計(jì)算每列表格的寬度,如果為空表示平均分配
    private int columnCount;//表格的總列數(shù)
    private int rowCount;//表格的總行數(shù)
    private int dividerSize = 2;//表格邊框的寬度
    private int[] mChildColumnWidth;//表格每列的寬度
    private int[] mChildRowHeight;//表格每行的高度
    private Path mDividerPath;//邊框的路徑
    private Paint mDividerPaint;//畫筆


    public FormLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.FormLayout);
        CharSequence[] weightArray = array.getTextArray(R.styleable.FormLayout_column_weight_array);
        setWeight(weightArray);
        columnCount = array.getInt(R.styleable.FormLayout_columnCount, 0);
        rowCount = array.getInt(R.styleable.FormLayout_rowCount, 0);
        int mDividerColor = array.getColor(R.styleable.FormLayout_dividerColor, Color.BLACK);
        dividerSize = array.getDimensionPixelSize(R.styleable.FormLayout_dividerWidth, dividerSize);
        array.recycle();
        mChildColumnWidth = new int[columnCount];
        mChildRowHeight = new int[rowCount];

        mDividerPath = new Path();
        mDividerPaint = new Paint();
        mDividerPaint.setColor(mDividerColor);
        mDividerPaint.setAntiAlias(true);
        mDividerPaint.setStrokeWidth(dividerSize);
        mDividerPaint.setStyle(Paint.Style.STROKE);
        setWillNotDraw(false);
    }

    private void setWeight(CharSequence[] weightArray) {
        if (weightArray == null) return;
        mChildWeight = new float[weightArray.length];
        for (int i = 0; i < weightArray.length; i++) {
            float w = Float.parseFloat(weightArray[i].toString().trim());
            mChildWeight[i] = w;
        }
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        if (columnCount == 0 || rowCount == 0) return;
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        //計(jì)算每列的寬度
        fillChildWidth(widthSize);
        for (int i = 0; i < mChildRowHeight.length; i++) {
            //計(jì)算每行的高度
            mChildRowHeight[i] = getChildAtRowMaxHeight(i);
        }
        final int childCount = getChildCount();

        for (int i = 0; i < childCount; i++) {
            View child = getChildAt(i);
            LayoutParams params = (LayoutParams) child.getLayoutParams();
            //計(jì)算總的寬度
            int childWidth = 0;
            for (int j = params.columnIndex; j < (params.columnCount + params.columnIndex); j++) {
                childWidth += mChildColumnWidth[j];
            }
            childWidth += (params.columnCount - 1) * dividerSize;
            //計(jì)算總的高度
            int childHeight = 0;
            for (int j = params.rowIndex; j < (params.rowIndex + params.rowCount); j++) {
                childHeight += mChildRowHeight[j];
            }
            childHeight += (params.rowCount - 1) * dividerSize;

            int childWidthMeasureSpec = getChildMeasureSpec(MeasureSpec.makeMeasureSpec(childWidth, MeasureSpec.EXACTLY),
                    getPaddingLeft() + getPaddingRight() + params.leftMargin
                            + params.rightMargin, childWidth);
            int childHeightMeasureSpec = getChildMeasureSpec(MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.EXACTLY),
                    getPaddingTop() + getPaddingBottom() + params.topMargin
                            + params.bottomMargin, childHeight);
            child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
        }
        //計(jì)算布局的總高度
        int allHeight = 0;
        for (int h : mChildRowHeight) {
            allHeight += h;
        }
        allHeight += (rowCount + 1) * dividerSize;
        allHeight += getPaddingBottom() + getPaddingTop();
        super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(allHeight, MeasureSpec.EXACTLY));
    }

    /**
     * 獲取每行的最大的高度
     *
     * @param row 行數(shù)
     * @return 最大高度
     */
    private int getChildAtRowMaxHeight(int row) {
        final int childCount = getChildCount();
        int maxHeight = 0;
        for (int i = 0; i < childCount; i++) {
            View child = getChildAt(i);
            LayoutParams params = (LayoutParams) child.getLayoutParams();
            if (row >= params.rowIndex && row < (params.rowIndex + params.rowCount)) {//表示在獲取范圍內(nèi)
                int childWidth = 0;
                for (int j = params.columnIndex; j < (params.columnCount + params.columnIndex); j++) {
                    childWidth += mChildColumnWidth[j];//計(jì)算所占列的總寬度
                }
                childWidth += (params.columnCount - 1) * dividerSize;//計(jì)算總的寬度
                int childHeight = getChildMeasureHeight(child, params, childWidth);
                int rowHeight = (childHeight - (params.rowCount - 1) * dividerSize) / params.rowCount;//把寬度分給所占行的高度
                maxHeight = Math.max(maxHeight, rowHeight);
            }
        }
        return maxHeight;
    }

    /**
     * 獲取行高
     *
     * @param child      子view
     * @param lp         子view的LayoutParams
     * @param childWidth 子view的寬度
     * @return 獲取子view的高度
     */
    private int getChildMeasureHeight(View child, LayoutParams lp, int childWidth) {
        int childWidthMeasureSpec = getChildMeasureSpec(MeasureSpec.makeMeasureSpec(childWidth, MeasureSpec.EXACTLY),
                getPaddingLeft() + getPaddingRight() + lp.leftMargin
                        + lp.rightMargin, childWidth);
        int childHeightMeasureSpec = getChildMeasureSpec(MeasureSpec.makeMeasureSpec(lp.height, MeasureSpec.UNSPECIFIED),
                getPaddingTop() + getPaddingBottom() + lp.topMargin
                        + lp.bottomMargin, ViewGroup.LayoutParams.WRAP_CONTENT);
        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
        return child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin;
    }

    /**
     * 計(jì)算每列的位置
     *
     * @param widthSize 寬度
     */
    private void fillChildWidth(int widthSize) {
        int paddingLeft = getPaddingLeft();
        int paddingRight = getPaddingRight();
        widthSize = widthSize - paddingLeft - paddingRight - (columnCount + 1) * dividerSize;
        if (mChildWeight != null && mChildWeight.length == mChildColumnWidth.length) {
            float allWeight = 0.0f;
            for (float w : mChildWeight) {
                allWeight += w;
            }
            float weightStep = widthSize / allWeight;
            for (int i = 0; i < mChildWeight.length; i++) {
                mChildColumnWidth[i] = Math.round(mChildWeight[i] * weightStep);
            }
        } else {
            float weightStep = widthSize / mChildColumnWidth.length;
            for (int i = 0; i < mChildColumnWidth.length; i++) {
                mChildColumnWidth[i] = Math.round(weightStep);
            }
        }
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        final int childTop = getPaddingTop() + dividerSize;
        final int childLeft = getPaddingLeft() + dividerSize;
        final int count = getChildCount();
        for (int i = 0; i < count; i++) {
            View child = getChildAt(i);
            LayoutParams params = (LayoutParams) child.getLayoutParams();
            int t = getCurrentTop(childTop, params.rowIndex) + params.rowIndex * dividerSize;
            int l = getCurrentLeft(childLeft, params.columnIndex) + params.columnIndex * dividerSize;
            int b = t + child.getMeasuredHeight();
            int r = l + child.getMeasuredWidth();
            child.layout(l, t, r, b);
        }
    }

    private int getCurrentTop(int childTop, int rowIndex) {
        for (int i = 0; i < rowIndex; i++) {
            childTop += mChildRowHeight[i];
        }
        return childTop;
    }

    private int getCurrentLeft(int childLeft, int columnIndex) {
        for (int i = 0; i < columnIndex; i++) {
            childLeft += mChildColumnWidth[i];
        }
        return childLeft;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //畫邊框
        mDividerPath.reset();
        final int childCount = getChildCount();
        final int halfSize = dividerSize / 2;
        for (int i = 0; i < childCount; i++) {
            View child = getChildAt(i);
            mDividerPath.moveTo(child.getLeft() - halfSize, child.getTop() - halfSize);
            mDividerPath.lineTo(child.getLeft() - halfSize, child.getBottom() + halfSize);
            mDividerPath.lineTo(child.getRight() + halfSize, child.getBottom() + halfSize);
            mDividerPath.lineTo(child.getRight() + halfSize, child.getTop() - halfSize);
            mDividerPath.lineTo(child.getLeft() - halfSize, child.getTop() - halfSize);
        }
        canvas.drawPath(mDividerPath, mDividerPaint);
    }

    @Override
    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
        return p instanceof LayoutParams;
    }

    @Override
    public LayoutParams generateLayoutParams(AttributeSet attrs) {
        return new LayoutParams(getContext(), attrs);
    }

    @Override
    protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
        return new LayoutParams(p);
    }

    /**
     * Per child parameters for children views of the {@link FormLayout}.
     */
    public static class LayoutParams extends ViewGroup.MarginLayoutParams {
        public int rowIndex;
        public int rowCount;
        public int columnIndex;
        public int columnCount;

        public LayoutParams(Context c, AttributeSet attrs) {
            super(c, attrs);
            TypedArray array = c.obtainStyledAttributes(attrs, R.styleable.FormLayout_Layout);
            rowIndex = array.getInt(R.styleable.FormLayout_Layout_layout_rowIndex, 0);
            rowCount = array.getInt(R.styleable.FormLayout_Layout_layout_rowCount, 1);
            columnIndex = array.getInt(R.styleable.FormLayout_Layout_layout_columnIndex, 0);
            columnCount = array.getInt(R.styleable.FormLayout_Layout_layout_columnCount, 1);
            array.recycle();
        }

        public LayoutParams(@Px int width, @Px int height) {
            super(new ViewGroup.LayoutParams(width, height));
        }

        public LayoutParams(LayoutParams source) {
            super(source);
            rowIndex = source.rowIndex;
            rowCount = source.rowCount;
            columnIndex = source.columnIndex;
            columnCount = source.columnCount;
        }

        public LayoutParams(ViewGroup.LayoutParams source) {
            super(source);
        }
    }
}

自定義屬性

<resources>
    <declare-styleable name="FormLayout">
        <attr name="column_weight_array" format="reference"/>
        <attr name="rowCount" format="integer"/>
        <attr name="columnCount" format="integer"/>
        <attr name="dividerColor" format="color"/>
        <attr name="dividerWidth" format="dimension"/>
    </declare-styleable>

    <declare-styleable name="FormLayout_Layout">
        <attr name="layout_rowIndex" format="integer"/>
        <attr name="layout_rowCount" format="integer"/>
        <attr name="layout_columnIndex" format="integer"/>
        <attr name="layout_columnCount" format="integer"/>
    </declare-styleable>
</resources>

Demo

效果圖

代碼布局:

<com.tongyan.qrcode.support.widget.FormLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:columnCount="3"
            app:rowCount="3">

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:gravity="center"
                android:padding="10dp"
                android:text="占一列三行"
                app:layout_columnIndex="0"
                app:layout_rowCount="3"
                app:layout_rowIndex="0"/>

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:gravity="center"
                android:padding="10dp"
                android:text="占兩列一行"
                app:layout_columnCount="2"
                app:layout_columnIndex="1"
                app:layout_rowIndex="0"/>

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:gravity="center"
                android:padding="10dp"
                android:text="占一列一行"
                app:layout_columnIndex="1"
                app:layout_rowIndex="1"/>

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:gravity="center"
                android:padding="10dp"
                android:text="占一列一行"
                app:layout_columnIndex="1"
                app:layout_rowIndex="2"/>

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:gravity="center"
                android:padding="10dp"
                android:text="占一列兩行"
                app:layout_columnIndex="2"
                app:layout_rowCount="2"
                app:layout_rowIndex="1"/>
        </com.tongyan.qrcode.support.widget.FormLayout>

如果要改動(dòng)每列的寬度權(quán)重
可以定義一個(gè)String數(shù)組

<string-array name="demo">
        <item>1</item>
        <item>1.5</item>
        <item>2</item>
    </string-array>

在布局中加入

app:column_weight_array="@array/demo"

效果圖

效果圖
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市比肄,隨后出現(xiàn)的幾起案子快耿,更是在濱河造成了極大的恐慌,老刑警劉巖芳绩,帶你破解...
    沈念sama閱讀 210,978評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件掀亥,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡妥色,警方通過(guò)查閱死者的電腦和手機(jī)搪花,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,954評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人撮竿,你說(shuō)我怎么就攤上這事吮便。” “怎么了幢踏?”我有些...
    開(kāi)封第一講書人閱讀 156,623評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵髓需,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我房蝉,道長(zhǎng)僚匆,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 56,324評(píng)論 1 282
  • 正文 為了忘掉前任搭幻,我火速辦了婚禮咧擂,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘粗卜。我一直安慰自己屋确,他們只是感情好纳击,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,390評(píng)論 5 384
  • 文/花漫 我一把揭開(kāi)白布续扔。 她就那樣靜靜地躺著,像睡著了一般焕数。 火紅的嫁衣襯著肌膚如雪纱昧。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 49,741評(píng)論 1 289
  • 那天堡赔,我揣著相機(jī)與錄音识脆,去河邊找鬼。 笑死善已,一個(gè)胖子當(dāng)著我的面吹牛灼捂,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播换团,決...
    沈念sama閱讀 38,892評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼悉稠,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了艘包?” 一聲冷哼從身側(cè)響起的猛,我...
    開(kāi)封第一講書人閱讀 37,655評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎想虎,沒(méi)想到半個(gè)月后卦尊,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,104評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡舌厨,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評(píng)論 2 325
  • 正文 我和宋清朗相戀三年岂却,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,569評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡淌友,死狀恐怖煌恢,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情震庭,我是刑警寧澤瑰抵,帶...
    沈念sama閱讀 34,254評(píng)論 4 328
  • 正文 年R本政府宣布,位于F島的核電站器联,受9級(jí)特大地震影響二汛,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜拨拓,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,834評(píng)論 3 312
  • 文/蒙蒙 一肴颊、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧渣磷,春花似錦婿着、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,725評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至形纺,卻和暖如春丘侠,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背逐样。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 31,950評(píng)論 1 264
  • 我被黑心中介騙來(lái)泰國(guó)打工蜗字, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人脂新。 一個(gè)月前我還...
    沈念sama閱讀 46,260評(píng)論 2 360
  • 正文 我出身青樓挪捕,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親争便。 傳聞我的和親對(duì)象是個(gè)殘疾皇子级零,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,446評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,734評(píng)論 25 707
  • 太長(zhǎng)不看版:在 Android UI 布局過(guò)程中,遵守一些慣用始花、有效的布局原則妄讯,可以制作出高效且復(fù)用性高的 UI。...
    Mupceet閱讀 3,827評(píng)論 0 14
  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫(kù)酷宵、插件亥贸、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 12,058評(píng)論 4 62
  • 也許只有這樣的時(shí)刻,才能對(duì)“在身邊”有一個(gè)更加直觀的感受浇垦。 曾經(jīng)有個(gè)人說(shuō)“我在外地炕置,家里的妻子就會(huì)很辛苦,雖然高鐵...
    曉冰小時(shí)光閱讀 165評(píng)論 0 1
  • 『肯定的語(yǔ)言』 心理學(xué)家威廉·詹姆斯說(shuō)過(guò),人類最深處的需要朴摊,可能就是感覺(jué)被人欣賞默垄。 想當(dāng)初,兩個(gè)人走到一起甚纲,肯定是...
    仲夏夜之夢(mèng)123閱讀 144評(píng)論 0 2