时间:2015-07-08 14:31作者:zhao人气:187
在平常的应用开发中,我们不时的需要进行自定义的ViewGroup的开发以适应我们的需求。下面是一个简单的demo用于说明自定义ViewGroup。
1. 在自定义ViewGroup的过程中我们首先需要学会自定义属性,自定义属性需要在value/attrs.xml,使用<declare-styleable>标签进行声明:
<declare-styleable name="CascadeLayout">
<attr name="horizontal_spacing" format="dimension"/>
<attr name="vertical_spacing" format="dimension"/>
</declare-styleable>
2. 在定义的ViewGroup中,我们需要在构造函数中去读取这些自定义的属性,用于实现我们需要的业务处理。读取自定义属性需要使用TypedArray类。
public CascadeLayout(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray ta = context.obtainStyledAttributes(attrs,
R.styleable.CascadeLayout);
try {
mHorizontalSpacing = ta.getDimensionPixelSize(
R.styleable.CascadeLayout_horizontal_spacing,
this.getResources().getDimensionPixelSize(
R.dimen.cascade_horizontal_spacing));
mVerticalSpacing = ta.getDimensionPixelSize(
R.styleable.CascadeLayout_vertical_spacing,
this.getResources().getDimensionPixelSize(
R.dimen.cascade_vertical_spaccing));
} finally {
ta.recycle();
}
}
3.在自定义ViewGroup的类中,需要实现两个函数分别是onMeasure()和onLayout(),这两个函数分别代表了测量阶段和布局阶段,不熟悉的同学可以参考这边博文http://my.oschina.net/u/565871/blog/132350。
在onMeasure()函数我们需要每个view去测量自己的大小,并且给整个ViewGroup设置大小
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int count = this.getChildCount();
int countWidth = 0;
int countHeight = this.getPaddingTop();
for (int i = 0; i < count; i++) {
View currentView = this.getChildAt(i);
//使每个view去测量自己的大小
this.measureChild(currentView, widthMeasureSpec, heightMeasureSpec);
LayoutParams lp = (LayoutParams) currentView.getLayoutParams();
countWidth = this.getPaddingLeft() + mHorizontalSpacing * i;
lp.x = countWidth;
lp.y = countHeight+lp.childVerticalSpcing;
countWidth += currentView.getMeasuredWidth();
countHeight += mVerticalSpacing+lp.childVerticalSpcing;
}
//算出整个屏幕的宽度和高度
countWidth += this.getPaddingRight();
countHeight += this.getChildAt(count - 1).getMeasuredHeight()
+ this.getPaddingBottom();
//设置ViewGroup的大小
this.setMeasuredDimension(resolveSize(countWidth, widthMeasureSpec),
resolveSize(countHeight, heightMeasureSpec));
}
在onMeasure()方法中我们用到了一个LayoutParams,这个类有两个成员变量x,y,这个类是我们自定义的类用于布局的需要。
public static class LayoutParams extends ViewGroup.LayoutParams {
int x;
int y;
int childVerticalSpcing;
public LayoutParams(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray ta = context.obtainStyledAttributes(attrs,
R.styleable.CascadeLayout_LayoutParams);
try {
childVerticalSpcing = ta
.getDimensionPixelSize(
R.styleable.CascadeLayout_LayoutParams_layout_vertical_spacing,
context.getResources()
.getDimensionPixelSize(
R.dimen.cascade_layout_vertical_spacing));
} finally {
ta.recycle();
}
}
public LayoutParams(int w, int h) {
super(w, h);
}
}
剩下的就是布局阶段,在onLayout()方法中实现,使每个view放在我们需要它在的地方。
protected void onLayout(boolean changed, int left, int top, int right,
int bottom) {
final int count = this.getChildCount();
for (int i = 0; i < count; i++) {
View currentView = this.getChildAt(i);
LayoutParams lp = (LayoutParams) currentView.getLayoutParams();
currentView.layout(lp.x, lp.y,
lp.x + currentView.getMeasuredWidth(),
lp.y + currentView.getMeasuredHeight());
}
}
这样就差不多完成了,全部的自定义的CascadeLayout.java的代码如下:
package com.cascadelayout.view;
import com.cascadelayout.main.R;
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
public class CascadeLayout extends ViewGroup {
int mHorizontalSpacing;
int mVerticalSpacing;
public CascadeLayout(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray ta = context.obtainStyledAttributes(attrs,
R.styleable.CascadeLayout);
try {
mHorizontalSpacing = ta.getDimensionPixelSize(
R.styleable.CascadeLayout_horizontal_spacing,
this.getResources().getDimensionPixelSize(
R.dimen.cascade_horizontal_spacing));
mVerticalSpacing = ta.getDimensionPixelSize(
R.styleable.CascadeLayout_vertical_spacing,
this.getResources().getDimensionPixelSize(
R.dimen.cascade_vertical_spaccing));
} finally {
ta.recycle();
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int count = this.getChildCount();
int countWidth = 0;
int countHeight = this.getPaddingTop();
for (int i = 0; i < count; i++) {
View currentView = this.getChildAt(i);
//使每个view去测量自己的大小
this.measureChild(currentView, widthMeasureSpec, heightMeasureSpec);
LayoutParams lp = (LayoutParams) currentView.getLayoutParams();
countWidth = this.getPaddingLeft() + mHorizontalSpacing * i;
lp.x = countWidth;
lp.y = countHeight+lp.childVerticalSpcing;
countWidth += currentView.getMeasuredWidth();
countHeight += mVerticalSpacing+lp.childVerticalSpcing;
}
//算出整个屏幕的宽度和高度
countWidth += this.getPaddingRight();
countHeight += this.getChildAt(count - 1).getMeasuredHeight()
+ this.getPaddingBottom();
//设置ViewGroup的大小
this.setMeasuredDimension(resolveSize(countWidth, widthMeasureSpec),
resolveSize(countHeight, heightMeasureSpec));
}
@Override
protected void onLayout(boolean changed, int left, int top, int right,
int bottom) {
final int count = this.getChildCount();
for (int i = 0; i < count; i++) {
View currentView = this.getChildAt(i);
LayoutParams lp = (LayoutParams) currentView.getLayoutParams();
currentView.layout(lp.x, lp.y,
lp.x + currentView.getMeasuredWidth(),
lp.y + currentView.getMeasuredHeight());
}
}
public static class LayoutParams extends ViewGroup.LayoutParams {
int x;
int y;
int childVerticalSpcing;
public LayoutParams(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray ta = context.obtainStyledAttributes(attrs,
R.styleable.CascadeLayout_LayoutParams);
try {
childVerticalSpcing = ta
.getDimensionPixelSize(
R.styleable.CascadeLayout_LayoutParams_layout_vertical_spacing,
context.getResources()
.getDimensionPixelSize(
R.dimen.cascade_layout_vertical_spacing));
} finally {
ta.recycle();
}
}
public LayoutParams(int w, int h) {
super(w, h);
}
}
@Override
protected android.view.ViewGroup.LayoutParams generateDefaultLayoutParams() {
return new LayoutParams(LayoutParams.WRAP_CONTENT,
LayoutParams.WRAP_CONTENT);
}
@Override
protected android.view.ViewGroup.LayoutParams generateLayoutParams(
android.view.ViewGroup.LayoutParams p) {
return new LayoutParams(LayoutParams.WRAP_CONTENT,
LayoutParams.WRAP_CONTENT);
}
@Override
public android.view.ViewGroup.LayoutParams generateLayoutParams(
AttributeSet attrs) {
return new LayoutParams(this.getContext(), attrs);
}
}
在自定义的value/attrs.xml中的定义如下:
<?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>
<declare-styleable name="CascadeLayout_LayoutParams">
<attr name="layout_vertical_spacing" format="dimension"/>
</declare-styleable>
</resources>
布局文件如下:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:cascade="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity" >
<com.cascadelayout.view.CascadeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/holo_orange_light"
cascade:horizontal_spacing="40dp"
cascade:vertical_spacing="40dp">
<TextView
android:layout_width="90dp"
android:layout_height="200dp"
android:text="aaa"
android:gravity="center"
android:background="#00FF00"/>
<TextView
android:layout_width="90dp"
android:layout_height="200dp"
android:text="bbb"
android:gravity="center"
android:background="#48D1CC" />
<TextView
android:layout_width="90dp"
android:layout_height="200dp"
android:text="ccc"
android:gravity="center"
cascade:layout_vertical_spacing="60dip"
android:background="#CDBE70" />
</com.cascadelayout.view.CascadeLayout>
</RelativeLayout>
最后实现的效果如下:
网友评论