Android自定义布局

首先我们观察Android API:

View.java

// 注意final修饰,该方法永远不会被覆盖,整个布局结构 measure方法唯一

public final void measure(int widthMeasureSpec, int heightMeasureSpec) {

         onMeasure(widthMeasureSpec, heightMeasureSpec);

}

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {}



//注意final修饰,该方法永远不会被覆盖,整个布局结构layout方法唯一

public final void layout(int l, int t, int r, int b) {

        boolean changed = setFrame(l, t, r, b);

        if (changed || (mPrivateFlags & LAYOUT_REQUIRED) == LAYOUT_REQUIRED) {

                  onLayout(changed, l, t, r, b);

        }

}

protected void onLayout(boolean changed, int left, int top, int right, int bottom) { } 空方法


ViewGroup.java extends View.java 

  @Override

   protected abstract void onLayout(boolean changed, int l, int t, int r, int b);

// 测量该ViewGroup所包含的所有布局

protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {}

protected void measureChild(View child, int parentWidthMeasureSpec,

            int parentHeightMeasureSpec) {}



//我会单讲mChildren数组mChildren中的View是如何来的。

public View getChildAt(int index) {  return mChildren[index];  }

public int getChildCount() {  return mChildrenCount; }
RelativeLayout.java extends ViewGroup.java

//当继承RelativeLayout布局时,我们应当覆盖该方法,以实现测量该布局包含的View,//此处的实现并不能测量所有的View

 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {}



protected void onLayout(boolean changed, int l, int t, int r, int b) {}

private void measureChild(View child, LayoutParams params, int myWidth, int myHeight) {}

//还包含一个重要的内部类,代表RelativeLayout所包含的每一个view大小及位置信息

public static class LayoutParams extends ViewGroup.MarginLayoutParams{

          private int mLeft, mTop, mRight, mBottom;

}

下面我要自定义一个布局,定义布局的目的肯定是为了向其内部放置View

CustomGridLayout.java extends RelativeLayout.java

初学者会问,我们到底需要继承RelativeLayout类的哪个方法呢!!

抛去一切,我们自己想象,布局控件需要

第一:控件(View)的大小 

第二:控件(View)的位置  

第三:知道要放置多少个View

通过熟读文档,我们应该知道:

onMeasure方法负责测量将要放在CustomGridLayout内部的View的大小。

onLayout方法负责分配尺寸及位置给将要放在CustomGridLayout内部的View。

所以很明显,需要我们继承的方法是

  1. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {}

功能:测量该布局所包含的所有View的大小(会在框架层循环取得每一个View,然后测量其大小),该方法会被View.java中的measure方法调用。而measure方法会被Window对象的DecorView调用

  1. protected void onLayout(boolean changed, int l, int t, int r, int b) {}

功能:在相应的位置放置相应的View,该方法会被View.java中的layout方法调用,而layout方法会被谁调用呢?

(1) 调用requestLayout()方法. 该方法内部会执行Object.layout(…)

(2) 直接调用 Object.layout(…)

(3) 调用addView(View child, …)时,

调用addView(…)之前一般需要先调用android.view.View.setLayoutParams(LayoutParams params)
这里写图片描述

也许有人会问当调用addView时,会和框架层的layout,onLayout,measure, onMeasure等几个布局方法有什么关系,或者说后者几个方法是怎么被触发的,别着急,看见addView(…)的底层实现了吗?

public void addView(View child, int index, LayoutParams params) {

        // addViewInner() will call child.requestLayout() when setting the new LayoutParams

        // therefore, we call requestLayout() on ourselves before, so that the child's 

        // request will be blocked at our level

        requestLayout(); 

        invalidate();

        addViewInner(child, index, params, false);

   }

很明显,addView在底层调用了requestLayout方法,该方法如时序图所示,会依次触发我们的onMeasure,onLayout方法。

示例1-子控件横向滚动的容器控件

