自定義控件重點方法解析

自定義控件(繼承ViewGroup)方法:

onMeasure() : 專門處理組件的大小和寬高
onLayout() : 處理所有的child安排大小和擺放位置
onDraw() : 繪制自定義控件
onTouchEvent(): 點擊事件處理

接下來將進行深入的解析每個方法:

onMeasure()的處理:

1)組件位置發(fā)生變化
2)子控件回調了onMeasure方法,父view一定也會回調onMeasure方法
3)onMeasure方法回調后笔链,一定會回調onLayout方法
4)onMeasure()方法傳入的參數(shù)凫乖,通過MeasureSpec類來進行組件大小模式的處理躁劣。

//獲取該組件的實際寬度
    private int measureWidth(int widthMeasureSpec) {
        int specMode =MeasureSpec.getMode(widthMeasureSpec);
        int specSize = MeasureSpec.getSize(widthMeasureSpec);
        int result = 400;
        if(specMode == MeasureSpec.AT_MOST){
            //WRAP_CONTENT 最大模式 其中的子組件可以根據(jù)自己的規(guī)定的尺寸進行展示
            result = specSize;
        }else if(specMode == MeasureSpec.EXACTLY){
            //精確的模式捺弦,它規(guī)定子組件確定的大小颜矿,無論子組件多大茴丰,子組件必須按照確定大小進行展示
            result = specSize;
        }
        return result;
    }

5)通過setMeasuredDimension()方法設置組件的實際展示寬高困介。

onLayout的處理

  1. onLayout的作用是給所有的child安排大小和擺放位置召衔。一般ViewGroup使用铃诬。
@Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        LogUtil.i("onLayout");
        int count = getChildCount();
        int width = getWidth();
        Log.i("TAG", "寬度 :"+width);
        for (int i = 0 ; i < count; i++){
            View childView = getChildAt(i);
            int w = childView.getMeasuredWidth();
            int h = childView.getMeasuredHeight();
            int x = listX.get(i);
            int y = listY.get(i);
            childView.layout(x, y, x + w, y + h);
        }
    }

完整的代碼如下:

package com.ml.weihenji.widgets;

import android.content.Context;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;

import com.ml.weihenji.utils.LogUtil;

import java.util.ArrayList;

/**
 * Created by malei on 2017/6/14.
 * 自定義自動換行viewGroup
 */

public class LineWrapLayout extends ViewGroup{

    private ArrayList<Integer> listX; //保存每一個子控件的起始x坐標
    private ArrayList<Integer> listY;  //保存每一個子控件的起始y坐標

    private int paddingLeft = 10;  //左間距
    private int paddingRight = 10;   //右間距
    private int paddingTop = 10;
    private int paddingBottom = 10;
    private int horizontalSpace = 10;  //水平方向間距
    private int verticalSpace = 5;    //行間距


    public LineWrapLayout(Context context) {
        this(context,null);
    }

    public LineWrapLayout(Context context, AttributeSet attrs) {
        this(context, attrs,0);
    }

