自定义控件

自定义控件中调用顺序为 onMeasure()->onLayout()->onDraw()

需要调用全部构造方法

1.onMeasure(int widthMeasureSpec, int heightMeasureSpec)

(1)这个方法的作用是确定父控件与动态添加的子控件宽与高。

(2)重写此方法需必须加上setMeasuredDimension(parentWidthPX, parentHeightPX);即父控件的宽高(pix值)

(3)其它方法:childView.measure(childWidth, childHeight);  绘制子控件的宽高。 

(4)这里面有一个重要的类:MeasureSpec。其中重要的属性与方法有:

         MeasureSpec封装了父布局传递给子布局的布局要求,每个MeasureSpec代表了一组宽度和高度的要求。一个MeasureSpec由大小和模式组成。

         它有三种模式:(Mode)

                    UNSPECIFIED(未指定),  父元素不对自元素施加任何束缚,子元素可以得到任意想要的大小;

                    EXACTLY(完全),父元素决定自元素的确切大小,子元素将被限定在给定的边界里而忽略它本身大小;//固定大小,与fill_parent

                    AT_MOST(至多),子元素至多达到指定大小的值。    //wrap_parent

        它常用的三个函数:

       1.static int getMode(int measureSpec):根据提供的测量值(格式)提取模式(上述三个模式之一)

      2.static int getSize(int measureSpec):根据提供的测量值(格式)提取大小值(这个大小也就是我们通常所说的大小)//pix

      3.static int makeMeasureSpec(int size,int mode):根据提供的大小值和模式创建一个测量值(格式)  ,方法体如下:  //因为measure(width, height) 中参数就是这个返回值

       public static int makeMeasureSpec(int size, int mode) {
return size + mode;
}

 

2. onLayout()

  (1)这个方法的作用是控制控件的确切位置,即其中子控件放到父控件中哪个具体位置,其中就一个重要方法:

     childView.layout(left, top, right, bottom);  相对于父控件左上右下的具体px点

      (2)还有一个方法  view.getMeasureWidth() view.getMeasureHeight()  这两个是得到控件的实际大小,需要在view.measure()方法后才会有值

3. onDraw()

下面是一个自定义LinearLayout,主要作用为动态添加控件,支持自动换行

@see  http://blog.csdn.net/long704480904/article/details/9011115

Code:

/**
 * 自定义LinearLayout
 * 主要作用 支持自动换行
 */
public class CustomLayout extends ViewGroup {
    private int childWidth;    //子控件的宽
    private int childHeight;   //子控件的高

    private int columns;        //动态展示的列数

    //构造函数要写全,否则会报错
    public CustomLayout(Context context) {
        super(context);
    }

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