public class LandspaceLayout extends ComplexLayout {
    private Rect mMaxChildSpace = new Rect();
    private Rect mLastChildSpace = new Rect();

    public LandspaceLayout(int w, int h) {
        super(w, h);
    }

    @Override
    public void reportChildLayout(Layout childLayout) {
        // 子控件布局时,会调用父控件布局类的这个方法。在这里记录需要传递给父控件布局的子控件布局信息。
        // 记录最大的子控件大小
        final Rect prune = childLayout.getNativeSpace();
        mMaxChildSpace.left = Math.max(mMaxChildSpace.left, prune.left);
        mMaxChildSpace.right = Math.max(mMaxChildSpace.right, prune.right);
        mMaxChildSpace.top = Math.max(mMaxChildSpace.top, prune.top);
        mMaxChildSpace.bottom = Math.max(mMaxChildSpace.bottom, prune.bottom);
    }

    @Override
    public void adjustSpace(Rect mySpace, Rect childSpace) {
        // 该方法会在子控件都布局完后调用,根据子控件的布局调整当前控件的一些布局设置,在这里可以调整每个子控件的布局位置
        // 未给定高度时,根据子控件大小改变当前控件高度
        if (!fixHeight()) {
            mySpace.bottom = mMaxChildSpace.bottom + mPaddingRect.bottom;
        }

        // 调整每个子控件的位置
        Element element = null;
        NodeList nodeList = mElement.getChildNodes();
        final int size = nodeList.getLength();
        for (int i = 0; i < size; i++) {
            Node child = nodeList.item(i);
            if (child.getNodeType() == Node.ELEMENT_NODE) {
                element = (Element) child;
                Layout layout = (Layout) element.getUserData(Entity.NODE_USER_STYLE);
                if (layout != null && layout instanceof ComplexLayout) {
                    // 将子控件按顺序横向排列
                    ComplexLayout childComplexLayout = (ComplexLayout) layout;
                    if (childComplexLayout.fixLocation()) {
                        // 子控件固定位置,不作处理
                        continue;
                    }
                    if (childComplexLayout.fixX()) {
                        // 固定X值,Y值调整为0或上边距
                        int offsetY = mMarginRect.top;
                        childComplexLayout.setStyleByName(Entity.NODE_ATTRIBUTE_TOP, Integer.toString(offsetY) + "nat");
                        childComplexLayout.requstLayout();
                    } else if (childComplexLayout.fixY()) {
                        // 固定Y值,横向向后排列
                        int offsetX = 0;
                        if (mLastChildSpace.right == 0) {
                            offsetX = mMarginRect.left;
                        } else {
                            offsetX = mLastChildSpace.right + childComplexLayout.mSpacingX;
                        }
                        childComplexLayout.setStyleByName(Entity.NODE_ATTRIBUTE_LEFT, Integer.toString(offsetX) + "nat");
                        childComplexLayout.requstLayout();
                    } else {
                        int offsetX = 0;
                        if (mLastChildSpace.right == 0) {
                            offsetX = mMarginRect.left;
                        } else {
                            offsetX = mLastChildSpace.right +     childComplexLayout.mSpacingX;
                        }
                        childComplexLayout.setStyleByName(Entity.NODE_ATTRIBUTE_LEFT, Integer.toString(offsetX) + "nat");
                        int offsetY = mMarginRect.top;
                        childComplexLayout.setStyleByName(Entity.NODE_ATTRIBUTE_TOP, Integer.toString(offsetY) + "nat");
                        childComplexLayout.requstLayout();
                    }
                    mLastChildSpace.set(childComplexLayout.getDisplaySpace());
                }
            }
        }
    }
}

示例2-子控件按圆形布局的容器控件

public class CircleLayout extends ComplexLayout {

    private Rect mMaxChildSpace = new Rect();
    private int mChildNum = 0;
    private int mRadius = 0;

    public CircleLayout(int w, int h) {
        super(w, h);
    }

