在我们的开发过程中,常常会遇到这样的场景:
我们展示一种物品或者为某一事物添加一些标签。比如说,我们买一件衣服,可以有以下几种标签:杰克琼斯,男士,运动等等。
但我们这时候可能并不知道标签的数量和每个标签的文字,所以,我们在开发过程中,需要实现下面的功能:
我们从服务器端获取标签的信息,然后将其动态的添加到布局中,并且我们能够得到我们选择容器的信息,并将选中的标签重新返回至服务器。
因此,我们必须计算出每个标签(Button)的长度,并且将其与它的容器做比较,如果容器剩余的长度并不足以容纳一个标签的时候,那么就会另起一行,添加标签,就这样周而复始,直到所有的标签添加到容器中。
在这之前,我们先来复习下view的绘制过程:
View 的绘制三步:
1,measure 控件的大小
调用onMeasure方法——>调用setMeasuredDimension()方法
// 确定控件的大小
setMeasuredDimension(width, height);
2,layout 控件的位置
调用onLayout方法
/**
* 控制viewgroup 里面的孩子 显示的位置 左上角的坐标 (l,t) 右下角的坐标 (r,b)
*/
protected void onLayout(boolean changed, int l, int t, int r, int b)
3,draw 控件长的样子
onMeasure()---> onLayout()-->onDraw();
view对象是在onstart方法之后的某一个时刻显示出来。
下面就是view绘制过程的流程图:
当然view的绘制过程很复杂,很多细节需要注意
今天我们要实现的效果是这样的,如图:
对于这样一个功能,我们的思路如下:
对于标签容器TagLayout继承ViewGroup:
维护行的集合
当前行
行的水平间距
行间的垂直间距
onMeasure方法:
1,获取父控件的宽度
2,获取行的宽度
3,获取子孩子的个数
4,测量每个子孩子
5,构造行(确定行的宽度,水平间隙),把孩子添加到行中,把行添加行容器中
6,获取自己的高度,累加行高+垂直间距
7,设置自己的宽高
onLayout方法:
1,计算每一行的marginTop和marginLeft
2, 让每一行自己去摆放自己孩子的位置
对于每一行Line:
维护的控件集合
行的最大宽度
行的使用了的宽度
行的高度
行控件的水平间距
方法1:添加控件:
计算控件的宽度,高度
计算行的使用了的宽度,和行的高度
把控件添加到控件集合中
方法2:判断控件是否可以添加
获取控件的宽度
如果剩余宽度不足则不能添加
方法3:摆放每个孩子
计算每个孩子的left,top,right,bottom的值,调用layout方法
自定义控件TayLayout继承ViewGroup,代码如下:
package com.hdc.flowlayoutdemo;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
/**
* Created by wk on 2016/6/14.
*/
public class TagLayout extends ViewGroup {
private List<Line> mLines = new LinkedList<Line>(); // 用来记录布局中有多少个行
private Line mCurrentLine; //当前行
private int horizontalSpace = 10; //控件之间的间隙
private int verticalSpace = 10; //行之间的间隙
public TagLayout(Context context) {
this(context,null);
}
public TagLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//super.onMeasure(widthMeasureSpec, heightMeasureSpec);
// 清空记录,,,不清空就会掉坑了
mLines.clear();
mCurrentLine = null;
//1,获取自身的宽度
int width = MeasureSpec.getSize(widthMeasureSpec);
//2,获取行的宽度
int maxWidth = width - getPaddingLeft() - getPaddingRight();
//3,获取孩子
for(int i = 0;i<getChildCount();i++){
View child = getChildAt(i);
if (child.getVisibility() == View.GONE)
{
continue;
}
//测量孩子的宽高
measureChild(child,widthMeasureSpec,heightMeasureSpec);
//将孩子添加到行中,把行添加到集合中
if(mCurrentLine == null){
mCurrentLine = new Line(maxWidth,horizontalSpace);
mCurrentLine.addView(child);
mLines.add(mCurrentLine);
}else{
if(mCurrentLine.canAdd(child)){
// 可以加入
mCurrentLine.addView(child);
}else{
// 换行
mCurrentLine = new Line(maxWidth, horizontalSpace);
// 添加到list
mLines.add(mCurrentLine);
// 添加孩子
mCurrentLine.addView(child);
}
}
}
//获取自己的高度
int measuredHeight = getPaddingTop() + getPaddingBottom();
for (int i = 0 ;i<mLines.size();i++){
measuredHeight += mLines.get(i).height;
}
measuredHeight = measuredHeight + (mLines.size()-1)*verticalSpace;
// 设置自己的宽度和高度
setMeasuredDimension(width,measuredHeight);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int marginTop = getPaddingTop();
int marginLeft = getPaddingLeft();
// 让line自己去摆放
for(int i = 0;i<mLines.size();i++){
Line line = mLines.get(i);
line.layout(marginLeft,marginTop);
marginTop += verticalSpace + line.height;
}
}
/**
* 用来记录每行控件的摆放
*/
private class Line{
private List<View> mViews = new ArrayList<View>();
private int maxWidth ; //行的宽度
private int space; //行的水平间隙
private int usedWidth ; //行使用了的宽度
public int height; //行高
public Line(int maxWidth, int horizontalSpace) {
this.maxWidth = maxWidth;
this.space = horizontalSpace;
}
/**
* 添加子控件
* @param child
*/
public void addView(View child) {
int childWidth = child.getMeasuredWidth();
int childHeight = child.getMeasuredHeight();
if(mViews.size()==0){
// 如果没有控件的情况下
if(childWidth > maxWidth){
usedWidth = maxWidth;
height = childHeight;
}else{
usedWidth = childWidth;
height = childHeight;
}
}else{
//有控件
usedWidth = usedWidth + space+ childWidth;
height = (childHeight>height)?childHeight:height;
}
mViews.add(child);
}
/**
* 判断是否可以添加子控件
* @param child
* @return
*/
public boolean canAdd(View child) {
int width = child.getMeasuredWidth();
// line中没有view时
if (mViews.size() == 0)
{
// 只要没有,就可以加
return true;
}
if(usedWidth + width + space > maxWidth){
return false;
}else{
return true;
}
}
/**
* 摆放行
* @param marginLeft
* @param marginTop
*/
public void layout(int marginLeft, int marginTop) {
int extraWidth = maxWidth - usedWidth;
int avgWidth = (int)(extraWidth*1f/mViews.size()+0.5f);
//计算控件的上下左右的位置
for(int i=0;i<mViews.size();i++){
View view = mViews.get(i);
int viewWidth = view.getMeasuredWidth();
int viewHeight = view.getMeasuredHeight();
if(avgWidth > 0){
// 重新的去期望孩子的宽高
int specWidth = MeasureSpec.makeMeasureSpec(viewWidth + avgWidth,MeasureSpec.EXACTLY);
int specHeight = MeasureSpec.makeMeasureSpec(viewHeight,MeasureSpec.EXACTLY);
view.measure(specWidth,specHeight);
// 重新获取宽度和高度
viewWidth = view.getMeasuredWidth();
viewHeight = view.getMeasuredHeight();
}
int extraTop =(int) ((height - viewHeight)/2f + 0.5);
int left = marginLeft;
int top = marginTop + extraTop;
int right = left + viewWidth;
int bottom = top + viewHeight;
//摆放每一个孩子的位置
view.layout(left,top,right,bottom);
marginLeft += viewWidth + space;
}
}
}
}
主界面布局文件,activity_main.xml
<ScrollView 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"
tools:context=".MainActivity" >
<com.hdc.flowlayoutdemo.TagLayout
android:id="@+id/flowlayout"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</ScrollView>
主界面调用很简单,MainActivity.java
package com.hdc.flowlayoutdemo;
import java.util.Random;
import android.os.Bundle;
import android.app.Activity;
import android.graphics.Color;
import android.view.Gravity;
import android.view.Menu;
import android.widget.TextView;
public class MainActivity extends Activity
{
private String[] mDatas = new String[] { "QQ", "视频", "放开那三国", "电子书", "酒店",
"单机", "小说", "斗地主", "优酷", "网游", "WIFI万能钥匙", "播放器", "捕鱼达人2", "机票",
"游戏", "熊出没之熊大快跑", "美图秀秀", "浏览器", "单机游戏", "我的世界", "电影电视", "QQ空间",
"旅游", "免费游戏", "2048", "刀塔传奇", "壁纸", "节奏大师", "锁屏", "装机必备", "天天动听",
"备份", "网盘", "海淘网", "大众点评", "爱奇艺视频", "腾讯手机管家", "百度地图", "猎豹清理大师",
"谷歌地图", "hao123上网导航", "京东", "youni有你", "万年历-农历黄历", "支付宝钱包" };
private TagLayout mLayout;
private int[] colors = {Color.GRAY,Color.LTGRAY,Color.RED,Color.YELLOW,Color.BLUE};
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mLayout = (TagLayout) findViewById(R.id.flowlayout);
mLayout.setPadding(10, 10, 10, 10);
// 动态加载数据
Random rdm = new Random();
for (int i = 0; i < mDatas.length; i++)
{
TextView view = new TextView(this);
view.setText(mDatas[i]);
view.setBackgroundColor(colors[rdm.nextInt(5)]);
view.setTextColor(Color.WHITE);
view.setPadding(5, 5, 5, 5);
view.setGravity(Gravity.CENTER);
view.setTextSize(rdm.nextInt(10) + 16);
// view.setTextSize(15);
mLayout.addView(view);
}
}
}
到这里已经完成,如需源码,点击 这里,欢迎留言。。。