您的位置:首页 > 移动开发 > Android开发

Android自定义控件之流布局

2017-07-09 23:57 190 查看
流布局之前我写过,但是之前写的问题很多。所以这里我重新写了一个流布局。

这里我先按照惯例先上代码https://github.com/GitHubToLiao/TagLayout.git

下来看下效果



前面铺垫做好之后,让我慢慢给大家讲讲我的实现过程,以及在这个过程中踩到的坑。

好了开始开车,请大家坐好并且系好安全带,我先讲下我实现的思路

1.我们知道流布局,其实就是如果自动换行,也就是我们的内容超过当前行宽,就会换一行,超过的内容在下一行显示。基于这个原则我们在写的时候就需要知道我们什么时候换行,而我们的大量工作就是在算什么时候换行。

2.同样的我们在写的时候,有的时候我们的数据是从后台获取到的,所以我们的布局不应该写死,而是用Adapter设计模式。也就是我们在给我们的组件添加数据的时候和ListView一样通过设置Adapter给设置数据。

一、上面讲了思路,然后我在给大家讲一讲具体的实现方法。

一、创建一个类,让其继承ViewGroup。然后重写里面的构造

二、重写onMeasure方法这个方法的作用是对数据进行测量。先上代码然后再解释

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//清除一次数据  原因是onMeasure可能走多次
mChildViews.clear();
//获取子View的个数
int childCount = getChildCount();
//每一行的高度  默认为他的paddingLeft  和 paddingRight
int lineWidth = getPaddingLeft() + getPaddingRight();
//获取总的宽度
int width = MeasureSpec.getSize(widthMeasureSpec);
//获取默认高度
int height = getPaddingTop() + getPaddingBottom();
//每一行最大高度
int maxHeight = 0;
//用来存放每一行
4000
的子View
ArrayList<View> childViews = new ArrayList<>();
mChildViews.add(childViews);
for (int i = 0; i < childCount; i++) {
//获取ViewGroup中的子View
View childView = getChildAt(i);
//如果子View不可见  就跳出本次循环
if(childView.getVisibility() ==GONE){
continue;
}
//对子孩子进行测量
measureChild(childView, widthMeasureSpec, heightMeasureSpec);
//获取layoutParams  目的为得到Margin
MarginLayoutParams layoutParams = (MarginLayoutParams) childView.getLayoutParams();

//判断是否换行
if ((lineWidth + childView.getMeasuredWidth() + layoutParams.leftMargin + layoutParams.rightMargin) > width) {
//换行
height += childView.getMeasuredHeight() + layoutParams.bottomMargin + layoutParams.topMargin;
lineWidth = getPaddingLeft() + getPaddingRight();
childViews = new ArrayList<>();
mChildViews.add(childViews);
} else {
//不换行
lineWidth += childView.getMeasuredWidth() + layoutParams.rightMargin + layoutParams.leftMargin;
maxHeight = Math.max(childView.getMeasuredHeight() + layoutParams.bottomMargin + layoutParams.topMargin, maxHeight);

}
childViews.add(childView);

}
height += maxHeight;
setMeasuredDimension(width, height);
}


//用来存放所有的子view

1.在上面的代码中我们首先创建两个集合,一个用来存放所有的子View,但是这里我们的集合的返现同样也是一个集合,也就是我们在存所有子View的过程中。就已经将所有的子View按照行进行划分出来

//这里存放所有的子View并且按照行进行划分
private List<List<View>> mChildViews = new ArrayList<>();


2.什么时候换行,我们队子View宽挨个往后叠加,如果在叠加过程中宽度超过屏幕宽度,那么我们就进行换行

//判断换行
if ((lineWidth + childView.getMeasuredWidth() + layoutParams.leftMargin + layoutParams.rightMargin) > width) {


三、重写onLayout进行布局摆放,直接上代码相信你们看了代码就会明白

@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int left, top = getPaddingTop(), right, bottom;
for (List<View> mChildView : mChildViews) {
left = getPaddingLeft();
for (View view : mChildView) {
//如果子View不可见  就跳出本次循环
if(view.getVisibility() ==GONE){
continue;
}

MarginLayoutParams layoutParams = (MarginLayoutParams) view.getLayoutParams();
left += layoutParams.leftMargin;
int childTop = top + layoutParams.bottomMargin;
right =left + view.getMeasuredWidth();
bottom = childTop + view.getMeasuredHeight();
//计算出位置然后计算
view.layout(left, childTop, right, bottom);
left += view.getMeasuredWidth();
}
MarginLayoutParams layoutParams = (MarginLayoutParams) mChildView.get(0).getLayoutParams();
top += mChildView.get(0).getMeasuredHeight() + layoutParams.bottomMargin + layoutParams.topMargin;
}
}


四、使用Adapter设计模式进行数据添加

public void setAdapter(TagAdapter adapter) {
if (adapter == null) {
return;
}

removeAllViews();
mAdapter = null;
mAdapter = adapter;

int childCount = mAdapter.getCount();
for (int i = 0; i < childCount; i++) {
View childView = mAdapter.getView(i, this, this);
addView(childView);
}

}


最后我来说说我在写的过程中所踩的坑

1.在我们进行测量的时候,在存放所有子View之前一定要将存放子View的集合清空,原因就是onMeasure会走多次如果,我们不清空存放子View的集合的数据那么会出现问题

//测量
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//清除一次数据  原因是onMeasure可能走多次
mChildViews.clear();
//获取子View的个数
int childCount = getChildCount();
...


2.测量的过程中,一定要对子View进行测量,如果不测量后面就会获取不到子View的宽高。如果不测量你会发现写完之后布局时空白的.

//对子孩子进行测量
measureChild(childView, widthMeasureSpec, heightMeasureSpec);


3.对Margin处理

我们在处理Margin的过程中肯定需要在代码中获取Margin。

MarginLayoutParams layoutParams = (MarginLayoutParams) view.getLayoutParams();
left += layoutParams.leftMargin;


上面是获取Margin的方法,但是你们认为这样写就完了吗?不不,我们在使用这个方法之前需要重写两个方法。

@Override
protected LayoutParams generateDefaultLayoutParams() {
return new MarginLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
}

@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new MarginLayoutParams(getContext(), attrs);
}


下面的方法我不做解释,这里我解释一下上面的方法。

先说下上面的方法是在什么时候用呢?他是在我们设置数据的过程中,我们肯定需要子View,但是我们的子View是通过动态创建,并且还获取Margin时调用。

不明白???那好上代码

mTagLayout.setAdapter(new TagAdapter() {
@Override
public int getCount() {
return mDatas.size();
}

@Override
public View getView(int position,View convertView, ViewGroup parent) {

TextView mTV = new TextView(MainActivity.this);
TextView mTV  = (TextView)
mTV.setText(mDatas.get(position));
return mTV  ;
}
});


上面是我们在Adapter里面设置数据的时候,我们的TextView不是通过findViewById的方式获取的,而是通过new的方式获取,但是如果你不重写第一个方法,你会发现你在运行的过程中,会报错。 不信你试试。

这就是我的自定义控件之流布局,这里我的一些处理还是有点问题,那就是处理Margin和Padding的时候。后面我会完善并将代码上传Github的。

代码地址https://github.com/GitHubToLiao/TagLayout.git
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