    @Override
    public void reportChildLayout(Layout childLayout) {
        // 子控件布局时,会调用父控件布局类的这个方法。在这里记录需要传递给父控件布局的子控件布局信息。
        // 记录子控件个数
        mChildNum++;
        // 记录最大的子控件大小
        final Rect prune = childLayout.getNativeSpace();
        mMaxChildSpace.left = Math.min(mMaxChildSpace.left, prune.left);
        mMaxChildSpace.right = Math.max(mMaxChildSpace.right, prune.right);
        mMaxChildSpace.top = Math.min(mMaxChildSpace.top, prune.top);
        mMaxChildSpace.bottom = Math.max(mMaxChildSpace.bottom, prune.bottom);
    }

    @Override
    public void adjustSpace(Rect mySpace, Rect childSpace) {
        // 计算圆的半径
        mRadius = (getWidth() - mMaxChildSpace.width()) / 2;

        // 调整每个子控件的位置
        int count = 0;
        childSpace.setEmpty();
        Element element = null;
        NodeList nodeList = mElement.getChildNodes();
        final int size = nodeList.getLength();
        for (int i = 0; i < size; i++) {
            Node child = nodeList.item(i);
            if (child.getNodeType() == Node.ELEMENT_NODE) {
                element = (Element) child;
                Layout layout = (Layout) element.getUserData(Entity.NODE_USER_STYLE);
                if (layout != null && layout instanceof ComplexLayout) {
                    // 按照子控件个数,将园N等分,从正上方开始,顺时针算出每个点的坐标,作为每个子控件中心点的位置
                    double angle = count * Math.PI * 2 / mChildNum - Math.PI / 2;
                    ComplexLayout childComplexLayout = (ComplexLayout) layout;
                    int offsetX = (int) (getWidth() / 2 + mRadius * Math.cos(angle));
                    offsetX -= childComplexLayout.mWidth / 2;
                    childComplexLayout.setStyleByName(Entity.NODE_ATTRIBUTE_LEFT, Integer.toString(offsetX) + "nat");
                    int offsetY = (int) (getWidth() / 2 + mRadius * Math.sin(angle));
                    offsetY -= childComplexLayout.mHeight / 2;
                    childComplexLayout.setStyleByName(Entity.NODE_ATTRIBUTE_TOP, Integer.toString(offsetY) + "nat");
                    childComplexLayout.requstLayout();

                    childSpace.union(childComplexLayout.getDisplaySpace());

                    count++;
                }
            }
        }
        // 未给定高度时,根据子控件大小改变当前控件高度
        if (!fixHeight()) {
            mySpace.bottom = childSpace.bottom + mPaddingRect.bottom;
        }
    }
}

自定义viewgroup实现等分格子布局

一般的思路就是,直接写布局文件,用LinearLayout 嵌套多层子LinearLayout,然后根据权重layout_weight可以达到上面的效果

还有就是利用gridview了,但是这里的需求就是不能上下滑动,使用gridview的时候还要计算布局的高度,否则内容超出下滑;

开始我是用的第一种,直接在布局文件实现了,但是后来发现代码太多太恶心哦,所以我继承viewGroup,重写两个关键的方法:onLayout(),onMeasure()

我的大致思路:

1.计算当前视图宽度和高度,然后根据边距,算出每个布局的item需要分配的多少宽度和高度:

2.支持adapter的方式,动态添加每一项,还可以设置每一项点击事件

package com.allen.view;

import static android.view.View.MeasureSpec.EXACTLY;
import static android.view.View.MeasureSpec.makeMeasureSpec;
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;

import com.allen.mygridlayout.R;


/**
 * @author allen
 * @email jaylong1302@163.com
 * @date 2013-11-26 下午1:19:35
 * @company 富媒科技
 * @version 1.0
 * @description 格子布局(类似4.0中的gridlayout)
 */
public class MyGridLayout extends ViewGroup {
    private final String TAG = "MyGridLayout";

