Android自定义布局
2015-08-19 16:43
471 查看
首先我们观察Android API:
初学者会问,我们到底需要继承RelativeLayout类的哪个方法呢!!
抛去一切,我们自己想象,布局控件需要
通过熟读文档,我们应该知道:
onMeasure方法负责测量将要放在CustomGridLayout内部的View的大小。
onLayout方法负责分配尺寸及位置给将要放在CustomGridLayout内部的View。
所以很明显,需要我们继承的方法是
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {}
功能:测量该布局所包含的所有View的大小(会在框架层循环取得每一个View,然后测量其大小),该方法会被View.java中的measure方法调用。而measure方法会被Window对象的DecorView调用
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(…)的底层实现了吗?
很明显,addView在底层调用了requestLayout方法,该方法如时序图所示,会依次触发我们的onMeasure,onLayout方法。
还有就是利用gridview了,但是这里的需求就是不能上下滑动,使用gridview的时候还要计算布局的高度,否则内容超出下滑;
开始我是用的第一种,直接在布局文件实现了,但是后来发现代码太多太恶心哦,所以我继承viewGroup,重写两个关键的方法:onLayout(),onMeasure()
我的大致思路:
1.计算当前视图宽度和高度,然后根据边距,算出每个布局的item需要分配的多少宽度和高度:
2.支持adapter的方式,动态添加每一项,还可以设置每一项点击事件
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。
所以很明显,需要我们继承的方法是
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {}
功能:测量该布局所包含的所有View的大小(会在框架层循环取得每一个View,然后测量其大小),该方法会被View.java中的measure方法调用。而measure方法会被Window对象的DecorView调用
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); } }); } } }
相关文章推荐
- 使用C++实现JNI接口需要注意的事项
- Android IPC进程间通讯机制
- Android Manifest 用法
- [转载]Activity中ConfigChanges属性的用法
- Android之获取手机上的图片和视频缩略图thumbnails
- Android之使用Http协议实现文件上传功能
- Android学习笔记(二九):嵌入浏览器
- android string.xml文件中的整型和string型代替
- i-jetty环境搭配与编译
- android之定时器AlarmManager
- android wifi 无线调试
- Android Native 绘图方法
- Android java 与 javascript互访(相互调用)的方法例子
- android 代码实现控件之间的间距
- android FragmentPagerAdapter的“标准”配置
- Android"解决"onTouch和onClick的冲突问题
- android:installLocation简析
- android searchView的关闭事件
- SourceProvider.getJniDirectories