自定義VIew(流式布局)

大家應(yīng)該對自定義View不太陌生安寺,為什么要使用自定義View呢厕妖,Android系統(tǒng)提供的原生控件滿足不了我們所要的需求,我們就需要自己定義一個可以滿足我們需求的控件挑庶,這時就用到了我們的自定義VIew言秸。在觀看這篇自定義View之前,建議可以先看一下小遍總結(jié)的:View繪制流程

下面我們進(jìn)入我們的主題:

自定義View

接下來我們通過一個流式布局來帶我們?nèi)チ私庾远xview具體繪制迎捺。
流式布局:可以進(jìn)行一個自動換行的ViewGroup 我們可以通過單獨(dú)設(shè)置我們的子View完成我們自動換行的布局举畸。
上代碼:

public class FlowLayout extends ViewGroup {

    private List<Line> mLines   = new ArrayList<Line>(); // 用來記錄描述有多少行View
    private Line        mCurrrenLine;   // 用來記錄當(dāng)前已經(jīng)添加到了哪一行
    private int         mHorizontalSpace    = 40;
    private int         mVerticalSpace      = mHorizontalSpace;
    private int mMaxLines = -1;

    public int getMaxLines() {
        return mMaxLines;
    }

    public void setMaxLines(int maxLines) {
        mMaxLines = maxLines;
    }

    public FlowLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public FlowLayout(Context context) {
        super(context);
    }
*
    public void setSpace(int horizontalSpace, int verticalSpace) {
        this.mHorizontalSpace = horizontalSpace;
        this.mVerticalSpace = verticalSpace;
    }

    public void clearAll(){
        mLines.clear();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        // 清空
        mLines.clear();
        mCurrrenLine = null;

        int layoutWidth = MeasureSpec.getSize(widthMeasureSpec);

        // 獲取行最大的寬度
        int maxLineWidth = layoutWidth - getPaddingLeft() - getPaddingRight();

        // 測量孩子
        int count = getChildCount();
        for (int i = 0; i < count; i++)
        {
            View view = getChildAt(i);

            // 如果孩子不可見
            if (view.getVisibility() == View.GONE)
            {
                continue;
            }

            // 測量孩子
            measureChild(view, widthMeasureSpec, heightMeasureSpec);

            // 往lines添加孩子
            if (mCurrrenLine == null)
            {
                // 說明還沒有開始添加孩子
                mCurrrenLine = new Line(maxLineWidth, mHorizontalSpace);

                // 添加到 Lines中
                mLines.add(mCurrrenLine);

                // 行中一個孩子都沒有
                mCurrrenLine.addView(view);
            }
            else
            {
                // 行不為空,行中有孩子了
                boolean canAdd = mCurrrenLine.canAdd(view);
                if (canAdd) {
                    // 可以添加
                    mCurrrenLine.addView(view);
                }
                else {
                    // 不可以添加,裝不下去
                    // 換行
                    if (mMaxLines >0){
                        if (mLines.size()<mMaxLines){
                            // 新建行
                            mCurrrenLine = new Line(maxLineWidth, mHorizontalSpace);
                            // 添加到lines中
                            mLines.add(mCurrrenLine);
                            // 將view添加到line
                            mCurrrenLine.addView(view);
                        }
                    }else {
                        // 新建行
                        mCurrrenLine = new Line(maxLineWidth, mHorizontalSpace);
                        // 添加到lines中
                        mLines.add(mCurrrenLine);
                        // 將view添加到line
                        mCurrrenLine.addView(view);
                    }
                }
            }
        }

        // 設(shè)置自己的寬度和高度
        int measuredWidth = layoutWidth;
        // paddingTop + paddingBottom + 所有的行間距 + 所有的行的高度

        float allHeight = 0;
        for (int i = 0; i < mLines.size(); i++)
        {
            float mHeigth = mLines.get(i).mHeigth;

            // 加行高
            allHeight += mHeigth;
            // 加間距
            if (i != 0)
            {
                allHeight += mVerticalSpace;
            }
        }

        int measuredHeight = (int) (allHeight + getPaddingTop() + getPaddingBottom() + 0.5f);
        setMeasuredDimension(measuredWidth, measuredHeight);
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b)
    {
        // 給Child 布局---> 給Line布局

        int paddingLeft = getPaddingLeft();
        int offsetTop = getPaddingTop();
        for (int i = 0; i < mLines.size(); i++)
        {
            Line line = mLines.get(i);

            // 給行布局
            line.layout(paddingLeft, offsetTop);

            offsetTop += line.mHeigth + mVerticalSpace;
        }
    }

