[Android development tips] Getting started-how to customize ViewGroup and androidviewgroup

Source: Internet
Author: User

[Android development tips] Getting started-how to customize ViewGroup and androidviewgroup

This article is original by Sun Guowei. If you need to reprint it, please indicate the source!


This article is about how to customize ViewGroup. For new users, custom ViewGroup is something that can be mastered by a great person, but it is daunting.

Don't be afraid. Remember, "everything new is a paper tiger. If you are willing to spend time researching, you will never learn anything ".

Let's get rid of the secrets of custom viewgroups.

By convention, let's start with an example.


It's easy. Three cards are stacked and displayed together. How can this layout effect be achieved? Some people should say that. This is very simple. Use RelativeLayout or FrameLayout, and then set margin for every playing card.

OK, let's see how this method is implemented. The Code is as follows:

<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>

Yes, it can be implemented in this way. However, do you think this method is a bit low ?! Let's implement it in a more advanced way and improve our own strength!

Before customizing ViewGroup, we need to understand several concepts.


How Android draws a view
I won't cover too many details here, but I need to understand the following section in the Android development document:

"The draw layout consists of two traversal processes: the measurement process and the layout process. The measurement process is completed by the measure (int, int) method, which traverses the view tree from top to bottom. During recursive traversal, each view transmits dimensions and specifications to the lower layer. When the measure method traversal ends, each view stores its own size information. The second process is completed by the layout (int, int) method, which also traverses the view tree from top to bottom. During the traversal process, each parent view locates the positions of all child views based on the results of the measurement process."

In short, the first step is to measure the width and height of the ViewGroup. In the onMeasure () method, the ViewGroup traverses all the subviews to calculate their size. The second step is to layout all the child views based on the dimensions obtained in the first step, which is completed in onLayout.


Create CascadeLayout

Finally, we reached the custom ViewGroup stage. Suppose we have customized a CascadeLayout container, we will use it like this.

<FrameLayout xmlns: cascade = "http://schemas.android.com/apk/res/com.manoel.custom" <! -- Declare namespace --> xmlns: android = "http://schemas.android.com/apk/res/android" android: layout_width = "fill_parent" android: layout_height = "fill_parent"> <com. manoel. view. cascadeLayout android: layout_width = "fill_parent" android: layout_height = "fill_parent" <! -- Custom Attributes --> cascade: horizontal_spacing = "30dp" cascade: vertical_spacing = "20dp"> <View android: layout_width = "100dp" android: layout_height = "150dp" 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. manoel. view. cascadeLayout> </FrameLayout>

First, define attributes. Create attrs. xml under the values folder. The Code is as follows:

<resources>    <declare-styleable name="CascadeLayout">        <attr name="horizontal_spacing" format="dimension" />        <attr name="vertical_spacing" format="dimension" />    </declare-styleable></resources>
At the same time, to be more rigorous, define some default vertical distance and horizontal distance, in case these attributes are not provided in the layout.

Add the following code to dimens. xml:

<resources>    <dimen name="cascade_horizontal_spacing">10dp</dimen>    <dimen name="cascade_vertical_spacing">10dp</dimen></resources>
The preparation is complete. Next, let's take a look at the source code of CascadeLayout, which is a little long. We will help you analyze it later.

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);    }    public LayoutParams(int w, int h) {      super(w, h);    }  }}

First, analyze the constructor.

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();    }  }
If CasecadeLayout is used in the layout, the system will call this constructor. You should know this. Why is not explained here. If you are interested, you can look at the source code, focusing on how the system parses the xml layout.

The constructor is simple, that is, to obtain the horizontal distance and vertical distance through the properties in the layout file.


Then, analyze the custom LayoutParams.

This class is used to save the x and Y axes of each subview. It is defined as a static internal class. Ps: when it comes to static internal classes, I think about the multi-thread Memory leakage problem again. If you have time to explain the memory leakage caused by multithreading.

public static class LayoutParams extends ViewGroup.LayoutParams {    int x;    int y;    public int verticalSpacing;    public LayoutParams(Context context, AttributeSet attrs) {      super(context, attrs);    }    public LayoutParams(int w, int h) {      super(w, h);    }  }

In addition, you also need to override some methods, such as checkLayoutParams () and generateDefaultLayoutParams (). This method is often the same between different viewgroups.


Next, analyze 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 ); // make each sub-view measure its own LayoutParams lp = (LayoutParams) child. getLayoutParams (); width = getPaddingLeft () + mHorizontalSpacing * I; // Save the x and Y coordinate lp of each subview. 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 (); // use the calculated width and height to set the measurement size of the entire layout setMeasuredDimension (resolveSize (width, widthMeasureSpec), resolveSize (height, heighturespec ));}


Finally, the onLayout () method is analyzed.

  @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());    }  }
The logic is simple. The value calculated using the onMeasure () method is the parameter that calls the layout () method of the sub-View cyclically.


Add custom attributes for the subview

As an example, we will add a subview to override the Vertical spacing method.

The first step is to add a new attribute to attrs. xml.

    <declare-styleable name="CascadeLayout_LayoutParams">        <attr name="layout_vertical_spacing" format="dimension" />    </declare-styleable>

The attribute name here is layout_vertical_spacing. Because the prefix of this attribute is layout _ and it is not an inherent attribute of the View, this attribute will be added to the LayoutParams Attribute Table. Read this new attribute in the constructor of the CascadeLayout class.

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);    }  }

How can we use this attribute? So easy!

<Com. manoel. view. CascadeLayout android: layout_width = "fill_parent" android: layout_height = "fill_parent" cascade: Signature = "30dp" cascade: vertical_spacing = "20dp"> <! -- Note: This "playing card" uses the layout_vertical_spacing attribute --> <View android: layout_width = "100dp" android: layout_height = "150dp" cascade: layout_vertical_spacing = "90dp" 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. manoel. view. cascadeLayout>


References

Http://developer.android.com/guide/topics/ui/how-android-draws.html

Http://developer.android.com/reference/android/view/ViewGroup.html

Http://developer.android.com/reference/android/view/ViewGroup.LayoutParams.html


Postscript

Call ~ This is all the content of this article. You should have a perceptual knowledge of how to customize ViewGroup.

If you want to be proficient in custom ViewGroup, there is only one way, that is, more brains, work-on!

Okay, that's all we have to say. If you have any questions, even if you leave a message, you can communicate and make progress together! Upgraded together, and made a great UI!


Strange View layout in Android custom ViewGroup

Hello, the problem is not surprising. The size of TextView is reduced. This explains:

Controls have two important attributes: x, y, and width and height.
Controls are actually rectangular boxes. After these two types of attributes are determined, you can draw the rectangle on the Canvas. After knowing this,
We need to know how android determines X, Y, width, and height.
X, Y: coordinates of the control in the parent Control
Width and height: the width and height of the rectangle,

Android uses onLayout () of View to determine the control's XY in the parent control. It uses onMeasure () to determine the control's width and height. Imagine a control tree (xml layout file) starting from the root node, the root node XY and width and height are determined by the screen size of the window. After the screen size is determined, the child nodes onLayout () and onMeasure () are called in sequence to determine the coordinates and sizes of the child nodes in the parent node. It is the entire process of android LayoutInflater. (This process is basically the same for other window systems)

After understanding this, you should know that the coordinates of the control are related to the parent control. The size of the Child control is also related to the parent control if the child control has attributes such as fill_parent.

Android custom ViewGroup, which can achieve adaptive height

I don't know your specific code. In general, the viewgroup has a scrollview and the viewgroup should be able to set warp_content,

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.