android动态添加网格布局,AutoFlowLayout:多功能流式布局与网格布局控件

近期工作需要用到流式布局,网上也有很多关于这方面的资料。发现流式布局与网格布局的自定义很有意思,是学习自定义控件的一个很好的方式,所以就撸了个几百行代码的控件,既实用又具有学习价值。

一、AutoFlowLayout应用场景

流式布局,在很多标签类的场景中可以用的;而网格布局在分类中以及自拍九宫格等场景很常见。如下所示:

1FP6095231-2325-0.jpeg

如此使用频繁而又实现简单的控件,怎能不自己撸一个呢?控件,还是定制的好啊。

二、AutoFlowLayout实现效果

先介绍下自己撸的这个控件的功能及效果。

1.功能

流式布局

自动换行

行数自定:单行/多行

支持单选/多选

支持行居中/靠左显示

支持添加/删除子View

支持子View点击/长按事件

网格布局

行数/列数自定

支持单选/多选

支持添加/删除子View

支持子View点击/长按事件

支持添加多样式分割线及横竖间隔

2.效果

下面以gif图的形式展现下实现的效果,样式简单了些,不过依然能展示出这个简单控件的多功能实用性。

流式布局

1FP6095231-3911-1.gif

1FP6095231-5158-2.gif

网格布局

1FP6095231-33T-3.gif

最后一个是带间隔以及分割线的,由于录屏原因,只在跳过去的一瞬间显示了粉红色的一条线。真实如下图所示,可以定义横竖间距的大小,以及分割线的颜色,宽度。

1FP6095231-43N-4.jpeg

三、AutoFlowLayout使用

1.添加依赖

①.在项目的 build.gradle 文件中添加

allprojects {

repositories {

...

maven { url 'https://jitpack.io' }

}

}

②.在 module 的 build.gradle 文件中添加依赖

dependencies {

compile 'com.github.LRH1993:AutoFlowLayout:1.0.2'

}

2.属性说明

下表是自定义的属性说明,可在xml中声明,同时有对应的get/set方法,可在代码中动态添加。

1FP6095231-3003-5.png

3.使用示例

布局

android:layout_width="match_parent"

android:layout_height="match_parent">

android:id="@+id/afl_cotent"

android:layout_width="match_parent"

android:layout_height="wrap_content"/>

代码设置数据

mFlowLayout.setAdapter(new FlowAdapter(Arrays.asList(mData)) {

@Override

public View getView(int position) {

View item = mLayoutInflater.inflate(R.layout.special_item, null);

TextView tvAttrTag = (TextView) item.findViewById(R.id.tv_attr_tag);

tvAttrTag.setText(mData[position]);

return item;

}

});

与ListView,GridView使用方式一样,实现FlowAdapter即可。

四、AutoFlowLayout原理

ViewGroup的测量、布局及绘制顺序如下所示:

1FP6095231-5537-6.png

详细的自定义View原理参考:图解View测量、布局及绘制原理

下面具体介绍自定义实现网格布局的过程。

1.重写generateLayoutParams()方法

因为我们要在onMeasure以及onLayout的过程中,测量子View的margin,所以要重写该方法,并返回MarginLayoutParams。

@Override

public LayoutParams generateLayoutParams(AttributeSet attrs)

{

return new MarginLayoutParams(getContext(), attrs);

}

@Override

protected LayoutParams generateLayoutParams(LayoutParams p) {

return new MarginLayoutParams(p);

}

@Override

protected LayoutParams generateDefaultLayoutParams() {

return new MarginLayoutParams(super.generateDefaultLayoutParams());

}

2.onMeasure过程

主要针对wrap_content情况下,要逐行逐列的测量每个子View的宽高,padding,margin以及横竖间距,来获得最终ViewGroup的宽高。

private void setGridMeasure(int widthMeasureSpec, int heightMeasureSpec) {

// 获得它的父容器为它设置的测量模式和大小

int sizeWidth = MeasureSpec.getSize(widthMeasureSpec);

int sizeHeight = MeasureSpec.getSize(heightMeasureSpec);

int modeWidth = MeasureSpec.getMode(widthMeasureSpec);

int modeHeight = MeasureSpec.getMode(heightMeasureSpec);

//获取viewgroup的padding

int paddingLeft = getPaddingLeft();

int paddingRight = getPaddingRight();

int paddingTop = getPaddingTop();

int paddingBottom = getPaddingBottom();

//最终的宽高值

int heightResult;

int widthResult;

//未设置行数 推测行数

if (mRowNumbers == 0) {

mRowNumbers = getChildCount()/mColumnNumbers == 0 ?

getChildCount()/mColumnNumbers : (getChildCount()/mColumnNumbers + 1);

}

int maxChildHeight = 0;

int maxWidth = 0;

int maxHeight = 0;

//统计最大高度/最大宽度

for (int i = 0; i < mRowNumbers; i++) {

for (int j = 0; j < mColumnNumbers; j++) {

final View child = getChildAt(i * mColumnNumbers + j);

if (child != null) {

if (child.getVisibility() != GONE) {

measureChild(child,widthMeasureSpec,heightMeasureSpec);

// 得到child的lp

MarginLayoutParams lp = (MarginLayoutParams) child

.getLayoutParams();

maxWidth +=child.getMeasuredWidth()+lp.leftMargin+lp.rightMargin;

maxChildHeight = Math.max(maxChildHeight, child.getMeasuredHeight()+lp.topMargin+lp.bottomMargin);

}

}

}

maxHeight += maxChildHeight;

maxChildHeight = 0;

}

int tempWidth = (int) (maxWidth+mHorizontalSpace*(mColumnNumbers-1)+paddingLeft+paddingRight);

int tempHeight = (int) (maxHeight+mVerticalSpace*(mRowNumbers-1)+paddingBottom+paddingTop);

if (tempWidth > sizeWidth) {

widthResult = sizeWidth;

} else {

widthResult = tempWidth;

}

//宽高超过屏幕大小,则进行压缩存放

if (tempHeight > sizeHeight) {

heightResult = sizeHeight;

} else {

heightResult = tempHeight;

}

setMeasuredDimension((modeWidth == MeasureSpec.EXACTLY) ? sizeWidth

: widthResult, (modeHeight == MeasureSpec.EXACTLY) ? sizeHeight

: heightResult);

}

