暴风影音的搜索页面有一个效果,每一行的组件如果最后一个显示即将超出屏幕宽度,则自动换行。效果图:
本demo的效果图:
实现该效果不能使用已有的组件,必须自定义一个布局。
重点介绍:自定义布局可以继承ViewGroup,要想实现该效果,我们的自定义布局里面必须要继承父类中的onMeasurt()和onLayout()两个方法。
onMeasure()方法的作用是设置布局的显示范围,超出该范围的部分将被遮挡.核心代码是:setMeasuredDimension(int width,int height).
onLayout()方法的作用是在父容器内布局子组件,核心功能是根据计算出来的子组件的四个”坐标”将其放在父容器的指定位置,核心代码为:childview.layout(int left,int top,int right,int bottom)
下面贴出Demo源码,算法解释已经在注释中了。
自定义布局:
HorizantalFallWaterLayout.java
package com.example.baofendeflectdemo.view;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
/**
* @Description:仿暴风影音搜索页面效果
* @company XX(北京)有限公司
* @author Joe
* @date 2013-12-18 下午4:38:01
*/
public class HorizantalFallWaterLayout extends ViewGroup {
private int maxWidth;// 可使用的最大宽度
public HorizantalFallWaterLayout(Context context) {
super(context);
}
public HorizantalFallWaterLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public HorizantalFallWaterLayout(Context context, AttributeSet attrs,
int defStyle) {
super(context, attrs, defStyle);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
maxWidth = MeasureSpec.getSize(widthMeasureSpec);
int containorHeight = 0;// 容器的高度,也就是本布局的高度。初始化赋值为0.
int count = getChildCount();// 获取该布局内子组件的个数
for (int i = 0; i < count; i++) {
View view = getChildAt(i);
/**
* measure(int widthMeasureSpec,int
* heightMeasureSpec)用于设置子组件显示模式.有三个值:<br/>
* MeasureSpec.AT_MOST 该组件可以设置自己的大小,但是最大不能超过其父组件的限定<br/>
* MeasureSpec.EXACTLY 无论该组件设置大小是多少,都只能按照父组件限制的大小来显示<br/>
* MeasureSpec.UNSPECIFIED 该组件不受父组件的限制,可以设置任意大小
*/
view.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
// 把每个子组件的高度相加就是该组件要显示的高度。
containorHeight += view.getMeasuredHeight();
}
setMeasuredDimension(maxWidth, containorHeight);// onMeasure方法的关键代码,该句设置父容器的大小。
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int childCount = getChildCount();// 获取子组件数
int row = 1;// 子组件行数,初始化赋值为1
int left = 0;// 子组件的左边“坐标”
int right = 0;// 子组件的右边“坐标”
int top = 0;// 子组件的顶部“坐标”
int bottom = 0;// 子组件的底部“坐标”
int p = getPaddingLeft();// 在父组件中设置的padding属性的值,该值显然也会影响到子组件在屏幕的显示位置
for (int i = 0; i < childCount; i++) {
View view = getChildAt(i);
int width = view.getMeasuredWidth();// 测量子组件的宽
int height = view.getMeasuredHeight();// 测量子组件的高
left = p + right;// ---------------------------------------------------备注1
right = left + width;// -----------------------------------------------备注2
top = p * row + height * (row - 1);// ---------------------------------备注3
bottom = top + height;// ----------------------------------------------备注4
if (right > maxWidth) {
row++;
left = 0;//每次换行后要将子组件左边“坐标”与右边“坐标”重新初始化
right = 0;
left = p + right;
right = left + width;
top = p * row + height * (row - 1);
bottom = top + height;
}
view.layout(left, top, right, bottom);// 最后按照计算出来的“坐标”将子组件放在父容器内
}
}
}
MainActivity.java什么都没有做
package com.example.baofendeflectdemo;
import android.os.Bundle;
import android.app.Activity;
public class MainActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
,下面使用自定义布局作为MainActivity的主布局,显示几个TextView。
activity_main.xml
<com.example.baofendeflectdemo.view.HorizantalFallWaterLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="5sp" >
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#DBE8F0"
android:text="生化危机:重返基地" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#F8E199"
android:text="失恋33天" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#FAD0D1"
android:text="宫" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#D2E39C"
android:text="南极大冒险" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#DBE8F0"
android:text="战争之王" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#F8E199"
android:text="最贫穷的哈佛女孩" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#FAD0D1"
android:text="蒙古王" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#D2E39C"
android:text="黑客帝国:重装出击" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#DBE8F0"
android:text="拯救大兵瑞恩" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#F8E199"
android:text="希特勒:帝国的毁灭" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#FAD0D1"
android:text="十二金刚" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#D2E39C"
android:text="我们俩" />
</com.example.baofendeflectdemo.view.HorizantalFallWaterLayout>
备注1:这里假设容器内的每个组件前面都有一个兄弟组件,则该组件的左边“坐标”其实就是padding值与其上一个兄弟组件的右边“坐标”之和。
备注2:前面已经计算出该组件的左边坐标,则其右边坐标肯定是其左边坐标与其宽度之和。
备注3:子组件的顶部“坐标”也有一定规律:第N行的组件,其顶部坐标值是N倍的padding值与(N-1)倍的自身高的和。这个效果中所有子组件的高度是相同的,所以可以这样计算
备注4:每个组件底部“坐标”是其顶部“坐标”的值与其高度之和。