您的位置:首页 > 其它

自定义ViewGroup实现流式布局

2016-12-10 21:22 537 查看
要实现的就是这样的效果



自定义ViewGroup

重写OnMeasure方法

指定每个孩子测量规则

实例化内部类【行】,进行逻辑处理(行宽够用,则加入孩子;行宽不够用,则换行)

重写Onlayout方法

for循环遍历行集合(拿到每行,调用行类的layout方法分配行内孩子位置,纵坐标t不断加上行宽和间隔)

FlowLayout.java

public class FlowLayout extends ViewGroup {

private int horizontolSpacing=UIutil.dip2px(13);//横向控件间隔
private int verticalSpacing=UIutil.dip2px(13);//纵向行间隔

private Line currentline;// 当前的行
private int useWidth=0;// 当前行使用的宽度,即当前行拥有的控件孩子所占用的宽度
private List<Line> mLines=new ArrayList<>();//保存行的行集合
private int width;//FlowLayout宽度

public FlowLayout(Context context) {
super(context);
}

public FlowLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}

public FlowLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}

// 测量 当前控件Flowlayout
// 父类是有义务测量每个孩子的
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

/**初始化【行】对象相关的变量*/
mLines.clear();//清空行集合
currentline=null;
useWidth=0;

/**重新指定每个孩子测量规则*/
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
width = widthSize-getPaddingLeft()-getPaddingRight();
int height = heightSize-getPaddingBottom()-getPaddingTop(); // 获取到父控件FlowLayout宽和高
int childeWidthMode;
int childeHeightMode;
//  为了测量每个孩子 需要指定每个孩子测量规则
childeWidthMode = widthMode == MeasureSpec.EXACTLY ? MeasureSpec.AT_MOST : widthMode;
childeHeightMode = heightMode == MeasureSpec.EXACTLY ? MeasureSpec.AT_MOST : heightMode;
int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width,childeWidthMode);
int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height,childeHeightMode);

/**开始【行】操作*/
currentline=new Line();// 创建了第一行
/**遍历FlowLayout中的孩子,分配每行拥有的控件*/
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);//拿到FlowLayout的孩子
System.out.println("孩子的数量:"+getChildCount());
child.measure(childWidthMeasureSpec,childHeightMeasureSpec);//测量每个孩子

int measuredWidth = child.getMeasuredWidth();//孩子控件宽度即所占FlowLayout的宽度
useWidth += measuredWidth;// 让当前行加上使用的长度
if(useWidth <= width){//控件孩子们占用的宽度小于当前行宽(行宽=FlowLayout的宽)
currentline.addChild(child);//这时候证明当前的孩子是可以放进当前的行里,放进去
useWidth += horizontolSpacing;//加上孩子之间的间隔
if(useWidth>width){//使用宽度大于行宽
//换行
newLine();
}
}else{

if(currentline.getChildCount() < 1){
currentline.addChild(child);// 保证当前行里面最少有一个孩子
}
newLine();//换行
}

}
if(!mLines.contains(currentline)){
mLines.add(currentline);// 添加当前行
}
int totalHeight = 0;//总高度,使用的高度
for(Line line : mLines){
totalHeight += line.getHeight();//等于所有控件的高度和
}
totalHeight += verticalSpacing*(mLines.size() - 1) + getPaddingTop() + getPaddingBottom();//加上所有行与行之间的间隔,paddingtop,paddingbottom。
System.out.println(totalHeight);
/**设置父视图FlowLayout的尺寸*/     setMeasuredDimension(width+getPaddingLeft()+getPaddingRight(),resolveSize(totalHeight, heightMeasureSpec));//resolveSize方法是通过比较当前的高度判定规则与父视图的规则以及totalHeight与父视图尺寸的大小,选取适当高度尺寸;看源码很容易理解

}
/**换行*/
private void newLine() {
mLines.add(currentline);// 记录之前的行
currentline=new Line(); // 创建新的一行
useWidth=0;//新的一行重置使用宽度
}

