Q: How do I create a layout as shown in?
Figure 1
(Original address: http://blog.csdn.net/vector_yi/article/details/24415537)
You may say that you can use RelativeLayout and margins. Indeed, the following XML code can easily build a similar layout:
<RelativeLayout xmlns:android = "http://schemas.android.com/apk/res/android" android:layout_width= "fill_parent" android:layout_height= "fill_parent" > <View android:layout_width ="100dp" android:layout_height ="150dp" android:background ="#FF0000" /> <View android:layout_width ="100dp" android:layout_height ="150dp" android:layout_marginLeft ="30dp" android:layout_marginTop ="20dp" android:background ="#00FF00" /> <View android:layout_width ="100dp" android:layout_height ="150dp" android:layout_marginLeft ="60dp" android:layout_marginTop ="40dp" android:background ="#0000FF" /></RelativeLayout>
Effect 2:
Figure 2
However, when a similar layout is complex and variable, the use of margins may seem complicated. Here, we can look at another way to create a similar layout-the benefits of custom ViewGroup are as follows:
- It is easier to maintain when you apply the layout to different activities.
- You can use custom attributes to customize each sub-View in the ViewGroup.
- More concise and readable XML file content
- If you need to change the margin, you do not need to manually calculate the margin of each sub-View.
1. Understand the steps to draw a View in Android. For more information about the steps to draw a View, see the official documentation of Android: http://developer.android.com/guide/topics/ui/how-android-draws.html here, we focus on the draw process of ViewGroup: 1. process the width and height of ViewGroup. the operation for processing width and height is performed in the onMeasure () method. In this method, ViewGroup calculates the layout space occupied by it based on its sub-View. 2. layout to the page. This operation is performed in the onLayout () method. In this method, ViewGroup draws each of its subviews Based on the information obtained from onMeasure.
Ii. Construct the CascadeLayout class first, add CascadeLayout in the XML layout file:
<FrameLayout <! -- Custom namespace to use custom properties below --> xmlns: cascade = "http://schemas.android.com/apk/res/com.manning.androidhacks.hack003" xmlns: android = "http://schemas.android.com/apk/res/android" android: layout_width = "fill_parent" android: layout_height = "fill_parent"> <com. manning. androidhacks. hack003.view. cascadeLayout android: layout_width = "fill_parent" android: layout_height = "fill_parent" cascade: horizontal_spacing = "30dp" <! -- Because the cascade namespace is added, you can use the Custom Attributes here --> cascade: vertical_spacing = "20dp"> <View android: layout_width = "100dp" android: layout_height = "150dp" cascade: layout_vertical_spacing = "90dp" <! -- The Custom Attributes added for the sub-View are used in the third part of this article --> android: background = "# FF0000"/> <View android: layout_width = "100dp" android: layout_height = "150dp" android: background = "#00FF00"/> <View android: layout_width = "100dp" android: layout_height = "150dp" android: background = "# 0000FF"/> </com. manning. androidhacks. hack003.view. cascadeLayout> </FrameLayout>
To use these custom attributes, we must define them. Create an attrs. xml file in the res/values Folder:
<? xml version ="1.0" encoding= "utf-8" ?><resources> <declare-styleable name= "CascadeLayout" > <attr name= "horizontal_spacing" format = "dimension" /> <attr name= "vertical_spacing" format = "dimension" /> </declare-styleable></resources>
Then, when we create CascadeLayout without specifying horizontal_spacing and vertical_spacing for it, we need a default value. We predefine this default value and place it in dimens. xml in the res/values Folder:
<? xml version ="1.0" encoding= "utf-8" ?><resources> <dimen name= "cascade_horizontal_spacing" >10dp</dimen> <dimen name= "cascade_vertical_spacing" >10dp</dimen></resources>
Finally, we need to create a Java class named CascadeLayout, which inherits the ViewGroup and overwrites the onMeasure () and OnLayout () methods. 1. constructor of CascadeLayout
public CascadeLayout (Context context, AttributeSet attrs) { super( context, attrs); TypedArray a = context .obtainStyledAttributes (attrs , R. styleable. CascadeLayout ); try { mHorizontalSpacing = a. getDimensionPixelSize( R. styleable. CascadeLayout_horizontal_spacing , getResources ().getDimensionPixelSize ( R. dimen. cascade_horizontal_spacing )); mVerticalSpacing = a. getDimensionPixelSize( R. styleable. CascadeLayout_vertical_spacing , getResources () .getDimensionPixelSize (R .dimen .cascade_vertical_spacing )); } finally { a .recycle (); }2. Build a custom LayoutParams class. The LayoutParams class will exist as the internal class of CascadeLayout. It will store the x and y coordinates of each sub-View. Definition:
public static class LayoutParams extends ViewGroup .LayoutParams { int x; int y; public LayoutParams( Context context , AttributeSet attrs) { super (context , attrs ); } public LayoutParams( int w , int h ) { super (w , h ); } }
3. override the onMeasure () method will be the most important part of the CascadeLayout class. This method not only calculates the layout space occupied by the entire ViewGroup, the layout space occupied by each sub-View is also calculated.
@Override protected void onMeasure (int widthMeasureSpec , int heightMeasureSpec ) { int width = 0; int height = getPaddingTop (); final int count = getChildCount (); for ( int i = 0; i < count; i++) { View child = getChildAt (i ); measureChild (child , widthMeasureSpec , heightMeasureSpec ); LayoutParams lp = (LayoutParams ) child .getLayoutParams (); width = getPaddingLeft () + mHorizontalSpacing * i; lp .x = width; lp .y = height; width += child .getMeasuredWidth (); height += mVerticalSpacing ; } width += getPaddingRight (); height += getChildAt (getChildCount () - 1). getMeasuredHeight () + getPaddingBottom (); setMeasuredDimension ( resolveSize( width, widthMeasureSpec ), resolveSize( height, heightMeasureSpec )); }
4. In the last step, the code for rewriting the onLayout () method is very simple, that is, every subview calls the layout () method.
@Override protected void onLayout (boolean changed, int l , int t , int r , int b ) { final int count = getChildCount (); for ( int i = 0; i < count; i++) { View child = getChildAt (i ); LayoutParams lp = ( LayoutParams ) child .getLayoutParams (); child .layout (lp .x , lp .y , lp .x + child. getMeasuredWidth (), lp .y + child .getMeasuredHeight ()); } }
Now, a layout page with the same effect as Figure 2 is created using the custom ViewGroup.
3. Add custom attributes for the sub-View
How can it be the same as the XML code of a few lines? Next, we will add custom attributes for the Child View in CascadeLayout: first, add the following code to the attrs. xml file created earlier:
<declare-styleable name="CascadeLayout_LayoutParams"> <attr name="layout_vertical_spacing" format="dimension" /></declare-styleable>
Because the newly added property starts with layout _, it will be added to LayoutParams. We can read this attribute from the constructor in the Custom internal class LayoutParams and change the first constructor:
public LayoutParams (Context context, AttributeSet attrs) { super (context , attrs ); TypedArray a = context .obtainStyledAttributes (attrs , R. styleable. CascadeLayout_LayoutParams ); try { verticalSpacing = a .getDimensionPixelSize ( R .styleable .CascadeLayout_LayoutParams_layout_vertical_spacing , -1 ); } finally { a .recycle (); } }
Since a new custom attribute is added, it must be processed in the onMeasure () method:
@Override protected void onMeasure (int widthMeasureSpec , int heightMeasureSpec ) { int width = getPaddingLeft (); int height = getPaddingTop (); int verticalSpacing ; final int count = getChildCount (); for ( int i = 0; i < count; i++) { verticalSpacing = mVerticalSpacing ; View child = getChildAt (i ); measureChild (child , widthMeasureSpec , heightMeasureSpec ); LayoutParams lp = ( LayoutParams ) child .getLayoutParams (); width = getPaddingLeft () + mHorizontalSpacing * i; lp .x = width; lp .y = height; if (lp .verticalSpacing >= 0 ) { verticalSpacing = lp .verticalSpacing ; } width += child .getMeasuredWidth (); height += verticalSpacing ; } width += getPaddingRight (); height += getChildAt (getChildCount () - 1). getMeasuredHeight () + getPaddingBottom (); setMeasuredDimension ( resolveSize( width, widthMeasureSpec ), resolveSize( height, heightMeasureSpec )); }
The complete CascadeLayout code is attached:
package com.manning.androidhacks.hack003.view;import android.content.Context;import android.content.res.TypedArray;import android.util.AttributeSet;import android.view.View;import android.view.ViewGroup;import com.manning.androidhacks.hack003.R;public class CascadeLayout extends ViewGroup { private int mHorizontalSpacing; private int mVerticalSpacing; public CascadeLayout(Context context, AttributeSet attrs) { super(context, attrs); TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CascadeLayout); try { mHorizontalSpacing = a.getDimensionPixelSize( R.styleable.CascadeLayout_horizontal_spacing, getResources().getDimensionPixelSize( R.dimen.cascade_horizontal_spacing)); mVerticalSpacing = a.getDimensionPixelSize( R.styleable.CascadeLayout_vertical_spacing, getResources() .getDimensionPixelSize(R.dimen.cascade_vertical_spacing)); } finally { a.recycle(); } } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int width = getPaddingLeft(); int height = getPaddingTop(); int verticalSpacing; final int count = getChildCount(); for (int i = 0; i < count; i++) { verticalSpacing = mVerticalSpacing; View child = getChildAt(i); measureChild(child, widthMeasureSpec, heightMeasureSpec); LayoutParams lp = (LayoutParams) child.getLayoutParams(); width = getPaddingLeft() + mHorizontalSpacing * i; lp.x = width; lp.y = height; if (lp.verticalSpacing >= 0) { verticalSpacing = lp.verticalSpacing; } width += child.getMeasuredWidth(); height += verticalSpacing; } width += getPaddingRight(); height += getChildAt(getChildCount() - 1).getMeasuredHeight() + getPaddingBottom(); setMeasuredDimension(resolveSize(width, widthMeasureSpec), resolveSize(height, heightMeasureSpec)); } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { final int count = getChildCount(); for (int i = 0; i < count; i++) { View child = getChildAt(i); LayoutParams lp = (LayoutParams) child.getLayoutParams(); child.layout(lp.x, lp.y, lp.x + child.getMeasuredWidth(), lp.y + child.getMeasuredHeight()); } } @Override protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { return p instanceof LayoutParams; } @Override protected LayoutParams generateDefaultLayoutParams() { return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); } @Override public LayoutParams generateLayoutParams(AttributeSet attrs) { return new LayoutParams(getContext(), attrs); } @Override protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { return new LayoutParams(p.width, p.height); } public static class LayoutParams extends ViewGroup.LayoutParams { int x; int y; public int verticalSpacing; public LayoutParams(Context context, AttributeSet attrs) { super(context, attrs); TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CascadeLayout_LayoutParams); try { verticalSpacing = a .getDimensionPixelSize( R.styleable.CascadeLayout_LayoutParams_layout_vertical_spacing, -1); } finally { a.recycle(); } } public LayoutParams(int w, int h) { super(w, h); } }}
Project directory structure:
(Original address: http://blog.csdn.net/vector_yi/article/details/24415537)