自定義ViewGroup之自定義佈局的實現
阿新 • • 發佈:2019-01-22
圖片預覽
1. 分析
1. 自定義簡易FrameLayout 分別左上,右上,左下,右下4個子View
2. 自定義簡易LinearLayout,實現橫向和縱向佈局
3. 自定義簡易RelativeLayout,實現layout_alignParentXXX方法
2. 實現原理
1. 自定義ViewGroup主要是複寫onMeasure測量每一個子View的寬高,複寫onLayout計算每一個子View的上下左右間距
2. 自定義一些屬性值
3. 複寫generateLayoutParams,計算margin值
3. 簡易FrameLayout實現
1. onMeasure
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//分別獲取寬高的測量模式
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
//記錄如果是wrap_content時設定的寬和高
int width, height;
//左右的高度
int lHeight = 0,rHeight = 0;
//上下的寬度
int tWidth = 0, bWidth = 0;
//測量所有子孩子的寬高
measureChildren(widthMeasureSpec,heightMeasureSpec);
for (int i = 0; i < getChildCount(); i++) {
View childView = getChildAt(i);
int childWidth = childView.getMeasuredWidth();
int childHeight = childView.getMeasuredHeight();
MarginLayoutParams mParams = (MarginLayoutParams) childView.getLayoutParams();
if(i ==0 || i == 1){
tWidth += childWidth + mParams.leftMargin + mParams.rightMargin;
}
if(i == 2 || i == 3){
bWidth += childWidth + mParams.leftMargin + mParams.rightMargin;
}
if(i == 0 || i== 2){
lHeight += childHeight + mParams.topMargin + mParams.bottomMargin;
}
if(i == 1 || i == 3){
rHeight += childHeight + mParams.topMargin + mParams.bottomMargin;
}
}
//wrap_content 時取寬高的最大值
width = Math.max(tWidth,bWidth);
height = Math.max(lHeight,rHeight);
int measureWidth = widthMode == MeasureSpec.EXACTLY ? widthSize : width;
int measureHeight = heightMode == MeasureSpec.EXACTLY ? heightSize : height;
setMeasuredDimension(measureWidth,measureHeight);
}
2. onLayout 需要注意的一個地方是最好計算一下父View的pading值
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
for (int i = 0; i < getChildCount(); i++) {
View childView = getChildAt(i);
int childWidth = childView.getMeasuredWidth();
int childHeight = childView.getMeasuredHeight();
MarginLayoutParams cParams = (MarginLayoutParams) childView.getLayoutParams();
int childLeft = 0,childTop = 0,childRight = 0,childBottom = 0;
switch (i){
case 0:
childLeft = cParams.leftMargin;
childTop = cParams.topMargin+ getPaddingTop();
break;
case 1:
childLeft = getMeasuredWidth() - childWidth - cParams.rightMargin;
childTop = cParams.topMargin + getPaddingTop();
break;
case 2:
childLeft = cParams.leftMargin;
childTop = getMeasuredHeight() - childHeight - cParams.bottomMargin
- getPaddingBottom() - getPaddingTop();
break;
case 3:
childLeft = getMeasuredWidth() - childWidth - cParams.rightMargin;
childTop = getMeasuredHeight() - childHeight - cParams.bottomMargin
-getPaddingTop() - getPaddingBottom();
break;
default:
}
childRight = childLeft + childWidth;
childBottom = childTop + childHeight;
childView.layout(childLeft,childTop,childRight,childBottom);
}
}
4. 簡易LinearLayout實現
分橫向和豎向
橫向的時候當寬高為wrap_content的時候,寬度累加+左右padding值
高度取子View的最大高度
豎向的時候當寬高為wrap_content的時候,寬度取子View中的最大寬度,高度累加+上下padding值
onLayout主要是計運算元View的left,top,right,bottom的距離,細心一點就好
1. onMeasure
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
//記錄如果是wrap_content時設定的寬和高
int width, height;
int totalWidth = 0;
int totalHeight = 0;
//測量所有子孩子的寬高
measureChildren(widthMeasureSpec,heightMeasureSpec);
for (int i = 0; i < getChildCount(); i++) {
View childView = getChildAt(i);
int childWidth = childView.getMeasuredWidth();
int childHeight = childView.getMeasuredHeight();
MarginLayoutParams mParams = (MarginLayoutParams) childView.getLayoutParams();
if("horizontal".equals(mOrientation)){
//橫向高度取子孩子中高度最大值 帶上margin和父View的padding值
int cHeight = childHeight + mParams.topMargin + mParams.bottomMargin + this.getPaddingBottom() + this.getPaddingTop();
if(cHeight > totalHeight){
totalHeight = cHeight;
}
//橫向第一個子孩子和最後一個子孩子需要帶上父View的padding值
if(i == 0){
totalWidth+= getPaddingLeft();
}else if(i == getChildCount() - 1){
totalWidth += getPaddingRight();
}
totalWidth += childWidth + mParams.leftMargin + mParams.rightMargin;
}else {
//豎向寬度取其中一個子孩子的最大高度
int cWidth = childWidth + mParams.leftMargin + mParams.rightMargin + this.getPaddingLeft() + this.getPaddingRight();
if (cWidth > totalWidth) {
totalWidth = cWidth;
}
//豎向的第一子孩子和最後一個子孩子需要帶上父View的padding值
if(i == 0){
totalHeight += getPaddingTop();
}else if(i == getChildCount() - 1){
totalHeight += getPaddingBottom();
}
totalHeight += childHeight + mParams.topMargin + mParams.bottomMargin;
}
}
width = totalWidth;
height = totalHeight;
int measureWidth = widthMode == MeasureSpec.EXACTLY ? widthSize : width;
int measureHeight = heightMode == MeasureSpec.EXACTLY ? heightSize : height;
setMeasuredDimension(measureWidth,measureHeight);
}
2. onLayout
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int childLeft ;
int childTop ;
int childRight ;
int childBottom ;
int lastTotalHeight = 0;
int lastTotalWidth = 0;
for (int i = 0; i < getChildCount(); i++) {
View childView = getChildAt(i);
int childWidth = childView.getMeasuredWidth();
int childHeight = childView.getMeasuredHeight();
MarginLayoutParams cParams = (MarginLayoutParams) childView.getLayoutParams();
if("horizontal".equals(mOrientation)) {
//橫向第一個帶上父View的左邊padding值
if(i == 0){
childLeft = cParams.leftMargin + getPaddingLeft();
}else {
childLeft = lastTotalWidth + cParams.leftMargin;
}
//橫向最後一個帶上父View的右邊padding值
if(i == getChildCount() - 1) {
childRight = childLeft + childWidth+ getPaddingRight();
}else{
childRight = childLeft + childWidth;
}
lastTotalWidth = childRight;
//橫向上下帶上上下padding值
childTop = cParams.topMargin + getPaddingTop();
childBottom = childTop + childHeight + getPaddingBottom();
}else{
//豎向左右帶上左右padding值
childLeft = cParams.leftMargin + getPaddingLeft();
childRight = childLeft + childWidth + getPaddingRight();
//豎向第一個帶上父View的上邊padding值
if(i == 0){
childTop = cParams.topMargin + getPaddingTop();
}else {
childTop = lastTotalHeight + cParams.topMargin;
}
////豎向最後個帶上父View的下邊padding值
if(i == getChildCount() - 1){
childBottom = childTop + childHeight+ getPaddingBottom();
}else {
childBottom = childTop + childHeight;
}
lastTotalHeight = childBottom;
}
childView.layout(childLeft,childTop,childRight,childBottom);
}
}
5. 簡易RelativeLayout實現
onMeasure
當寬高為wrap_content的時候,寬度取子View的最大寬度
高度取子View的最大高度,記得帶上左右padding值
onLayout
根據自定義的子View的不同位置layout_position,計運算元View的left,top,right,bottom的距離。
1. onMeasure
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
//用於計算wrap_content時候的寬高
int width = 0;
int height = 0;
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
//測量每一個子孩子
measureChild(child,widthMeasureSpec,heightMeasureSpec);
CustomLayoutParam lp = (CustomLayoutParam) child.getLayoutParams();
int lrMargin = lp.leftMargin + lp.rightMargin;
int tbMargin = lp.topMargin + lp.bottomMargin;
int childWidth = child.getMeasuredWidth() + lrMargin;
int childHeight = child.getMeasuredHeight() + tbMargin;
//獲取子孩子中最大的子孩子寬度
if(width < childWidth){
width = childWidth;
}
//獲取子孩子總最大的子孩子高度
if(height < childHeight){
height = childHeight;
}
}
//帶上父View的上下左右的pading
width += getPaddingLeft() + getPaddingRight();
height += getPaddingTop() + getPaddingBottom();
int measureWidth = widthMode == MeasureSpec.AT_MOST ? width : widthSize;
int measureHeight = heightMode == MeasureSpec.AT_MOST ? height : heightSize;
setMeasuredDimension(measureWidth,measureHeight);
}
2. onLayout
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int pWidth = getMeasuredWidth();
int pHeight = getMeasuredHeight();
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
CustomLayoutParam lp = (CustomLayoutParam) child.getLayoutParams();
int lrMargin = lp.leftMargin + lp.rightMargin;
int tbMargin = lp.topMargin + lp.bottomMargin;
int cWidthAndMargin = child.getMeasuredWidth() + lrMargin;
int cHeightAndMargin = child.getMeasuredHeight() + tbMargin;
int cWidth = child.getMeasuredWidth();
int cHeight = child.getMeasuredHeight();
int left = 0;
int top = 0;
int right = 0;
int bottom = 0;
switch (lp.getPosition()){
case "leftTop":
left = lp.leftMargin + getPaddingLeft();
top = lp.topMargin + getPaddingTop();
break;
case "rightTop":
left = pWidth - cWidthAndMargin- getPaddingRight();
top = lp.topMargin + getPaddingTop();
right += getPaddingRight();
break;
case "leftBottom":
left = lp.leftMargin + getPaddingLeft();
top = pHeight - cHeightAndMargin;
bottom += getPaddingBottom();
break;
case "rightBottom":
left = pWidth - cWidthAndMargin- getPaddingLeft();
top = pHeight - cHeightAndMargin;
bottom += getPaddingBottom();
break;
case "horizontalCenter":
left = (pWidth - cWidth) / 2;
top = lp.topMargin + getPaddingTop();
break;
case "verticalCenter":
left = lp.leftMargin + getPaddingLeft();
top = (pHeight - cHeight) / 2;
break;
case "rightVerticalCenter":
left = pWidth - cWidthAndMargin- getPaddingLeft();
top = (pHeight - cHeight) / 2;
break;
case "bottomHorizontalCenter":
left = (pWidth - cWidth) / 2;
top = pHeight - cHeightAndMargin;
bottom += getPaddingBottom();
break;
case "center":
left = (pWidth - cWidth) / 2;
top = (pHeight - cHeight) / 2;
break;
}
right += left + child.getMeasuredWidth();
bottom += top + child.getMeasuredHeight();
child.layout(left,top,right,bottom);
}
}
6. 複寫generateLayoutParams方法
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new CustomLayoutParam(getContext(),attrs);
}
public class CustomLayoutParam extends ViewGroup.MarginLayoutParams{
/**
* leftTop
* rightTop
* horizontalCenter
* verticalCenter
* rightVerticalCenter
* bottomHorizontalCenter
* center
* leftBottom
* rightBottom
*/
private String mPosition;
public CustomLayoutParam(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.MyRelativeLayout);
mPosition = typedArray.getString(R.styleable.MyRelativeLayout_layout_position);
if(TextUtils.isEmpty(mPosition)){
mPosition = "leftTop";
}
typedArray.recycle();
}
public String getPosition() {
return mPosition;
}
}
7. 專案原始碼下載
後面統一提供下載地址
8. 聯絡方式
QQ:1509815887