Android自定义控件之流布局
2017-07-09 23:57
190 查看
流布局之前我写过,但是之前写的问题很多。所以这里我重新写了一个流布局。
这里我先按照惯例先上代码https://github.com/GitHubToLiao/TagLayout.git
下来看下效果
前面铺垫做好之后,让我慢慢给大家讲讲我的实现过程,以及在这个过程中踩到的坑。
好了开始开车,请大家坐好并且系好安全带,我先讲下我实现的思路
1.我们知道流布局,其实就是如果自动换行,也就是我们的内容超过当前行宽,就会换一行,超过的内容在下一行显示。基于这个原则我们在写的时候就需要知道我们什么时候换行,而我们的大量工作就是在算什么时候换行。
2.同样的我们在写的时候,有的时候我们的数据是从后台获取到的,所以我们的布局不应该写死,而是用Adapter设计模式。也就是我们在给我们的组件添加数据的时候和ListView一样通过设置Adapter给设置数据。
一、上面讲了思路,然后我在给大家讲一讲具体的实现方法。
一、创建一个类,让其继承ViewGroup。然后重写里面的构造
二、重写onMeasure方法这个方法的作用是对数据进行测量。先上代码然后再解释
//用来存放所有的子view
1.在上面的代码中我们首先创建两个集合,一个用来存放所有的子View,但是这里我们的集合的返现同样也是一个集合,也就是我们在存所有子View的过程中。就已经将所有的子View按照行进行划分出来
2.什么时候换行,我们队子View宽挨个往后叠加,如果在叠加过程中宽度超过屏幕宽度,那么我们就进行换行
三、重写onLayout进行布局摆放,直接上代码相信你们看了代码就会明白
四、使用Adapter设计模式进行数据添加
最后我来说说我在写的过程中所踩的坑
1.在我们进行测量的时候,在存放所有子View之前一定要将存放子View的集合清空,原因就是onMeasure会走多次如果,我们不清空存放子View的集合的数据那么会出现问题
2.测量的过程中,一定要对子View进行测量,如果不测量后面就会获取不到子View的宽高。如果不测量你会发现写完之后布局时空白的.
3.对Margin处理
我们在处理Margin的过程中肯定需要在代码中获取Margin。
上面是获取Margin的方法,但是你们认为这样写就完了吗?不不,我们在使用这个方法之前需要重写两个方法。
下面的方法我不做解释,这里我解释一下上面的方法。
先说下上面的方法是在什么时候用呢?他是在我们设置数据的过程中,我们肯定需要子View,但是我们的子View是通过动态创建,并且还获取Margin时调用。
不明白???那好上代码
上面是我们在Adapter里面设置数据的时候,我们的TextView不是通过findViewById的方式获取的,而是通过new的方式获取,但是如果你不重写第一个方法,你会发现你在运行的过程中,会报错。 不信你试试。
这就是我的自定义控件之流布局,这里我的一些处理还是有点问题,那就是处理Margin和Padding的时候。后面我会完善并将代码上传Github的。
代码地址https://github.com/GitHubToLiao/TagLayout.git
这里我先按照惯例先上代码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
相关文章推荐
- Android自定义控件实现及其布局
- Android自定义控件实现及其布局
- Android自定义控件之流式布局
- android 自定义控件学习之三 控件布局常用知识总结
- Android中自定义控件的测量及布局
- android自定义控件后如何调整自身子控件与父类中子控件的布局
- android开发之四种基本布局和自定义控件
- Android基础之自定义控件、布局以及ListView控件
- 【Android自定义控件】布局悬停在顶部
- Android使用后自定义控件如何在该布局文件以外动态的设置自定义控件的属性
- Android自定义控件实现及其布局
- Android自定义控件系列 十:利用添加自定义布局来搞定触摸事件的分发,解决组合界面中特定控件响应特定方向的事件
- Android自定义控件系列 十:利用添加自定义布局来搞定触摸事件的分发,解决组合界面中特定控件响应特定方向的事件
- Android 自定义控件之打造流布局实现热门搜索标签
- Android自定义控件系列 十:利用添加自定义布局来搞定触摸事件的分发,解决组合界面中特定控件响应特定方向的事件
- Android 自定义控件布局 NullPointerException findViewById
- android自定义控件系列教程----视图的测量和布局
- Android自定义控件系列 十:利用添加自定义布局来搞定触摸事件的分发,解决组合界面中特定控件响应特定方向的事件
- Android自定义控件系列 十:利用添加自定义布局来搞定触摸事件的分发,解决组合界面中特定控件响应特定方向的事件
- Android自定义控件--流式布局(FlowLayout)--自动适配