仿淘宝,自定义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
- 仿淘宝,自定义ViewGroup实现自动换行布局
- 自定义ViewGroup实现自动换行---FlowLayout
- 自定义布局实现自动换行
- android之自定义ViewGroup和自动换行的布局的实现
- android之自定义ViewGroup和自动换行的布局的实现
- android之自定义ViewGroup和自动换行的布局的实现
- android之自定义ViewGroup和自动换行的布局的实现
- android之自定义ViewGroup和自动换行的布局的实现
- android之自定义ViewGroup和自动换行的布局的实现
- android之自定义ViewGroup和自动换行的布局的实现
- android之自定义ViewGroup和自动换行的布局的实现
- android之自定义ViewGroup和自动换行的布局的实现
- Android自定义ViewGroup自动换行实现滑动任意布局及事件处理效果
- android之自定义ViewGroup和自动换行的布局的实现
- android之自定义ViewGroup和自动换行的布局的实现
- 基于ViewGroup自定义自动换行的布局的实现(用于备忘)
- android之自定义ViewGroup和自动换行的布局的实现
- android之自定义ViewGroup和自动换行的布局的实现
- MJExtension字典和模型之间相互转换的轻量级框架的使用实例
- New package not yet registered with the system. Waiting 3 seconds before next attempt
- Java第四周常用类与集合框架之接口+抽象类9-9
- linux 下面清除历史纪录的方法
- Python 代码风格和PEP8
- 仿淘宝,自定义ViewGroup实现自动换行布局
- vim配置YCM
- Android的Style的使用和用处
- 第一次只出现一次的字符
- USACO 1.1 Friday the Thirteenth
- Netsh命令详解
- Spring学习笔记(八):数据库连接池的小例子
- HDU 1711 Number Sequence(kmp)
- C++中int与char*、int与Byte、double与char*的相互装换