仿淘宝,自定义ViewGroup实现自动换行布局

来源:互联网 发布:sopcast2016地址源码 编辑:程序博客网 时间:2024/06/05 15:30

先来一张淘宝的效果图和自定义实现的效果图对比


淘宝图


自定义效果



效果实现的原理很简单,继承自ViewGroup来实现一个自定义的布局容器,主要逻辑在onMeasure和onLayout当中进行处理。


onMeasure: 对所有子控件的宽度和高度进行测量,并且根据子控件的个数,来设置布局本身的高度。

@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {// 测量出所有的childView的宽和高measureChildren(widthMeasureSpec, heightMeasureSpec);// 按全部控件的大小测量出实际的控件高度int height = getWrapHeight(widthMeasureSpec, heightMeasureSpec);setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), height);}/** * 返回计算的包含内容的布局高度 *  * @return */private int getWrapHeight(int widthMeasureSpec, int heightMeasureSpec) {WrapLayoutParams parmas = null;int row = 1; // 总行数(默认是一行)int maxWidth = MeasureSpec.getSize(widthMeasureSpec); // 当前容器所拥有的最大宽度int hlength = 0;// 当前行的水平已占宽度int vlength = 0; // 当前控件的高度(返回结果)for (int i = 0; i < getChildCount(); i++) {View child = getChildAt(i);parmas = (WrapLayoutParams) child.getLayoutParams();int childWidth = child.getMeasuredWidth() + parmas.leftMargin+ parmas.rightMargin; // 子控件的左右边距加上宽度int childHeight = child.getMeasuredHeight() + parmas.topMargin+ parmas.bottomMargin;// 子控件的上下边距加上高度hlength += childWidth;// 水平轴上已经使用的宽度if (hlength > maxWidth) { // 换行了行数就自增加1row++;hlength = childWidth; // 当前控件被分配到下一行,从新计算}// ((上边距+下边距) + 子控件的高度 )* 总行数vlength = row * childHeight;}return vlength;}
主要逻辑已在代码中注释明白,布局容器本身的高度是由控件的个数来决定的,在onMeasure当中不但要对子控件的宽高进行测量,还需要根据子控件的数据来测量本身的宽和高,代码中并未对不同的布局方式(wrap_content, match_parent)做出处理,默认宽度使用父控件传递下来的宽度,高度则是根据所包含子控件的高度来自适应(wrap_content)大小。如果需要处理,只需根据widthMeasureSpec 和 heightMeasureSpec 的Mode来进行相应处理即可。

自定义ViewGroup的时候,想要子控件的Margin属性生效,需要重写下面这个方法:

@Overridepublic LayoutParams generateLayoutParams(AttributeSet attrs) {// TODO Auto-generated method stubreturn new WrapLayoutParams(getContext(), attrs);}/** *  * @ClassName: WrapLayoutParams * @Description: TODO 布局参数对象 * @date 2015-9-8 下午4:18:30 *  */public class WrapLayoutParams extends ViewGroup.MarginLayoutParams {public WrapLayoutParams(Context ctxt, AttributeSet attrs) {super(ctxt, attrs);}}


ViewGroup和LayoutParams之间的关系

当在LinearLayout中写childView的时候,可以写layout_gravity,layout_weight属性;

在RelativeLayout中的childView有layout_centerInParent属性,却没有layout_gravity,layout_weight,这是为什么呢?

这是因为每个ViewGroup需要指定一个LayoutParams,用于确定支持childView支持哪些属性,比如LinearLayout指定LinearLayout.LayoutParams等。如果大家去看LinearLayout的源码,会发现其内部定义了LinearLayout.LayoutParams,在此类中,你可以发现weight和gravity的身影。

onLayout: 对所包含的所有子控件进行定位,指定子控件在哪个位置绘制自己。

@Overrideprotected void onLayout(boolean changed, int l, int t, int r, int b) {Log.d("---onLayout---", "l:" + l + "t:" + t + "r:" + r + "b:" + b);// TODO Auto-generated method stubWrapLayoutParams params = null;int row = 0; // 总行数int hlength = l; // 当前行的水平已占宽度int vlength = t; // 当前垂直布局已占用的高度for (int i = 0; i < getChildCount(); i++) {View child = getChildAt(i);params = (WrapLayoutParams) child.getLayoutParams();// 计算一个控件的有效宽度和高度int childWidth = child.getMeasuredWidth() + params.leftMargin+ params.rightMargin;int childHeight = child.getMeasuredHeight() + params.topMargin+ params.bottomMargin;// 定位水平光标位置if (hlength + childWidth > r) {// 换行了行数就自增加1hlength = l; // 当前控件被分配到下一行,从新计算row++;}// 定位垂直光标位置vlength = row * childHeight;// 先把控件布局到光标位置child.layout(hlength + params.leftMargin, vlength, hlength+ childWidth, vlength + childHeight);// 然后从新定位光标位置hlength += childWidth;}}

代码注释也已经写的很详细,主要就是检测当前所放置的行是否还可以放置当前子控件,超出宽度范围,则换行放置。

下面给出完整源码:

/** *  * @ClassName: WrapLayout * @Description: TODO 自动换行的Layout * @date 2015-9-6 上午11:23:47 *  */public class WrapLayout extends ViewGroup {public WrapLayout(Context context) {super(context);}public WrapLayout(Context context, AttributeSet attrs) {super(context, attrs);}public WrapLayout(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);}@SuppressLint("NewApi")public WrapLayout(Context context, AttributeSet attrs, int defStyleAttr,int defStyleRes) {super(context, attrs, defStyleAttr, defStyleRes);}@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {// 测量出所有的childView的宽和高measureChildren(widthMeasureSpec, heightMeasureSpec);// 按全部控件的大小测量出实际的控件高度int height = getWrapHeight(widthMeasureSpec, heightMeasureSpec);setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), height);}/** * 返回计算的包含内容的布局高度 *  * @return */private int getWrapHeight(int widthMeasureSpec, int heightMeasureSpec) {WrapLayoutParams parmas = null;int row = 1; // 总行数(默认是一行)int maxWidth = MeasureSpec.getSize(widthMeasureSpec); // 当前控件所拥有的最大宽度int hlength = 0;// 当前行的水平已占宽度int vlength = 0; // 当前控件的高度(返回结果)for (int i = 0; i < getChildCount(); i++) {View child = getChildAt(i);parmas = (WrapLayoutParams) child.getLayoutParams();int childWidth = child.getMeasuredWidth() + parmas.leftMargin+ parmas.rightMargin; // 子控件的左右边距加上宽度int childHeight = child.getMeasuredHeight() + parmas.topMargin+ parmas.bottomMargin;// 子控件的上下边距加上高度hlength += childWidth;// 水平轴上已经使用的宽度if (hlength > maxWidth) { // 换行了行数就自增加1row++;hlength = childWidth; // 当前控件被分配到下一行,从新计算}// ((上边距+下边距) + 子控件的高度 )* 总行数vlength = row * childHeight;}return vlength;}@Overrideprotected void onLayout(boolean changed, int l, int t, int r, int b) {Log.d("---onLayout---", "l:" + l + "t:" + t + "r:" + r + "b:" + b);// TODO Auto-generated method stubWrapLayoutParams params = null;int row = 0; // 总行数int hlength = l; // 当前行的水平已占宽度int vlength = t; // 当前垂直布局已占用的高度for (int i = 0; i < getChildCount(); i++) {View child = getChildAt(i);params = (WrapLayoutParams) child.getLayoutParams();// 计算一个控件的有效宽度和高度int childWidth = child.getMeasuredWidth() + params.leftMargin+ params.rightMargin;int childHeight = child.getMeasuredHeight() + params.topMargin+ params.bottomMargin;// 定位水平光标位置if (hlength + childWidth > r) {// 换行了行数就自增加1hlength = l; // 当前控件被分配到下一行,从新计算row++;}// 定位垂直光标位置vlength = row * childHeight;// 先把控件布局到光标位置child.layout(hlength + params.leftMargin, vlength, hlength+ childWidth, vlength + childHeight);// 然后从新定位光标位置hlength += childWidth;}}@Overridepublic LayoutParams generateLayoutParams(AttributeSet attrs) {// TODO Auto-generated method stubreturn new WrapLayoutParams(getContext(), attrs);}/** *  * @ClassName: WrapLayoutParams * @Description: TODO 布局参数对象 * @date 2015-9-8 下午4:18:30 *  */public class WrapLayoutParams extends ViewGroup.MarginLayoutParams {public WrapLayoutParams(Context ctxt, AttributeSet attrs) {super(ctxt, attrs);}}}

测试界面

public class MainActivity extends Activity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);String strs[] = { "世界基于MIUI", "阿里巴巴", "天猫", "亚马孙", "京东", "百度", "奇虎360","微软", "Google", "雅虎", "华为", "小米", "中兴" };WrapLayout layout = (WrapLayout) findViewById(R.id.wlayout);LayoutInflater inflater = LayoutInflater.from(this);for (int i = 0; i < strs.length; i++) {LinearLayout item = (LinearLayout) inflater.inflate(R.layout.item_taobao, layout, false);TextView tValue = (TextView) item.findViewById(R.id.tv_value);tValue.setText(strs[i]);layout.addView(item);}}}

测试主布局界面 activity_main:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:tools="http://schemas.android.com/tools"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:orientation="vertical" >    <widgets.WrapLayout        android:id="@+id/wlayout"        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:layout_marginTop="20dp"        android:background="@android:color/darker_gray" >    </widgets.WrapLayout></LinearLayout>

子布局项 item_taobao:

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="wrap_content"    android:layout_height="wrap_content"    android:gravity="center_vertical"    android:layout_margin="5dp"    android:orientation="horizontal" >    <ImageView        android:id="@+id/iv_icon"        android:layout_width="13dp"        android:layout_height="13dp"        android:scaleType="fitXY"        android:src="@drawable/fav_item_icon_selected" />    <TextView        android:layout_marginLeft="2dp"        android:id="@+id/tv_value"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:text="集分宝"        android:textColor="@android:color/black"        android:textSize="13sp" /></LinearLayout>

OK,到此结束,有问题欢迎交流指正。

0 0
原创粉丝点击