3.onLayout过程

网格布局默认所有子View的宽高一致,先推算出每个子View的平均宽高,然后逐个推算每个子View的left,top,right,bottom位置,调用child.layout()进行子View布局。

private void setGridLayout() {

mCheckedViews.clear();

mCurrentItemIndex = -1;

int sizeWidth = getWidth();

int sizeHeight = getHeight();

//子View的平均宽高 默认所有View宽高一致

View tempChild = getChildAt(0);

MarginLayoutParams lp = (MarginLayoutParams) tempChild

.getLayoutParams();

int childAvWidth = (int) ((sizeWidth - getPaddingLeft() - getPaddingRight() - mHorizontalSpace * (mColumnNumbers-1))/mColumnNumbers)-lp.leftMargin-lp.rightMargin;

int childAvHeight = (int) ((sizeHeight - getPaddingTop() - getPaddingBottom() - mVerticalSpace * (mRowNumbers-1))/mRowNumbers)-lp.topMargin-lp.bottomMargin;

for (int i = 0; i < mRowNumbers; i++) {

for (int j = 0; j < mColumnNumbers; j++) {

final View child = getChildAt(i * mColumnNumbers + j);

if (child != null) {

mCurrentItemIndex++;

if (child.getVisibility() != View.GONE) {

setChildClickOperation(child, -1);

int childLeft = (int) (getPaddingLeft() + j * (childAvWidth + mHorizontalSpace))+j * (lp.leftMargin + lp.rightMargin) + lp.leftMargin;

int childTop = (int) (getPaddingTop() + i * (childAvHeight + mVerticalSpace)) + i * (lp.topMargin + lp.bottomMargin) + lp.topMargin;

child.layout(childLeft, childTop, childLeft + childAvWidth, childAvHeight +childTop);

}

}

}

}

}

4.dispatchDraw过程

绘制分割线得问过程,需要逐个对子View进行绘制分割线。所以重写dispatchDraw()方法。因为不需要对自己进行绘制,所以不需要重写onDraw()方法。

需要额外注意下,绘制过程中,考虑横竖间距的大小,这种情况下默认不考虑margin。

protected void dispatchDraw(Canvas canvas) {

super.dispatchDraw(canvas);

if (mIsGridMode && mIsCutLine) {

Paint linePaint = new Paint();

linePaint.setStyle(Paint.Style.STROKE);

linePaint.setStrokeWidth(mCutLineWidth);

linePaint.setColor(mCutLineColor);

for (int i = 0; i < mRowNumbers; i++) {

for (int j = 0; j < mColumnNumbers; j++) {

View child = getChildAt(i * mColumnNumbers + j);

//最后一列

if (j == mColumnNumbers-1) {

//不是最后一行 只画底部

if (i != mRowNumbers-1){

canvas.drawLine(child.getLeft()-mHorizontalSpace/2,child.getBottom()+mVerticalSpace/2,

child.getRight(),child.getBottom()+mVerticalSpace/2,linePaint);

}

} else {

//最后一行 只画右部

if (i == mRowNumbers -1) {

canvas.drawLine(child.getRight()+mHorizontalSpace/2, child.getTop()-mVerticalSpace/2,

child.getRight()+mHorizontalSpace/2,child.getBottom(),linePaint);

} else {

//底部 右部 都画

if (j == 0) {

canvas.drawLine(child.getLeft(),child.getBottom()+mVerticalSpace/2,

child.getRight()+mHorizontalSpace/2,child.getBottom()+mVerticalSpace/2,linePaint);

} else {

canvas.drawLine(child.getLeft()-mHorizontalSpace/2,child.getBottom()+mVerticalSpace/2,

child.getRight()+mHorizontalSpace/2,child.getBottom()+mVerticalSpace/2,linePaint);

}

if (i == 0) {

canvas.drawLine(child.getRight()+mHorizontalSpace/2, child.getTop(),

child.getRight()+mHorizontalSpace/2,child.getBottom()+mVerticalSpace/2,linePaint);

} else {

canvas.drawLine(child.getRight()+mHorizontalSpace/2, child.getTop()-mVerticalSpace/2,

child.getRight()+mHorizontalSpace/2,child.getBottom()+mVerticalSpace/2,linePaint);

}

}

}

}

}

}

}

绘制流式标签的过程类似,一样的简单。不过通过实现的过程,确实加深了对自定义ViewGroup的理解。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值