├── .gitignore ├── .idea ├── .name ├── compiler.xml ├── copyright │ └── profiles_settings.xml ├── encodings.xml ├── gradle.xml ├── misc.xml ├── modules.xml └── runConfigurations.xml ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── openxu │ │ └── customlayout │ │ └── ApplicationTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── openxu │ │ │ └── customlayout │ │ │ ├── CustomLayout.java │ │ │ └── MainActivity.java │ └── res │ │ ├── layout │ │ └── activity_main.xml │ │ ├── mipmap-hdpi │ │ └── ic_launcher.png │ │ ├── mipmap-mdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxxhdpi │ │ └── ic_launcher.png │ │ └── values │ │ ├── attrs.xml │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── openxu │ └── customlayout │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── pic.png └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | -------------------------------------------------------------------------------- /.idea/.name: -------------------------------------------------------------------------------- 1 | CustomLayout -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 23 | 24 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 19 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 46 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # View-CustomLayout 2 | 3 | 自定义布局容器 4 | 5 | 讲解博客请参考:[http://blog.csdn.net/xmxkf/article/details/51500304](http://blog.csdn.net/xmxkf/article/details/51500304) 6 | 7 | ![示例图](https://github.com/openXu/View-CustomLayout/blob/master/pic.png) 8 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 21 5 | buildToolsVersion "21.1.2" 6 | defaultConfig { 7 | applicationId "com.openxu.customlayout" 8 | minSdkVersion 18 9 | targetSdkVersion 21 10 | versionCode 1 11 | versionName "1.0" 12 | } 13 | buildTypes { 14 | release { 15 | minifyEnabled false 16 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 17 | } 18 | } 19 | } 20 | 21 | dependencies { 22 | compile fileTree(dir: 'libs', include: ['*.jar']) 23 | testCompile 'junit:junit:4.12' 24 | compile 'com.android.support:appcompat-v7:21.0.3' 25 | } 26 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in D:\IDE\sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/openxu/customlayout/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package com.openxu.customlayout; 2 | 3 | import android.app.Application; 4 | import android.test.ApplicationTestCase; 5 | 6 | /** 7 | * Testing Fundamentals 8 | */ 9 | public class ApplicationTest extends ApplicationTestCase { 10 | public ApplicationTest() { 11 | super(Application.class); 12 | } 13 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /app/src/main/java/com/openxu/customlayout/CustomLayout.java: -------------------------------------------------------------------------------- 1 | package com.openxu.customlayout; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.content.Context; 5 | import android.content.res.TypedArray; 6 | import android.util.AttributeSet; 7 | import android.view.View; 8 | import android.view.ViewGroup; 9 | 10 | /** 11 | * 自定义布局管理器的示例。 这是一个相当全面的布局管理器,处理了所有布局情况。 可以简化为更具体的案例。 12 | */ 13 | public class CustomLayout extends ViewGroup { 14 | 15 | private static final String TAG = "CustomLayout"; 16 | 17 | public CustomLayout(Context context) { 18 | super(context); 19 | } 20 | 21 | public CustomLayout(Context context, AttributeSet attrs) { 22 | this(context, attrs, 0); 23 | } 24 | 25 | public CustomLayout(Context context, AttributeSet attrs, int defStyle) { 26 | super(context, attrs, defStyle); 27 | } 28 | 29 | /** 30 | * 要求所有的孩子测量自己的大小,然后根据这些孩子的大小完成自己的尺寸测量 31 | */ 32 | @SuppressLint("NewApi") 33 | @Override 34 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 35 | /** 36 | * 获得此ViewGroup上级容器为其推荐的宽和高,以及计算模式 37 | */ 38 | int widthMode = MeasureSpec.getMode(widthMeasureSpec); 39 | int heightMode = MeasureSpec.getMode(heightMeasureSpec); 40 | int sizeWidth = MeasureSpec.getSize(widthMeasureSpec); 41 | int sizeHeight = MeasureSpec.getSize(heightMeasureSpec); 42 | int layoutWidth = 0; 43 | int layoutHeight = 0; 44 | int cWidth = 0; 45 | int cHeight = 0; 46 | int count = getChildCount(); 47 | 48 | // 计算出所有的childView的宽和高 49 | for(int i = 0; i < count; i++){ 50 | View child = getChildAt(i); 51 | measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0); 52 | } 53 | CustomLayoutParams params = null; 54 | if(widthMode == MeasureSpec.EXACTLY){ 55 | //如果布局容器的宽度模式时确定的(具体的size或者match_parent) 56 | layoutWidth = sizeWidth; 57 | }else{ 58 | //如果是未指定或者wrap_content,我们都按照包裹内容做,宽度方向上只需要拿到所有子控件中宽度做大的作为布局宽度 59 | for (int i = 0; i < count; i++) { 60 | View child = getChildAt(i); 61 | cWidth = child.getMeasuredWidth(); 62 | params = (CustomLayoutParams) child.getLayoutParams(); 63 | //获取子控件宽度和左右边距之和,作为这个控件需要占据的宽度 64 | int marginWidth = cWidth+params.leftMargin+params.rightMargin; 65 | layoutWidth = marginWidth > layoutWidth ? marginWidth : layoutWidth; 66 | } 67 | } 68 | //高度很宽度处理思想一样 69 | if(heightMode == MeasureSpec.EXACTLY){ 70 | layoutHeight = sizeHeight; 71 | }else{ 72 | for (int i = 0; i < count; i++) { 73 | View child = getChildAt(i); 74 | cHeight = child.getMeasuredHeight(); 75 | params = (CustomLayoutParams) child.getLayoutParams(); 76 | int marginHeight = cHeight+params.topMargin+params.bottomMargin; 77 | layoutHeight = marginHeight > layoutHeight ? marginHeight : layoutHeight; 78 | } 79 | } 80 | 81 | // 测量并保存layout的宽高 82 | setMeasuredDimension(layoutWidth, layoutHeight); 83 | } 84 | 85 | /** 86 | * 为所有的子控件摆放位置. 87 | */ 88 | @Override 89 | protected void onLayout(boolean changed, int left, int top, int right, 90 | int bottom) { 91 | final int count = getChildCount(); 92 | int childMeasureWidth = 0; 93 | int childMeasureHeight = 0; 94 | CustomLayoutParams params = null; 95 | for (int i = 0; i < count; i++) { 96 | View child = getChildAt(i); 97 | // 注意此处不能使用getWidth和getHeight,这两个方法必须在onLayout执行完,才能正确获取宽高 98 | childMeasureWidth = child.getMeasuredWidth(); 99 | childMeasureHeight = child.getMeasuredHeight(); 100 | params = (CustomLayoutParams) child.getLayoutParams(); 101 | switch (params.position) { 102 | case CustomLayoutParams.POSITION_MIDDLE: // 中间 103 | left = (getWidth()-childMeasureWidth)/2 - params.rightMargin + params.leftMargin; 104 | top = (getHeight()-childMeasureHeight)/2 + params.topMargin - params.bottomMargin; 105 | break; 106 | case CustomLayoutParams.POSITION_LEFT: // 左上方 107 | left = 0 + params.leftMargin; 108 | top = 0 + params.topMargin; 109 | break; 110 | case CustomLayoutParams.POSITION_RIGHT: // 右上方 111 | left = getWidth()-childMeasureWidth - params.rightMargin; 112 | top = 0 + params.topMargin; 113 | break; 114 | case CustomLayoutParams.POSITION_BOTTOM: // 左下角 115 | left = 0 + params.leftMargin; 116 | top = getHeight()-childMeasureHeight-params.bottomMargin; 117 | break; 118 | case CustomLayoutParams.POSITION_RIGHTANDBOTTOM:// 右下角 119 | left = getWidth()-childMeasureWidth - params.rightMargin; 120 | top = getHeight()-childMeasureHeight-params.bottomMargin; 121 | break; 122 | default: 123 | break; 124 | } 125 | 126 | // 确定子控件的位置,四个参数分别代表(左上右下)点的坐标值 127 | child.layout(left, top, left+childMeasureWidth, top+childMeasureHeight); 128 | } 129 | 130 | } 131 | 132 | /** 133 | * 如果自定义布局参数,需要重写这个方法,返回我们自己的 134 | */ 135 | @Override 136 | public LayoutParams generateLayoutParams(AttributeSet attrs) { 137 | return new CustomLayoutParams(getContext(), attrs); 138 | } 139 | @Override 140 | protected LayoutParams generateLayoutParams(LayoutParams p) { 141 | return new CustomLayoutParams(p); 142 | } 143 | 144 | @Override 145 | protected LayoutParams generateDefaultLayoutParams() { 146 | return new CustomLayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); 147 | } 148 | 149 | @Override 150 | protected boolean checkLayoutParams(LayoutParams p) { 151 | return p instanceof CustomLayoutParams; 152 | } 153 | public static class CustomLayoutParams extends MarginLayoutParams { 154 | public static final int POSITION_MIDDLE = 0; // 中间 155 | public static final int POSITION_LEFT = 1; // 左上方 156 | public static final int POSITION_RIGHT = 2; // 右上方 157 | public static final int POSITION_BOTTOM = 3; // 左下角 158 | public static final int POSITION_RIGHTANDBOTTOM = 4; // 右下角 159 | 160 | public int position = POSITION_LEFT; 161 | 162 | public CustomLayoutParams(Context c, AttributeSet attrs) { 163 | super(c, attrs); 164 | TypedArray a = c.obtainStyledAttributes(attrs,R.styleable.CustomLayout); 165 | //获取设置在子控件上的位置属性 166 | position = a.getInt(R.styleable.CustomLayout_layout_position,position); 167 | 168 | a.recycle(); 169 | } 170 | 171 | public CustomLayoutParams(int width, int height) { 172 | super(width, height); 173 | } 174 | 175 | public CustomLayoutParams(LayoutParams source) { 176 | super(source); 177 | } 178 | 179 | } 180 | 181 | } 182 | -------------------------------------------------------------------------------- /app/src/main/java/com/openxu/customlayout/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.openxu.customlayout; 2 | import android.app.Activity; 3 | import android.os.Bundle; 4 | 5 | public class MainActivity extends Activity { 6 | @Override 7 | protected void onCreate(Bundle savedInstanceState) { 8 | super.onCreate(savedInstanceState); 9 | setContentView(R.layout.activity_main); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 |