    class Line
    {
        // 屬性
        private List<View>  mViews  = new ArrayList<View>();    // 用來記錄每一行有幾個View
        private float       mMaxWidth;                          // 行最大的寬度
        private float       mUsedWidth;                     // 已經(jīng)使用了多少寬度
        private float       mHeigth;                            // 行的高度
        private float       mMarginLeft;
        private float       mMarginRight;
        private float       mMarginTop;
        private float       mMarginBottom;
        private float       mHorizontalSpace;                   // View和view之間的水平間距

        // 構(gòu)造
        public Line(int maxWidth, int horizontalSpace) {
            this.mMaxWidth = maxWidth;
            this.mHorizontalSpace = horizontalSpace;
        }

        // 方法
        /**
         * 添加view,記錄屬性的變化
         * 
         * @param view
         */
        public void addView(View view)
        {
            // 加載View的方法

            int size = mViews.size();
            int viewWidth = view.getMeasuredWidth();
            int viewHeight = view.getMeasuredHeight();
            // 計算寬和高
            if (size == 0)
            {
                // 說還沒有添加View
                if (viewWidth > mMaxWidth)
                {
                    mUsedWidth = mMaxWidth;
                }
                else
                {
                    mUsedWidth = viewWidth;
                }
                mHeigth = viewHeight;
            }
            else
            {
                // 多個view的情況
                mUsedWidth += viewWidth + mHorizontalSpace;
                mHeigth = mHeigth < viewHeight ? viewHeight : mHeigth;
            }

            // 將View記錄到集合中
            mViews.add(view);
        }

        /**
         * 用來判斷是否可以將View添加到line中
         * 
         * @param view
         * @return
         */
        public boolean canAdd(View view)
        {
            // 判斷是否能添加View

            int size = mViews.size();

            if (size == 0) { return true; }

            int viewWidth = view.getMeasuredWidth();

            // 預(yù)計使用的寬度
            float planWidth = mUsedWidth + mHorizontalSpace + viewWidth;

            if (planWidth > mMaxWidth)
            {
                // 加不進(jìn)去
                return false;
            }

            return true;
        }

        /**
         * 給孩子布局
         * 
         * @param offsetLeft
         * @param offsetTop
         */
        public void layout(int offsetLeft, int offsetTop)
        {
            // 給孩子布局

            int currentLeft = offsetLeft;

            int size = mViews.size();
            // 判斷已經(jīng)使用的寬度是否小于最大的寬度
            float extra = 0;
            float widthAvg = 0;
            if (mMaxWidth > mUsedWidth)
            {
                extra = mMaxWidth - mUsedWidth;
                widthAvg = extra / size;
            }

            for (int i = 0; i < size; i++)
            {
                View view = mViews.get(i);
                int viewWidth = view.getMeasuredWidth();
                int viewHeight = view.getMeasuredHeight();

                // 判斷是否有富余
                if (widthAvg != 0)
                {
                    // 改變寬度,變?yōu)椴桓淖?避免最后一行因label不足,單個label變寬
                    //int newWidth = (int) (viewWidth + widthAvg + 0.5f);
                    int newWidth = viewWidth;
                    int widthMeasureSpec = MeasureSpec.makeMeasureSpec(newWidth, MeasureSpec.EXACTLY);
                    int heightMeasureSpec = MeasureSpec.makeMeasureSpec(viewHeight, MeasureSpec.EXACTLY);
                    view.measure(widthMeasureSpec, heightMeasureSpec);

                    viewWidth = view.getMeasuredWidth();
                    viewHeight = view.getMeasuredHeight();
                }

                // 布局
                int left = currentLeft;
                int top = (int) (offsetTop + (mHeigth - viewHeight) / 2 +
                            0.5f);
                // int top = offsetTop;
                int right = left + viewWidth;
                int bottom = top + viewHeight;
                view.layout(left, top, right, bottom);

                currentLeft += viewWidth + mHorizontalSpace;
            }
        }
    }

}

