自定义ViewGroup
需要重写两个方法:
(1)onMeasure:测量View的款和高,设置自己的宽和高
(2)onLayout:设置子View 的位置
onMeasure:根据子View的布局文件,为子View设置测量模式和测量值
测量包括:测量模式和测量值;
测量模式,有三种情况:
1、当设置的容器大小为精确值或者match_parent时,为EXACTLY;
2、当设置的容器大小为wrap_content时,为AT_MOST;
3、当自定义容器的大小时,为UNSPCIFIED;
ViewGroup的LayoutParams为MarginLayoutParams
(关于LayoutParams,在设置的时候的规则是容器是哪种Layout,设置的Layoutparams就是哪种params)
效果图如下:
首先创建新项目Mooc,自定义一个GroupView--FlowLayout.java:
import java.util.ArrayList;
import java.util.List;
import android.annotation.SuppressLint;
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
public class FlowLayout extends ViewGroup {
// 有自定义的属性
public FlowLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public FlowLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public FlowLayout(Context context) {
this(context, null);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// 测量子控件的宽和高
// 容器是fill或者是精确的值
int sizeWidth = MeasureSpec.getSize(widthMeasureSpec);
int modeWidth = MeasureSpec.getMode(widthMeasureSpec);// 测量模式
// 容器是fill或者是精确的值这里会是EXACTLY
int sizeHeight = MeasureSpec.getSize(heightMeasureSpec);
int modeHeight = MeasureSpec.getMode(heightMeasureSpec);
// wrap_content属性的情况,控件的宽度和高度
int width = 0;
int height = 0;
// 记录每一行的宽和高
int lineWidth = 0;
int lineHeight = 0;
// 得到内部元素的个数
int cCount = getChildCount();
for (int i = 0; i < cCount; i++) {
View child = getChildAt(i);
// 测量子View的宽和高
measureChild(child, widthMeasureSpec, heightMeasureSpec);
// 得到LayoutParams
MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
// 子view占据的宽度
int childWidth = child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin;
// 高度
int childHeight = child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin;
// 检测是否换行
if (lineWidth + childWidth > sizeWidth - getPaddingLeft() - getPaddingRight()) {
// 得到最大的宽度
width = Math.max(lineWidth, width);// 这里决定了整个容器的高度和宽度
// 重置lineWidth
lineWidth = childWidth;
height += lineHeight;
lineHeight = childHeight;
} else {
// 叠加行的宽度
lineWidth += childWidth;
// 得到当前行最大的高度
lineHeight = Math.max(lineHeight, childHeight);
}
if (i == cCount - 1) {
width = Math.max(lineWidth, width);
height += lineHeight;
}
}
Log.e("TAG", "sizeWidth" + sizeWidth);
Log.e("TAG", "sizeHeight" + sizeHeight);
setMeasuredDimension(modeWidth == MeasureSpec.EXACTLY ? sizeWidth : width
+ getPaddingLeft() + getPaddingRight(),
modeHeight == MeasureSpec.EXACTLY ? sizeHeight : height + getPaddingTop()
+ getPaddingBottom());
}
private List<List<View>> mAllViews = new ArrayList<List<View>>();
private List<Integer> mLineHeight = new ArrayList<Integer>();
@SuppressLint("DrawAllocation")
@Override
protected void onLayout(boolean changed, int l, int r, int t, int b) {
// 这里的原因是这个执行过程可能会执行很多次
mAllViews.clear();
mLineHeight.clear();
int width = getWidth();
int lineWidth = 0;
int lineHeight = 0;
List<View> lineViews = new ArrayList<View>();
int cCount = getChildCount();
for (int i = 0; i < cCount; i++) {
View child = getChildAt(i);
MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
int childWidht = child.getMeasuredWidth();
int childHeight = child.getMeasuredHeight();
// 换行
if (childWidht + lineWidth + lp.leftMargin + lp.rightMargin > width - getPaddingLeft() - getPaddingRight()) {
mLineHeight.add(lineHeight);
mAllViews.add(lineViews);
lineWidth = 0;
lineHeight = childHeight + lp.topMargin + lp.bottomMargin;
lineViews = new ArrayList<View>();
}
lineWidth += childWidht + lp.leftMargin + lp.rightMargin;
lineHeight = Math.max(lineHeight, childHeight + lp.topMargin + lp.bottomMargin);
lineViews.add(child);
}// for end
// the last line
mLineHeight.add(lineHeight);
mAllViews.add(lineViews);
// set the position of view;
int left = getPaddingLeft();
int top = getPaddingTop();
int lineNum = mAllViews.size();
for (int i = 0; i < lineNum; i++) {
lineViews = mAllViews.get(i);
lineHeight = mLineHeight.get(i);
for (int j = 0; j < lineViews.size(); j++) {
View child = lineViews.get(j);
if (child.getVisibility() == View.GONE) {
continue;
}
MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
int lc = left + lp.leftMargin;
int tc = top + lp.topMargin;
int rc = lc + child.getMeasuredWidth();
int bc = tc + child.getMeasuredHeight();
// layout child views;
child.layout(lc, tc, rc, bc);
left += child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin;
}
left = getPaddingLeft();
top += lineHeight;
}
}
// 对应的MarginLayoutParams
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
// 定义params
return new MarginLayoutParams(getContext(), attrs);
}
}
这里考虑到了多种情况,比如你设置了容器的padding或者无论你设置是match_parent还是wrap_content,但是有一个问题自己有所疑问,就是打印这里面的log时,当布局为match_parent时为屏幕的宽和高,但是当设置为精确值时比如200dp,打印的理所当然应该是200dp,但是却打印的是400dp,为真实值的2倍。这里需要注意一下当你设置为精确值的情况,可能不会得到你预期的效果。
一下为了效果的美观进行了一些自定义控件的外观:
一个背景效果:/drawable/text_bg.xml
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android=" http://schemas.android.com/apk/res/android" >
<solid android:color="#e7e7e7" >
</solid>
<corners android:radius="30dp" />
<padding
android:bottom="2dp"
android:left="10dp"
android:right="10dp"
android:top="2dp" />
</shape>
<shape xmlns:android=" http://schemas.android.com/apk/res/android" >
<solid android:color="#e7e7e7" >
</solid>
<corners android:radius="30dp" />
<padding
android:bottom="2dp"
android:left="10dp"
android:right="10dp"
android:top="2dp" />
</shape>
一个自定义的TextView:/res/layout/text_view.xml:
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android=" http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="5dp"
android:background="@drawable/text_bg" >
</TextView>
<TextView xmlns:android=" http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="5dp"
android:background="@drawable/text_bg" >
</TextView>
然后就可以直接在MainActivity.java中使用这个容器FlowLayout,把他直接添加到MainActivity的layout文件中去。
MainActivity.java代码:
import android.os.Bundle;
import android.app.Activity;
import android.view.LayoutInflater;
import android.view.ViewGroup.MarginLayoutParams;
import android.widget.TextView;
public class MainActivity extends Activity {
private String[] mVals = new String[] { "优酷", "PPTV聚力", "暴风影音", "QQ控件", "微博", "Adobe", "凤凰新闻",
"淘宝", "京东", "一号店", "百度云盘", "酷我音乐", "网易新闻" };
private FlowLayout mFlowLayout;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mFlowLayout = (FlowLayout) findViewById(R.id.flow_layout);
initData();
}
private void initData(){
LayoutInflater mInflater = LayoutInflater.from(this);
for(int i = 0; i < mVals.length; i++){
TextView myText = (TextView) mInflater.inflate(R.layout.text_view, mFlowLayout, false);
myText.setText(mVals[i]);
// MarginLayoutParams lp = new MarginLayoutParams(MarginLayoutParams.WRAP_CONTENT, MarginLayoutParams.WRAP_CONTENT);
// mFlowLayout.addView(myText,lp);//这里注意,如果设置的自定义控件的margin没有效果的时候,请尝试使用这段代码,否则使用下一行
mFlowLayout.addView(myText);
}
}
}
至此,上述效果图就可以实现了
2715





