上篇主要是简单的介绍下自定义viewGroup的用途以及方法,这篇写个小例子试试水,先几张效果图
模仿微信朋友圈显示图片,可以看到,根据图片数量的不同,显示的方式也不一样,下面来看看实现的思路
思路
1,测量viewGroup,根据子view的数量来控制viewGroup的宽高
(一):如果子view的数量少于或者等于3的时候,就显示一行,宽就是子view宽的总和,高则是子view的高
(二):如果子view大于3并且小于等于6,分两种情况,一种是子view数量是4,如果是4,那么宽则是子view宽的2倍,高是子view高的2倍,如果不是4,那么就显示两行,一行是3,一行是2或者3,那么宽则是取最大,也就是子view宽的3倍,高是子view的2倍
(三):如果子view大于6了,那么宽就是子view的3倍,高是子view的3倍
2,测量完成之后,就到了子view的摆放了
(一):如果子view数量少于或者等于3,则摆放一行,从左到右
(二):如果子view大于3并且小于等于6,如果是4,则两行,上下各2个,否则就第一行3个,第二行2个或者3个
(三):如果子view数量大于6,则3行,前2行3个,最后一行1个,2个或者3个
先看看代码:
onMeasure:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//总宽度和高度
int totalWidth = 0;
int totalHeight = 0;
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
measureChild(child, widthMeasureSpec, heightMeasureSpec);
// 得到child的lp
MarginLayoutParams lp = (MarginLayoutParams) child
.getLayoutParams();
// 当前子空间实际占据的宽度
int childWidth = child.getMeasuredWidth() + lp.leftMargin
+ lp.rightMargin;
// 当前子空间实际占据的高度
int childHeight = child.getMeasuredHeight() + lp.topMargin
+ lp.bottomMargin;
if (getChildCount() <= 3) {
totalWidth += childWidth;
totalHeight = childHeight;
} else if (getChildCount() <= 6) {
if (getChildCount() == 4) {
totalWidth = childWidth * 2;
totalHeight = childHeight * 2;
} else {
totalWidth = childWidth * 3;
totalHeight = childHeight * 2;
}
} else if (getChildCount() <= 9) {
totalWidth = childWidth * 3;
totalHeight = childHeight * 3;
}
}
setMeasuredDimension(totalWidth + getPaddingLeft() + getPaddingRight(),
totalHeight + getPaddingTop() + getPaddingBottom());
}
不难理解,首先我们定义出两个变量,分别记录总宽度和高度,然后,我们遍历拿到所有子view的宽高,根据思路来进行测量,测量完成之后,在来看看如何摆放子view
onLayout
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
//onLayout会被调用多次,为了预防重叠
mAllViews.clear();
mLineHeight.clear();
//获取总宽度
int width = getWidth();
//单行宽度和当行高度
int lineWidth = 0;
int lineHeight = 0;
// 存储每一行所有的childView
List<View> childViews = new ArrayList<>();
int childCount = getChildCount();
// 遍历所有的子view
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
MarginLayoutParams lp = (MarginLayoutParams) child
.getLayoutParams();
int childWidth = child.getMeasuredWidth();
int childHeight = child.getMeasuredHeight();
// 如果已经需要换行
if (childWidth + lp.leftMargin + lp.rightMargin + lineWidth > width) {
// 记录这一行所有的View以及最大高度
mLineHeight.add(lineHeight);
// 将当前行的childView保存,然后开启新的ArrayList保存下一行的childView
mAllViews.add(childViews);
lineWidth = 0;// 重置行宽
childViews = new ArrayList<>();
}
// 如果不需要换行,则累加
lineWidth += childWidth + lp.leftMargin + lp.rightMargin;
lineHeight = Math.max(lineHeight, childHeight + lp.topMargin
+ lp.bottomMargin);
childViews.add(child);
}
// 记录最后一行
mLineHeight.add(lineHeight);
mAllViews.add(childViews);
int left = getPaddingLeft();
int top = getPaddingTop();
// 得到总行数
int lineNum = mAllViews.size();
for (int i = 0; i < lineNum; i++) {
// 每一行的所有的views
childViews = mAllViews.get(i);
// 当前行的最大高度
lineHeight = mLineHeight.get(i);
// 遍历当前行所有的子View
for (int j = 0; j < childViews.size(); j++) {
View child = childViews.get(j);
if (child.getVisibility() != View.GONE) {
MarginLayoutParams lp = (MarginLayoutParams) child
.getLayoutParams();
//计算childView的left,top,right,bottom
int childLeft = left + lp.leftMargin;
int childTop = top + lp.topMargin;
int childRight = childLeft + child.getMeasuredWidth();
int childBottom = childTop + child.getMeasuredHeight();
child.layout(childLeft, childTop, childRight, childBottom);
left += child.getMeasuredWidth() + lp.rightMargin
+ lp.leftMargin;
}
}
//换行后,重新从第一个开始,高度累加
left = getPaddingTop();
top += lineHeight;
}
}
我们定义两个list来存储所有的子view和每一行的高度,然后,我们继续遍历子view,如果当行所有子view的宽累加大于了布局的宽度,我们就换行,否则就继续累加,最后把所有的行数和子view添加到我们的list中,最后通过遍历list来将子view拿出来,确定left,top,right,bottom之后,就可以进行布局了,这里要注意的是,如果需要换行了,那么子view的位置是从第一个开始,所以我们需要将left重置,并且高度增加,现在看看怎么用,分两种用法,一种就是静态布局,一种是动态布局,我们在开发中,一般都是使用动态布局方式
静态用法
静态用法就是写在xml之中,view的数量手动去添加
<com.example.administrator.myactivity.MyView
android:id="@+id/myView"
android:layout_width="wrap_content"
android:background="@color/colorAccent"
android:layout_height="wrap_content">
<ImageView
android:layout_width="100dp"
android:layout_height="100dp"
android:src="@mipmap/ic_launcher" />
<ImageView
android:layout_width="100dp"
android:layout_height="100dp"
android:src="@mipmap/ic_launcher" />
<ImageView
android:layout_width="100dp"
android:layout_height="100dp"
android:src="@mipmap/ic_launcher" />
<ImageView
android:layout_width="100dp"
android:layout_height="100dp"
android:src="@mipmap/ic_launcher" />
<ImageView
android:layout_width="100dp"
android:layout_height="100dp"
android:src="@mipmap/ic_launcher" />
<ImageView
android:layout_width="100dp"
android:layout_height="100dp"
android:src="@mipmap/ic_launcher" />
<ImageView
android:layout_width="100dp"
android:layout_height="100dp"
android:src="@mipmap/ic_launcher" />
<ImageView
android:layout_width="100dp"
android:layout_height="100dp"
android:src="@mipmap/ic_launcher" />
<ImageView
android:layout_width="100dp"
android:layout_height="100dp"
android:src="@mipmap/ic_launcher" />
</com.example.administrator.myactivity.MyView>
这也是我效果图的用法
动态用法
动态用法则是根据服务器返回的数据来添加子view,这里我模拟一下
首先我们定义一个item布局,里面就一个ImageView
<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:src="@mipmap/ic_launcher">
</ImageView>
然后我们在activity中,动态的添加数据
public class MainActivity extends AppCompatActivity {
MyView myView;
private int[] images = {R.mipmap.ic_launcher, R.mipmap.ic_launcher, R.mipmap.ic_launcher};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
myView = (MyView) findViewById(R.id.myView);
setData();
}
private void setData() {
LayoutInflater inflater = LayoutInflater.from(this);
for (int i = 0; i < images.length; i++) {
ImageView imageView = (ImageView) inflater.inflate(R.layout.item, myView, false);
imageView.setImageResource(images[i]);
myView.addView(imageView);
}
}
}
这样就完成了动态添加数据,好了,到这里就完成了模仿微信朋友圈显示图片效果,最后附上整个自定义viewGroup的代码:
package com.example.administrator.myactivity;
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import java.util.ArrayList;
import java.util.List;
/**
* Created by Pan_ on 2015/2/2.
*/
public class MyView extends ViewGroup {
/**
* 存储所有的View,按行记录
*/
private List<List<View>> mAllViews = new ArrayList<>();
/**
* 记录每一行的最大高度
*/
private List<Integer> mLineHeight = new ArrayList<>();
public MyView(Context context) {
super(context);
}
public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//总宽度和高度
int totalWidth = 0;
int totalHeight = 0;
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
measureChild(child, widthMeasureSpec, heightMeasureSpec);
// 得到child的lp
MarginLayoutParams lp = (MarginLayoutParams) child
.getLayoutParams();
// 当前子空间实际占据的宽度
int childWidth = child.getMeasuredWidth() + lp.leftMargin
+ lp.rightMargin;
// 当前子空间实际占据的高度
int childHeight = child.getMeasuredHeight() + lp.topMargin
+ lp.bottomMargin;
if (getChildCount() <= 3) {
totalWidth += childWidth;
totalHeight = childHeight;
} else if (getChildCount() <= 6) {
if (getChildCount() == 4) {
totalWidth = childWidth * 2;
totalHeight = childHeight * 2;
} else {
totalWidth = childWidth * 3;
totalHeight = childHeight * 2;
}
} else if (getChildCount() <= 9) {
totalWidth = childWidth * 3;
totalHeight = childHeight * 3;
}
}
setMeasuredDimension(totalWidth + getPaddingLeft() + getPaddingRight(),
totalHeight + getPaddingTop() + getPaddingBottom());
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
//onLayout会被调用多次,为了预防重叠
mAllViews.clear();
mLineHeight.clear();
//获取总宽度
int width = getWidth();
//单行宽度和当行高度
int lineWidth = 0;
int lineHeight = 0;
// 存储每一行所有的childView
List<View> childViews = new ArrayList<>();
int childCount = getChildCount();
// 遍历所有的子view
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
MarginLayoutParams lp = (MarginLayoutParams) child
.getLayoutParams();
int childWidth = child.getMeasuredWidth();
int childHeight = child.getMeasuredHeight();
// 如果已经需要换行
if (childWidth + lp.leftMargin + lp.rightMargin + lineWidth > width) {
// 记录这一行所有的View以及最大高度
mLineHeight.add(lineHeight);
// 将当前行的childView保存,然后开启新的ArrayList保存下一行的childView
mAllViews.add(childViews);
lineWidth = 0;// 重置行宽
childViews = new ArrayList<>();
}
// 如果不需要换行,则累加
lineWidth += childWidth + lp.leftMargin + lp.rightMargin;
lineHeight = Math.max(lineHeight, childHeight + lp.topMargin
+ lp.bottomMargin);
childViews.add(child);
}
// 记录最后一行
mLineHeight.add(lineHeight);
mAllViews.add(childViews);
int left = getPaddingLeft();
int top = getPaddingTop();
// 得到总行数
int lineNum = mAllViews.size();
for (int i = 0; i < lineNum; i++) {
// 每一行的所有的views
childViews = mAllViews.get(i);
// 当前行的最大高度
lineHeight = mLineHeight.get(i);
// 遍历当前行所有的子View
for (int j = 0; j < childViews.size(); j++) {
View child = childViews.get(j);
if (child.getVisibility() != View.GONE) {
MarginLayoutParams lp = (MarginLayoutParams) child
.getLayoutParams();
//计算childView的left,top,right,bottom
int childLeft = left + lp.leftMargin;
int childTop = top + lp.topMargin;
int childRight = childLeft + child.getMeasuredWidth();
int childBottom = childTop + child.getMeasuredHeight();
child.layout(childLeft, childTop, childRight, childBottom);
left += child.getMeasuredWidth() + lp.rightMargin
+ lp.leftMargin;
}
}
//换行后,重新从第一个开始,高度累加
left = getPaddingTop();
top += lineHeight;
}
}
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new MarginLayoutParams(getContext(), attrs);
}
}
这里补充一点,为什么我们需要设置LayoutParams 呢,因为我们每一个layout对应一个LayoutParams ,比如LinereanLayout对应的就是LinereanLayout的LayoutParams ,RelativeLayout对应的是RelativeLayout的LayoutParams,所以,我们在写LayoutParams 的时候,会发现有很多layout的LayoutParams ,那怎么知道我需要哪一个呢?这时候就看你的父view是哪个布局,就对应哪个LayoutParams ,我们在自定义viewGroup的时候,因为考虑到子view有Margin的属性,所以,我们设置他对应的LayoutParams 就是MarginLayoutParams,好了,这篇文章到这里就结束了,快到新年了,最后祝大家新年快乐~~