package com.bosi.chineseclass.views;
import java.util.ArrayList;
import java.util.List;
import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
/**
* 自动换行的viewgroup
*
* @author zhujohnle
* @modify + reason
*/
public class AutoChangeLineViewGroup extends ViewGroup {
public static final int DEFAULT_SPACING = 20;
/** 横向间隔 */
private int mHorizontalSpacing = DEFAULT_SPACING;
/** 纵向间隔 */
private int mVerticalSpacing = 10;
/** 是否需要布局,只用于第一次 */
boolean mNeedLayout = true;
/** 当前行已用的宽度,由子View宽度加上横向间隔 */
private int mUsedWidth = 0;
/** 代表每一行的集合 */
private final List<Line> mLines = new ArrayList<Line>();
private Line mLine = null;
/** 最大的行数 */
private int mMaxLinesCount = Integer.MAX_VALUE;
public AutoChangeLineViewGroup(Context context) {
super(context);
}
public void setHorizontalSpacing(int spacing) {
if (mHorizontalSpacing != spacing) {
mHorizontalSpacing = spacing;
requestLayoutInner();
}
}
public void setVerticalSpacing(int spacing) {
if (mVerticalSpacing != spacing) {
mVerticalSpacing = spacing;
requestLayoutInner();
}
}
public void setMaxLines(int count) {
if (mMaxLinesCount != count) {
mMaxLinesCount = count;
requestLayoutInner();
}
}
/**
* 在哪个Activity使用就将context转成哪个Activity
*/
private void requestLayoutInner() {requestLayout();}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int sizeWidth = MeasureSpec.getSize(widthMeasureSpec)
- getPaddingRight() - getPaddingLeft();
int sizeHeight = MeasureSpec.getSize(heightMeasureSpec)
- getPaddingTop() - getPaddingBottom();
int modeWidth = MeasureSpec.getMode(widthMeasureSpec);
int modeHeight = MeasureSpec.getMode(heightMeasureSpec);
restoreLine();// 还原数据,以便重新记录
final int count = getChildCount();
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
//hide view is not need to show
if (child.getVisibility() == GONE) {
continue;
}
int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(sizeWidth,
modeWidth == MeasureSpec.EXACTLY ?MeasureSpec.AT_MOST
: modeWidth);
int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
sizeHeight,
modeHeight == MeasureSpec.EXACTLY ? MeasureSpec.AT_MOST
: modeHeight);
// 测量child
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
if (mLine == null) {
mLine = new Line();
}
//to measure the child width
int childWidth = child.getMeasuredWidth();
mUsedWidth += childWidth;// 增加使用的宽度
if (mUsedWidth <= sizeWidth) {// 使用宽度小于总宽度,该child属于这一行。
mLine.addView(child);// 添加child
mUsedWidth += mHorizontalSpacing;// 加上间隔
if (mUsedWidth >= sizeWidth) {// 加上间隔后如果大于等于总宽度,需要换行
if (!newLine()) {
break;
}
}
} else {// 使用宽度大于总宽度。需要换行
if (mLine.getViewCount() == 0) {// 如果这行一个child都没有,即使占用长度超过了总长度,也要加上去,保证每行都有至少有一个child
mLine.addView(child);// 添加child
if (!newLine()) {// 换行
break;
}
} else {// 如果该行有数据了,就直接换行
if (!newLine()) {// 换行
break;
}
// 在新的一行,不管是否超过长度,先加上去,因为这一行一个child都没有,所以必须满足每行至少有一个child
mLine.addView(child);
mUsedWidth += childWidth + mHorizontalSpacing;
}
}
}
if (mLine != null && mLine.getViewCount() > 0
&& !mLines.contains(mLine)) {
// 由于前面采用判断长度是否超过最大宽度来决定是否换行,则最后一行可能因为还没达到最大宽度,所以需要验证后加入集合中
mLines.add(mLine);
}
int totalWidth = MeasureSpec.getSize(widthMeasureSpec);
int totalHeight = 0;
final int linesCount = mLines.size();
for (int i = 0; i < linesCount; i++) {// 加上所有行的高度
totalHeight += mLines.get(i).mHeight;
}
totalHeight += mVerticalSpacing * (linesCount - 1);// 加上所有间隔的高度
totalHeight += getPaddingTop() + getPaddingBottom();// 加上padding
// 设置布局的宽高,宽度直接采用父view传递过来的最大宽度,而不用考虑子view是否填满宽度,因为该布局的特性就是填满一行后,再换行
// 高度根据设置的模式来决定采用所有子View的高度之和还是采用父view传递过来的高度
setMeasuredDimension(totalWidth,
resolveSize(totalHeight, heightMeasureSpec));
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if (!mNeedLayout || changed) {// 没有发生改变就不重新布局
mNeedLayout = false;
int left = getPaddingLeft();// 获取最初的左上点
int top = getPaddingTop();
final int linesCount = mLines.size();
for (int i = 0; i < linesCount; i++) {
final Line oneLine = mLines.get(i);
oneLine.layoutView(left, top);// 布局每一行
top += oneLine.mHeight + mVerticalSpacing;// 为下一行的top赋值
}
}
}
/** 还原所有数据 */
private void restoreLine() {
mLines.clear();
mLine = new Line();
mUsedWidth = 0;
}
/** 新增加一行 */
private boolean newLine() {
mLines.add(mLine);
if (mLines.size() < mMaxLinesCount||(mChindSizeEnoughSize!=-1&&getChildCount()==mChindSizeEnoughSize)) {
mLine = new Line();
mUsedWidth = 0;
return true;
}
return false;
}
int mChindSizeEnoughSize = -1;
public void setChildsEnoughSize(int mChindSizeEnoughSize){
this.mChindSizeEnoughSize = mChindSizeEnoughSize;
}
// ==========================================================================
// Inner/Nested Classes
// ==========================================================================
/**
* 代表着一行,封装了一行所占高度,该行子View的集合,以及所有View的宽度总和
*/
class Line {
int mWidth = 0;// 该行中所有的子View累加的宽度
int mHeight = 0;// 该行中所有的子View中高度的那个子View的高度
List<View> views = new ArrayList<View>();
public void addView(View view) {// 往该行中添加一个
views.add(view);
mWidth += view.getMeasuredWidth();
int childHeight = view.getMeasuredHeight();
mHeight = mHeight < childHeight ? childHeight : mHeight;// 高度等于一行中最高的View
}
public int getViewCount() {
return views.size();
}
public void layoutView(int l, int t) {// 布局
int left = l;
int top = t;
int count = getViewCount();
// 总宽度
int layoutWidth = getMeasuredWidth() - getPaddingLeft()
- getPaddingRight();
// 剩余的宽度,是除了View和间隙的剩余空间
int surplusWidth = layoutWidth - mWidth - mHorizontalSpacing
* (count - 1);
if (surplusWidth >= 0) {// 剩余空间
// 采用float类型数据计算后四舍五入能减少int类型计算带来的误差
int splitSpacing = (int) (surplusWidth / count + 0.5);
for (int i = 0; i < count; i++) {
final View view = views.get(i);
int childWidth = view.getMeasuredWidth();
int childHeight = view.getMeasuredHeight();
// 计算出每个View的顶点,是由最高的View和该View高度的差值除以2
int topOffset = (int) ((mHeight - childHeight) / 2.0 + 0.5);
if (topOffset < 0) {
topOffset = 0;
}
// 把剩余空间平均到每个View上
// childWidth = childWidth + splitSpacing;
view.getLayoutParams().width = childWidth;
if (splitSpacing > 0) {// View的长度改变了,需要重新measure
int widthMeasureSpec = MeasureSpec.makeMeasureSpec(
childWidth, MeasureSpec.EXACTLY);
int heightMeasureSpec = MeasureSpec.makeMeasureSpec(
childHeight, MeasureSpec.EXACTLY);
view.measure(widthMeasureSpec, heightMeasureSpec);
}
// 布局View
view.layout(left, top + topOffset, left + childWidth, top
+ topOffset + childHeight);
left += childWidth + mHorizontalSpacing; // 为下一个View的left赋值
}
} else {
if (count == 1) {
View view = views.get(0);
view.layout(left, top, left + view.getMeasuredWidth(), top
+ view.getMeasuredHeight());
} else {
// 走到这里来,应该是代码出问题了,目前按照逻辑来看,是不可能走到这一步
}
}
}
}
}