本文来自 慕课网视频学习 以及 github上源码总结
hyman 的视频 链接:http://www.imooc.com/learn/237
tagGroup的github链接: https://github.com/2dxgujun/AndroidTagGroup
上图:
上原理:
自定义控件 流式布局 (关键字搜索 、热门标签)
自定义ViewGroup
1、 onMeasure :测量子View 的宽和高 ,设置自己的宽和高
2、 onLayout : 设置子View 的位置
onMeasure : 根据子View 的布局文件 ,为子View 设置 测量模式 和 测量值
测量 = 测量模式 + 测量值
测量模式: 3 种 (MeasusreSpec)
EXACTLY : 100dp ,match_parent;
AT_MOS : wrap_content
UNSEPCIFIED : 子View 想多大就多大
LayoutParams
ViewGroup -- LayoutParams
子View.getLayoutParams(); -----> 父View 的LayoutParams ;
override the method
generateLayoutParams();
我们要写的流式布局 使用系统的MarginLayoutparams 即可
特别注意: 核心逻辑 就是 分别在 onMeasure 和 onLayout 进行view填充时的换行和不换行的逻辑 宽和高的计算 处理 以及最后一行的处理
其中包括 父view的最终width 和 height的 获取 子view的left 和top的计算
上源码:
package diyview;
import java.util.ArrayList;
import java.util.List;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
public class FlowLayout extends ViewGroup {
public FlowLayout(Context context) {
super(context,null);
}
public FlowLayout(Context context, AttributeSet attrs) {
super(context, attrs,0);
}
public FlowLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int sizeWidth = MeasureSpec.getSize(widthMeasureSpec);
int modeWidth = MeasureSpec.getMode(widthMeasureSpec);
// int sizeHeight = MeasureSpec.getSize(heightMeasureSpec);
int modeHeight = MeasureSpec.getMode(heightMeasureSpec);
int width = 0;
int height = 0;
int lineWidth = 0;
int lineHeight = 0;
int cCount = getChildCount();
if (cCount>0) {
for(int i = 0; i<cCount;i++){
View child = getChildAt(i);
measureChild(child, widthMeasureSpec, heightMeasureSpec);
MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
int childWidth = child.getMeasuredWidth()+lp.leftMargin+lp.rightMargin;
int childHeight = child.getMeasuredHeight()+lp.topMargin+lp.bottomMargin;
if (lineWidth+childWidth>sizeWidth-getPaddingLeft()-getPaddingRight()) {
//换行
width = Math.max(width, lineWidth);
lineWidth = childWidth;
height+=lineHeight;
lineHeight = childHeight;
}else {
lineWidth+= childWidth;
lineHeight = Math.max(lineHeight,childHeight);
}
if (i==cCount-1) {
//最后一行
width = Math.max(width, lineWidth);
height+=lineHeight;
}
}
setMeasuredDimension(modeWidth==MeasureSpec.EXACTLY?widthMeasureSpec:width+getPaddingLeft()+getPaddingRight(), modeHeight==MeasureSpec.EXACTLY?heightMeasureSpec:height+getPaddingBottom()+getPaddingTop());
}
}
//所有子View的集合
private List<List<View>> mAllViews = new ArrayList<List<View>>();
//每一行的行高
private List<Integer> mLineHeight = new ArrayList<Integer>();
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
mAllViews.clear();
mLineHeight.clear();
//自定义View的宽
int width =getWidth();
int lineWidth = 0;
int lineHeight = 0;
List<View> lineViews = new ArrayList<View>();
int cCount = getChildCount();
for (int i = 0; i < cCount; i++) {
View child = getChildAt(i);
MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
if (lineWidth+child.getMeasuredWidth()+lp.leftMargin+lp.rightMargin>width-getPaddingLeft()-getPaddingRight()) {
mAllViews.add(lineViews);
mLineHeight.add(lineHeight);
//重置 下一行的行高 行宽
lineWidth = 0;
lineHeight =child.getMeasuredHeight()+lp.topMargin+lp.bottomMargin;
lineViews = new ArrayList<View>();
}
lineViews.add(child);
lineWidth += child.getMeasuredWidth()+lp.leftMargin+lp.rightMargin;
lineHeight = Math.max(lineHeight,child.getMeasuredHeight()+lp.topMargin+lp.bottomMargin);
}
//for end
mAllViews.add(lineViews);
mLineHeight.add(lineHeight);
//子View的位置
int left = getPaddingLeft();
int top = getPaddingTop();
int lineSum = mAllViews.size();
for (int i = 0; i < lineSum; i++) {
lineViews= mAllViews.get(i);
lineHeight = mLineHeight.get(i);
for (int j = 0; j < lineViews.size(); j++) {
View child = lineViews.get(j);
if (child.getVisibility() == View.GONE) {
continue;
}
MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
int lc = left+lp.leftMargin;
int tc = top+lp.topMargin;
int rc =lc+child.getMeasuredWidth();
int bc = tc +child.getMeasuredHeight();
//为子View 进行布局
child.layout(lc, tc, rc, bc);
left+=child.getMeasuredWidth()+lp.leftMargin+lp.rightMargin;
}
left = getPaddingLeft();
top +=lineHeight;
}
}
/**
* 当前对应的ViewGroup对应的LayoutParams
*/
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new MarginLayoutParams(getContext(),attrs);
}
}
public class ThreeActivity extends Activity {
FlowLayout layout;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.flow_xml);
layout = (FlowLayout) findViewById(R.id.flowlayout);
String[] strs = new String[] { "1234", "劉備", "張飛", "關羽", "藝術", "国家",
"我是小明小红小森", "东方红", "红星照我去战斗", "红星闪闪", "这一刻", "西湖的水", "红颜旧" };
for (int i = 0; i < strs.length; i++) {
Button b = new Button(this);
MarginLayoutParams lp = new MarginLayoutParams(MarginLayoutParams.WRAP_CONTENT, MarginLayoutParams.WRAP_CONTENT);
b.setText(strs[i]);
b.setLayoutParams(lp);
layout.addView(b);
}
}
}
再看一下 tagGroup的源码:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
final int heightSize = MeasureSpec.getSize(heightMeasureSpec);
measureChildren(widthMeasureSpec, heightMeasureSpec);
int width = 0;
int height = 0;
int row = 0; // The row counter.
int rowWidth = 0; // Calc the current row width.
int rowMaxHeight = 0; // Calc the max tag height, in current row.
final int count = getChildCount();
for (int i = 0; i < count; i++)
{
final View child = getChildAt(i);
final int childWidth = child.getMeasuredWidth();
final int childHeight = child.getMeasuredHeight();
if (child.getVisibility() != GONE)
{
rowWidth += childWidth;
if (rowWidth > widthSize)
{ // Next line.
rowWidth = childWidth; // The next row width.
height += rowMaxHeight + verticalSpacing;
rowMaxHeight = childHeight; // The next row max height.
row++;
} else
{ // This line.
rowMaxHeight = Math.max(rowMaxHeight, childHeight);
}
rowWidth += horizontalSpacing;
}
}
// Account for the last row height.
height += rowMaxHeight;
// Account for the padding too.
height += getPaddingTop() + getPaddingBottom();
// If the tags grouped in one row, set the width to wrap the tags.
if (row == 0)
{
width = rowWidth;
width += getPaddingLeft() + getPaddingRight();
} else
{// If the tags grouped exceed one line, set the width to match the
// parent.
width = widthSize;
}
setMeasuredDimension(widthMode == MeasureSpec.EXACTLY ? widthSize
: width, heightMode == MeasureSpec.EXACTLY ? heightSize
: height);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b)
{
final int parentLeft = getPaddingLeft();
final int parentRight = r - l - getPaddingRight();
final int parentTop = getPaddingTop();
final int parentBottom = b - t - getPaddingBottom();
int childLeft = parentLeft;
int childTop = parentTop;
int rowMaxHeight = 0;
final int count = getChildCount();
for (int i = 0; i < count; i++)
{
final View child = getChildAt(i);
final int width = child.getMeasuredWidth();
final int height = child.getMeasuredHeight();
if (child.getVisibility() != GONE)
{
if (childLeft + width > parentRight)
{ // Next line
childLeft = parentLeft;
childTop += rowMaxHeight + verticalSpacing;
rowMaxHeight = height;
} else
{
rowMaxHeight = Math.max(rowMaxHeight, height);
}
child.layout(childLeft, childTop, childLeft + width, childTop
+ height);
childLeft += width + horizontalSpacing;
}
}
}
@Override
public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs)
{
return new TagGroup.LayoutParams(getContext(), attrs);
}
/**
* Per-child layout information for layouts.
*/
public static class LayoutParams extends ViewGroup.LayoutParams
{
public LayoutParams(Context c, AttributeSet attrs)
{
super(c, attrs);
}
public LayoutParams(int width, int height)
{
super(width, height);
}
}