Android 自定义View 入门——FlowLayout实例(流布局)

了解了自定义View的流程,那么接下来最好就是对着一个自定义View去分析,加深一下对步骤的理解,这里拿FlowLayout去分析。这样一个自定义View 是继承的ViewGroup,因为它里面还有一个个的子View 填充,因此我们分析出这是继承自ViewGroup。然后接着分析,这个布局会自动判断 当宽度达到屏幕的宽度时,自动换到下一行,那接下来我们就去实现这样一个布局。

第一步:定义FlowLayout继承ViewGroup,并实现构造方法,以及父类的抽象方法 

public class FlowLayout extends ViewGroup {

	public FlowLayout(Context context) {
		super(context);
		// TODO Auto-generated constructor stub
	}

	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		// TODO Auto-generated method stub
		super.onMeasure(widthMeasureSpec, heightMeasureSpec);
	}
	
	@Override
	protected void onLayout(boolean changed, int l, int t, int r, int b) {
		// TODO Auto-generated method stub

	}

}
第二部 根据 MeasureSpec (测量规格)拿到 mode(测量模式) 和size(测量大小),然后根据这个大小去measure里面的child。

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		// 1获取with height 以及mode
		int widthMode = MeasureSpec.getMode(widthMeasureSpec);
		int heightMode = MeasureSpec.getMode(heightMeasureSpec);

		int heightSize = MeasureSpec.getSize(heightMeasureSpec)
				- getPaddingBottom() - getPaddingTop();
		int widthSize = MeasureSpec.getSize(widthMeasureSpec)
				- getPaddingLeft() - getPaddingRight();
		restoreLine();
		int count = getChildCount();
		// 2 测量子View
		for (int i = 0; i < count; i++) {
			View child = getChildAt(i);

			int widthSpec = MeasureSpec.makeMeasureSpec(widthSize,
					widthMode == MeasureSpec.EXACTLY ? MeasureSpec.AT_MOST
							: widthMode);
			int heightSpec = MeasureSpec.makeMeasureSpec(heightSize,
					heightMode == MeasureSpec.EXACTLY ? MeasureSpec.AT_MOST
							: heightMode);

			child.measure(widthSpec, heightSpec);
			if(mLine==null){
				mLine=new Line();
			}
			//将childview 添加到每一行中
			int childWidth=child.getMeasuredWidth();
			//当前行已经占用的宽度
			mUsedWidth+=childWidth;
			if(mUsedWidth<widthSize){
				//当前行还没有达到上限,那么该child就添加进这一行
				mLine.addView(child);
				mUsedWidth+=mHorizontalSpacing; //添加上两个子View之间水平方向的间隔
			}else{
				//说明长度超出了当前的最大宽度
				if(mLine.getViewCount()==0){
					//表示当前行中还没有元素,添加的第一个元素 长度就超过了最大宽度,那么也要把该child 添加进去保证有数据
					mLine.addView(child);
					
				}else{
					//表示当前行中已经有元素,那么换一行,添加进去
					newLine();
					mLine.addView(child);
					//改变已使用的宽度
					mUsedWidth+=mHorizontalSpacing+childWidth;
				}
				
			}
		}

		//前面只有换行的时候才将Line 添加到lines 集合中,这里要判断一下最后一行,将最后一行也添加进去
		if(mLine!=null&&mLine.getViewCount()>0&&!mLines.contains(mLine) ){
			//表示有数据
			mLines.add(mLine);
		}
		
		// 设置测量的宽高setMeasuredDimension
		int totoalHeight=0;
		for(int i=0;i<mLines.size();i++){
			totoalHeight+=mLines.get(i).mHeight;//N行的高度
		}
		//加上 行间距
		totoalHeight+=(mLines.size()-1)*mVerticalSpacing;
		//加上padding
		totoalHeight+=getPaddingBottom()+getPaddingTop();
		// 设置FlowLayout的宽度值 高度值 宽度就是默认的宽度,高度是总的高度
		int measuredHeight = resolveSize(totoalHeight, heightMeasureSpec);
		setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec),
				measuredHeight);
	}

measure 结束,接下来就是layout 去将view 具体确定view 的位置。

	@Override
	protected void onLayout(boolean changed, int l, int t, int r, int b) {

		//主要是调用child.layout
		int count=mLines.size();
		int left = getPaddingLeft();
		int top = getPaddingTop();
		
		for(int i=0;i<count;i++){
		Line line=mLines.get(i);
		line.layout(left,top);
		top+=mVerticalSpacing+line.mHeight;
		}

	}
这个方法主要用来layout 子view。

	public void layout(int l, int t) {
			int left = l;
			int top = t;
			// 父布局的宽度
			int totoalWidth = getMeasuredWidth() - getPaddingLeft()
					- getPaddingRight();
			// 当前line 中view的个数
			int count = getViewCount();
			// 剩余空间平分给每个View
			int spaceLast = totoalWidth - mWidth - (count - 1)
					* mHorizontalSpacing;
			int averageWidth = spaceLast / count;
			// 平分的宽度
			// int splitSpacing = (int) (spaceLast / count + 0.5);
			for (int i = 0; i < count; i++) {
				View child = views.get(i);
				int childHeight = child.getMeasuredHeight();
				int childWidth = child.getMeasuredWidth();

				childWidth += averageWidth;
				child.getLayoutParams().width = childWidth;
				// 改变了原来的宽高,重新测量一次
				int widthMeasureSpec = MeasureSpec.makeMeasureSpec(childWidth,
						MeasureSpec.EXACTLY);
				int heightMeasureSpec = MeasureSpec.makeMeasureSpec(
						childHeight, MeasureSpec.EXACTLY);

				child.measure(widthMeasureSpec, heightMeasureSpec);
				// 布局View
				child.layout(left, top, left + childWidth, top + childHeight);
				left += childWidth + mHorizontalSpacing; // 为下一个View的left赋值
			}
		}
这个方法主要实现的功能是将每一行剩余的空间,平均分配到该行的每一个View 身上。

这样一个布局在这里就基本实现了。

当然,可以根据自己的需要 进行细微的调整,比如改变子view的 弧度,padding 等等。下面给出具体效果




代码地址: https://github.com/CodingForAndroid/FlowLayoutDemo


评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值