    int margin = 2;// 每个格子的水平和垂直间隔
    int colums = 2;
    private int mMaxChildWidth = 0;
    private int mMaxChildHeight = 0;
    int count = 0;

    GridAdatper adapter;

    public MyGridLayout(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        if (attrs != null) {
            TypedArray a = getContext().obtainStyledAttributes(attrs,
                    R.styleable.MyGridLayout);
            colums = a.getInteger(R.styleable.MyGridLayout_numColumns, 2);
            margin = (int) a.getInteger(R.styleable.MyGridLayout_itemMargin, 2);
        }
    }

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

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

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        // TODO Auto-generated method stub
        mMaxChildWidth = 0;
        mMaxChildHeight = 0;

        int modeW = 0, modeH = 0;
        if (MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.UNSPECIFIED)
            modeW = MeasureSpec.UNSPECIFIED;
        if (MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.UNSPECIFIED)
            modeH = MeasureSpec.UNSPECIFIED;

        final int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
                MeasureSpec.getSize(widthMeasureSpec), modeW);
        final int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
                MeasureSpec.getSize(heightMeasureSpec), modeH);

        count = getChildCount();
        if (count == 0) {
            super.onMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
            return;
        }
        for (int i = 0; i < count; i++) {
            final View child = getChildAt(i);
            if (child.getVisibility() == GONE) {
                continue;
            }

            child.measure(childWidthMeasureSpec, childHeightMeasureSpec);

            mMaxChildWidth = Math.max(mMaxChildWidth, child.getMeasuredWidth());
            mMaxChildHeight = Math.max(mMaxChildHeight,
                    child.getMeasuredHeight());
        }
        setMeasuredDimension(resolveSize(mMaxChildWidth, widthMeasureSpec),
                resolveSize(mMaxChildHeight, heightMeasureSpec));
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        // TODO Auto-generated method stub
        int height = b - t;// 布局区域高度
        int width = r - l;// 布局区域宽度
        int rows = count % colums == 0 ? count / colums : count / colums + 1;// 行数
        if (count == 0)
            return;
        int gridW = (width - margin * (colums - 1)) / colums;// 格子宽度
        int gridH = (height - margin * rows) / rows;// 格子高度

        int left = 0;
        int top = margin;

        for (int i = 0; i < rows; i++) {// 遍历行
            for (int j = 0; j < colums; j++) {// 遍历每一行的元素
                View child = this.getChildAt(i * colums + j);
                if (child == null)
                    return;
                left = j * gridW + j * margin;
                // 如果当前布局宽度和测量宽度不一样,就直接用当前布局的宽度重新测量
                if (gridW != child.getMeasuredWidth()
                        || gridH != child.getMeasuredHeight()) {
                    child.measure(makeMeasureSpec(gridW, EXACTLY),
                            makeMeasureSpec(gridH, EXACTLY));
                }
                child.layout(left, top, left + gridW, top + gridH);
                // System.out
                // .println("--top--" + top + ",bottom=" + (top + gridH));

            }
            top += gridH + margin;
        }
    }

    public interface GridAdatper {
        View getView(int index);

        int getCount();
    }

    /** 设置适配器 */
    public void setGridAdapter(GridAdatper adapter) {
        this.adapter = adapter;
        // 动态添加视图
        int size = adapter.getCount();
        for (int i = 0; i < size; i++) {
            addView(adapter.getView(i));
        }
    }

    public interface OnItemClickListener {
        void onItemClick(View v, int index);
    }

    public void setOnItemClickListener(final OnItemClickListener click) {
        if (this.adapter == null)
            return;
        for (int i = 0; i < adapter.getCount(); i++) {
            final int index = i;
            View view = getChildAt(i);
            view.setOnClickListener(new View.OnClickListener() {

                @Override
                public void onClick(View v) {
                    // TODO Auto-generated method stub
                    click.onItemClick(v, index);
                }
            });
        }
    }

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值