1.定義初始值 以及對高度的記錄
2.定義一個Line類 里面用來存放我們的我們最大寬度以及最大高度凳枝,以及最大左邊距抄沮,最大右邊距跋核,最大上邊距,最大下邊距叛买,行高以及水平間距砂代。

onMeasure方法

這個方法用來對我們ViewGroup以及View的測量
1.通過MeasureSpec.getSize(widthMeasureSpec);獲取到默認(rèn)的最大寬度,接下來通過減去左邊距和右邊距,獲取我們每一行的最大寬度
2.通過getChildCount() 獲取到子view的總數(shù)量,通過for循環(huán)進(jìn)行一個遍歷率挣,獲取到我們每一個子view刻伊,再通過measureChild()方法完成對子VIew的測量。
3.通過我們創(chuàng)建的Line的集合來記錄我一共有多少行VIew
4.循環(huán)遍歷我們的Lines集合進(jìn)行一個行高的相加
5.最后通過添加我們高的上邊距下邊距(可以忽略)
6.通過setMeasuredDimension()這個方法將我們測量過后的寬高進(jìn)行一個設(shè)置

onLayout()方法

這個方法主要是對VIew位置的一個擺放椒功,通過我們自己定義的一個方法中完成的我們子View的一個擺放
1.判斷已經(jīng)使用的寬度是否小于我們最大的寬度捶箱,如果大于的話就進(jìn)行一個換行操作,對我們寬度進(jìn)行添加蛾茉。
2.從左上角開始進(jìn)行一個位置的擺放
3.對View上下左右一個測量讼呢,通過layout()方法把我們測量過后的上下左右一個具體的擺放撩鹿。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末谦炬,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子节沦,更是在濱河造成了極大的恐慌键思,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,542評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件甫贯,死亡現(xiàn)場離奇詭異吼鳞,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)叫搁,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評論 3 394
  • 文/潘曉璐 我一進(jìn)店門赔桌,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人渴逻,你說我怎么就攤上這事疾党±煤玻” “怎么了宛裕?”我有些...
    開封第一講書人閱讀 163,912評論 0 354
  • 文/不壞的土叔 我叫張陵佃牛,是天一觀的道長盗似。 經(jīng)常有香客問我垫言,道長堕伪,這世上最難降的妖魔是什么纺弊? 我笑而不...
    開封第一講書人閱讀 58,449評論 1 293
  • 正文 為了忘掉前任阿逃,我火速辦了婚禮卧波,結(jié)果婚禮上时肿,老公的妹妹穿的比我還像新娘。我一直安慰自己港粱,他們只是感情好螃成,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,500評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般锈颗。 火紅的嫁衣襯著肌膚如雪顷霹。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,370評論 1 302
  • 那天击吱,我揣著相機(jī)與錄音淋淀,去河邊找鬼。 笑死覆醇,一個胖子當(dāng)著我的面吹牛朵纷,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播永脓,決...
    沈念sama閱讀 40,193評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼袍辞,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了常摧?” 一聲冷哼從身側(cè)響起搅吁,我...
    開封第一講書人閱讀 39,074評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎落午,沒想到半個月后谎懦,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,505評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡溃斋,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,722評論 3 335
  • 正文 我和宋清朗相戀三年界拦,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片梗劫。...
    茶點(diǎn)故事閱讀 39,841評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡享甸,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出梳侨,到底是詐尸還是另有隱情蛉威,我是刑警寧澤,帶...
    沈念sama閱讀 35,569評論 5 345
  • 正文 年R本政府宣布猫妙,位于F島的核電站瓷翻,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏割坠。R本人自食惡果不足惜齐帚,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,168評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望彼哼。 院中可真熱鬧对妄,春花似錦、人聲如沸敢朱。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,783評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至孝常,卻和暖如春旗们,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背构灸。 一陣腳步聲響...
    開封第一講書人閱讀 32,918評論 1 269
  • 我被黑心中介騙來泰國打工上渴, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人喜颁。 一個月前我還...
    沈念sama閱讀 47,962評論 2 370
  • 正文 我出身青樓稠氮,卻偏偏與公主長得像,于是被迫代替她去往敵國和親半开。 傳聞我的和親對象是個殘疾皇子隔披,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,781評論 2 354

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