    public LineWrapLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(attrs);
    }

    private void init(AttributeSet attrs) {
        listX = new ArrayList<>();
        listY = new ArrayList<>();
    }

    /**
     * layout的作用是給所有的child安排大小和擺放位置。
     */
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        LogUtil.i("onLayout");
        int count = getChildCount();
        int width = getWidth();
        Log.i("TAG", "寬度 :"+width);
        for (int i = 0 ; i < count; i++){
            View childView = getChildAt(i);
            int w = childView.getMeasuredWidth();
            int h = childView.getMeasuredHeight();
            int x = listX.get(i);
            int y = listY.get(i);
            childView.layout(x, y, x + w, y + h);
        }
    }

    /**
     * 1.onMeasure方法會在view的位置信息發(fā)生變化或調用。
     * 2.子view回調了onMeasure方法趣席,父view一定也會回調onMeasure方法兵志。
     * 3.onMeasure方法回調后,一定會回調onLayout方法宣肚。
     *
     * UNSPECIFIED:父布局沒有給子布局任何限制毒姨,子布局可以任意大小。
       EXACTLY:父布局決定子布局的確切大小钉寝。不論子布局多大弧呐,它都必須限制在這個界限里。
       AT_MOST:子布局可以根據(jù)自己的大小選擇任意大小

     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        LogUtil.i("onMeasure");

        int count = getChildCount(); //獲取子組件的個數(shù)
        int width = measureWidth(widthMeasureSpec);
        Log.i("TAG", "寬度 :"+width);

        int startOffsetX = paddingLeft;// 橫坐標開始
        int startOffsety = 0+paddingTop;//縱坐標開始

        int rowCount = 1;  //行數(shù)目
        int preEndOffsetX = startOffsetX;

        listX.clear();
        listY.clear();

        //處理子組件的大小寬高
        for (int i = 0; i < count; i++){
            Log.i("TAG", "----");
            final View childView = getChildAt(i);
            // TODO: 2017/6/14  為什么要這么做嵌纲?
            childView.measure(0,0);
            int childWidth = childView.getMeasuredWidth(); //子控件寬高
            int childHeight = childView.getMeasuredHeight();
            Log.v("TAG", "childWidth :"+childWidth+" childHeight :"+childHeight);
            preEndOffsetX = startOffsetX + childWidth;  //進行累計一行子組件共有寬度

            //當子組件的寬度大于一行的時候
            if(preEndOffsetX > width - paddingRight){
                if(startOffsetX > paddingLeft){
                    //換行
                    startOffsetX = paddingLeft;
                    startOffsety += childHeight+verticalSpace;
                    rowCount++;
                }
            }

            listX.add(startOffsetX);
            listY.add(startOffsety);  //不停會增加

            startOffsetX = startOffsetX + childWidth + horizontalSpace; //下一個子組件的x坐標是 累計的x+上一個組件的寬度+間隔
        }

        int lastLineHeight = 0;
        View lastChild = getChildAt(count-1);
        if(null != lastChild){
            lastLineHeight = lastChild.getMeasuredHeight(); //獲取最后一個子控件的高度
        }
        setMeasuredDimension(measureWidth(widthMeasureSpec), startOffsety+lastLineHeight+paddingBottom);
    }

    //獲取該組件的實際寬度
    private int measureWidth(int widthMeasureSpec) {
        int specMode =MeasureSpec.getMode(widthMeasureSpec);
        int specSize = MeasureSpec.getSize(widthMeasureSpec);
        int result = 400;
        if(specMode == MeasureSpec.AT_MOST){
            //WRAP_CONTENT 最大模式 其中的子組件可以根據(jù)自己的規(guī)定的尺寸進行展示
            result = specSize;
        }else if(specMode == MeasureSpec.EXACTLY){
            //精確的模式俘枫,它規(guī)定子組件確定的大小,無論子組件多大逮走,子組件必須按照確定大小進行展示
            result = specSize;
        }
        return result;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        return super.onTouchEvent(event);
    }
}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末鸠蚪,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子师溅,更是在濱河造成了極大的恐慌茅信,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,743評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件墓臭,死亡現(xiàn)場離奇詭異蘸鲸,居然都是意外死亡,警方通過查閱死者的電腦和手機窿锉,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,296評論 3 385
  • 文/潘曉璐 我一進店門酌摇,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人嗡载,你說我怎么就攤上這事窑多。” “怎么了洼滚?”我有些...
    開封第一講書人閱讀 157,285評論 0 348
  • 文/不壞的土叔 我叫張陵埂息,是天一觀的道長。 經(jīng)常有香客問我遥巴,道長千康,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,485評論 1 283
  • 正文 為了忘掉前任挪哄,我火速辦了婚禮吧秕,結果婚禮上,老公的妹妹穿的比我還像新娘迹炼。我一直安慰自己砸彬,他們只是感情好颠毙,可當我...
    茶點故事閱讀 65,581評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著砂碉,像睡著了一般蛀蜜。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上增蹭,一...
    開封第一講書人閱讀 49,821評論 1 290
  • 那天滴某,我揣著相機與錄音,去河邊找鬼滋迈。 笑死霎奢,一個胖子當著我的面吹牛,可吹牛的內容都是我干的饼灿。 我是一名探鬼主播幕侠,決...
    沈念sama閱讀 38,960評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼碍彭!你這毒婦竟也來了晤硕?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 37,719評論 0 266
  • 序言:老撾萬榮一對情侶失蹤庇忌,失蹤者是張志新(化名)和其女友劉穎舞箍,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體皆疹,經(jīng)...
    沈念sama閱讀 44,186評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡疏橄,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,516評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了墙基。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片软族。...
    茶點故事閱讀 38,650評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖残制,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情掖疮,我是刑警寧澤初茶,帶...
    沈念sama閱讀 34,329評論 4 330
  • 正文 年R本政府宣布,位于F島的核電站浊闪,受9級特大地震影響恼布,放射性物質發(fā)生泄漏。R本人自食惡果不足惜搁宾,卻給世界環(huán)境...
    茶點故事閱讀 39,936評論 3 313
  • 文/蒙蒙 一折汞、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧盖腿,春花似錦爽待、人聲如沸损同。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,757評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽膏燃。三九已至,卻和暖如春何什,著一層夾襖步出監(jiān)牢的瞬間组哩,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,991評論 1 266
  • 我被黑心中介騙來泰國打工处渣, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留伶贰,地道東北人。 一個月前我還...
    沈念sama閱讀 46,370評論 2 360
  • 正文 我出身青樓罐栈,卻偏偏與公主長得像黍衙,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子悠瞬,可洞房花燭夜當晚...
    茶點故事閱讀 43,527評論 2 349

推薦閱讀更多精彩內容