// 分配每个孩子的位置
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
l+=getPaddingLeft();
t+=getPaddingTop();//改变起点
for (int i = 0; i < mLines.size(); i++) {
Line line = mLines.get(i);
line.layout(l,t);//交给每一行去分配
t+= line.getHeight() + verticalSpacing;//改变高度t+=行高、行与行间隔之和
}
}

/**内部类行*/
private class Line {
int height = 0; //当前行的高度
int lineWidth = 0;//行宽
private List<View> children = new ArrayList<View>();//控件集合

/**
* 添加一个孩子
*
* @param child
*/
public void addChild(View child) {
children.add(child);
if (child.getMeasuredHeight() > height) {
height = child.getMeasuredHeight();//行高等于当前行最大孩子的高
}
lineWidth += child.getMeasuredWidth();
}
/**当前行高*/
public int getHeight() {
return height;
}

/**
* 返回孩子的数量
*
* @return
*/
public int getChildCount() {
return children.size();
}
/**在一行内分配各孩子位置*/
public void layout(int l, int t) {
lineWidth += horizontolSpacing*(children.size()-1);//行宽+=当前行所有孩子之间的水平间隔
int surplusChild = 0;//将剩余宽度平均分配给每个孩子的宽度
int surplus = width - lineWidth;//当前行分配孩子后剩余的宽度
if(surplus > 0 && children.size() > 0){

System.out.println("children:" + children.size());
surplusChild = surplus / children.size();//算出将剩余宽度平均分配给每个孩子的宽度

}

for (int i = 0; i < children.size(); i++) {
View child = children.get(i);
child.layout(l,t,l+child.getMeasuredWidth()+surplusChild,t+child.getMeasuredHeight());//确定当前控件的位置,绘出控件
l+=child.getMeasuredWidth()+ surplusChild+ horizontolSpacing ;//计算下一个孩子的横坐标(加上当前控件宽度,控件间隔,分给每个控件的剩余宽度)
}
}
}
}


自定义ViewGroup的OnMeasure():

指定控件孩子的判定规则

根据父视图的规则模式确定控件孩子的规则模式



测量每个孩子

设定父视图尺寸



在Activity中应用自定义布局

创建并实例化一些文本控件显示文本,将文本控件填充到FlowLayout中。

private List<String> datas;
public void onCreate(){
ScrollView scrollView = new ScrollView(getContext());//最上层布局ScrollView
scrollView.setBackgroundResource(R.drawable.grid_item_bg_normal);

FlowLayout layout= new FlowLayout(getContext());//实例化FlowLayout
int padding=UIutil.dip2px(13);
layout.setPadding(padding, padding, padding, padding);

int backColor = 0xffcecece;//背景色,用于创建Drawable的背景色
Drawable pressedDrawable = DrawableUtil.createShape(backColor);//创建点击后的显示的Drawable
/**根据文本数据的数量确定文本控件*/
for (int i = 0; i < datas.size(); i++) {
TextView textView = new TextView(getContext());
final String s = datas.get(i);
textView.setText(s);

Random random=new Random();   //创建随机对象
int red = random.nextInt(255)+1;
int green = random.nextInt(255)+1;
int blue = random.nextInt(255)+1;//创建随机色
int color = Color.rgb(red, green, blue);//范围 0-255 随机色
GradientDrawable createShape = DrawableUtil.createShape(color);// 默认显示的图片
StateListDrawable selectorDrawable = DrawableUtil.createSelectorDrawable(pressedDrawable, createShape);//Drawable选择器

/**设置文本控件参数和点击事件*/            textView.setBackground(selectorDrawable);//将选择器设成背景
textView.setTextColor(Color.WHITE);
textView.setTextSize(18);
textView.setGravity(Gravity.CENTER);
int textPaddingV = UIutil.dip2px(4);
int textPaddingH = UIutil.dip2px(7);
textView.setPadding(textPaddingH,textPaddingV,textPaddingH,textPaddingV);
textView.setClickable(true);
textView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(getContext(),s,Toast.LENGTH_SHORT).show();
}
});
/**FlowLayout添加孩子*/
layout.addView(textView,new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT,-2));// -2 包裹内容

}
/**scrollView添加FlowLayout为孩子*/
scrollView.addView(layout);

setContentView(scrollView);
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息