    public CustomLayout(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    /**
     * 设置需要添加的子控件的 宽、高 此处设置的宽高为最大值 具体显示的值需要看下面方法中mode设置
     *
     * @param childWidth MeasureSpec.makeMeasureSpec(size, mode) 中的size值
     * @see //MeasureSpec.makeMeasureSpec(size, mode)
     */
    public void setChild(int childWidth, int childHeight) {
        this.childWidth = childWidth;
        this.childHeight = childHeight;
    }

    /**
     * 控制子控件的换行
     * 参数中 relative to parent 是此LinearLayout相对于它的父控件,即此LinearLayout 的上一个控件
     *
     * @param changed   This is a new size or position for this view
     * @param leftPos   Left position, relative to parent
     * @param topPos    Top position, relative to parent
     * @param rightPos  Right position, relative to parent
     * @param bottomPos Bottom position, relative to parent
     */
    @Override
    protected void onLayout(boolean changed, int leftPos, int topPos, int rightPos, int bottomPos) {

        int childTotalWidth = childWidth;
        int childTotalHeight = childHeight;

        if (columns < 0) {
            columns = 1;
        }


        int curLeft = 0;     //当前 距离此控件 的左坐标   , 当换行是需要清零
        int curTop = 0;      //当前 距离此控件 的 上坐标  , 换行不需要清零
        int curColPos = 0;   //当前 绘制的第几列,用来控制换行的

        int count = getChildCount();
        for (int i = 0; i < count; i++) {
            final View childView = getChildAt(i);
            // 获取子控件Child的实际宽高值, 必须在此之前调用 child.measure() 方法,否则值为0
            int childActualWidth = childView.getMeasuredWidth();
            int childActualHeight = childView.getMeasuredHeight();

            // 计算子控件的顶点坐标,控制居中
            int left = curLeft + ((childTotalWidth - childActualWidth) / 2);
            int top = curTop + ((childTotalHeight - childActualHeight) / 2);

            // 布局子控件  这个方法可以固定指定子控件的左上右下的指定px位置,即可以通过column换行
            //重要方法, 左上右下是相对于此控件的
            childView.layout(left, top, left + childActualWidth, top + childActualHeight);

            if (curColPos >= (columns - 1)) {
                curColPos = 0;
                curLeft = 0;
                curTop += (childTotalHeight + switchDipToPix(10));  //高度差为10dip
            } else {
                curColPos++;
                curLeft += childTotalWidth;
            }
        }
    }

    /**
     * 计算控件及子控件所占区域
     *
     * @param widthMeasureSpec  size与mode 相加, 这个控件 size 为 px值  mode  MeasureSpec.AT_MOST|EXACTLY,wrap_parent|fill_parent or 具体值
     * @param heightMeasureSpec
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        // 创建测量参数, 下面的值是实际上控件的宽和高,因为MeasureSpec.AT_MOST的是wrap_parent的意思,即显示min_width,如果背景小于childWidth
        // 则有背景的时候,得到的width值为背景width,如果无背景,则显示最小的width
        // 如果 mode设置为MeasureSpec.EXACTLY  fill_parent的意思,则值等于前面赋予的size值,
        int cellWidthSpec = MeasureSpec.makeMeasureSpec(childWidth, MeasureSpec.AT_MOST);
        int cellHeightSpec = MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.AT_MOST);

        // 记录ViewGroup中Child的总个数
        int count = getChildCount();
        // 设置子空间Child的宽高
        for (int i = 0; i < count; i++) {
            View childView = getChildAt(i);
            childView.measure(cellWidthSpec, cellHeightSpec);
        }
        // 设置父容器控件所占区域大小
        int parentWidthPX = MeasureSpec.getSize(widthMeasureSpec);
        columns = parentWidthPX / childWidth;  //列数
        int lineCounts = 0;     //总共行数,用来计算整个LinearLayout的高度
        if (columns != 0) {
            lineCounts = (count / columns);
            if ((count % columns) != 0) {           //如果不能整除,证明还需要多一行
                lineCounts++;
            }
        }
        int parentHeightPX = (lineCounts * (childHeight + switchDipToPix(10)));   //行间距 10dip
        //重写onMeasure方法 必须加上 setMeasuredDimension()这个方法来设置这个控件最终的宽高!
        setMeasuredDimension(parentWidthPX, parentHeightPX);

        // 不需要调用父类的方法
        // super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

    private int switchDipToPix(int dipValue) {
        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dipValue, getResources().getDisplayMetrics());
    }

//    /**
//     * 为控件添加边框
//     */
//    @Override
//    protected void dispatchDraw(Canvas canvas) {
//        // 获取布局控件宽高
//        int width = getWidth();
//        int height = getHeight();
//        // 创建画笔
//        Paint mPaint = new Paint();
//        // 设置画笔的各个属性
//        mPaint.setColor(Color.BLUE);
//        mPaint.setStyle(Paint.Style.STROKE);
//        mPaint.setStrokeWidth(10);
//        mPaint.setAntiAlias(true);
//        // 创建矩形框
//        Rect mRect = new Rect(0, 0, width, height);
//        // 绘制边框
//        canvas.drawRect(mRect, mPaint);
//        // 最后必须调用父类的方法
//        super.dispatchDraw(canvas);
//    }
}

原文地址:https://www.cnblogs.com/lianghui66/p/3205758.html