Android LinearLayout实现自动换行效果
在我们开发过程中会经常遇见一些客户要求但是Android系统又不提供的效果,这时我们只能自己动手去实现它,或者从网络上借鉴他人的资源,本着用别人不如自己会做的心态,在此我总结了一下Android中如何实现自动换行的LinearLayout。
在本文中,说是LinearLayout其实是继承自GroupView,在这里主要重写了两个方法,onMeasure、onLayout方法,下面我对此加以介绍。(代码中使用了AttributeSet,由于时间问题不再予以介绍)。
1. onMeasure是干什么的?
在ViewGroup的创建过程中,onMeasure是在onLayout之前的,所以在此先对onMeasure进行介绍,onMeasure方法是计算子控件与父控件在屏幕中所占长宽大小的,onMeasure传入两个参数――widthMeasureSpec和heightMeasureSpec. 这两个参数指明控件可获得的空间以及关于这个空间描述的元数据.
int withMode = MeasureSpec.getMode(widthMeasureSpec); int withSize = MeasureSpec.getSize(widthMeasureSpec); int heightMode = MeasureSpec.getMode(heightMeasureSpec); int heightSize = MeasureSpec.getSize(heightMeasureSpec);
Mode有3种模式分别是UNSPECIFIED, EXACTLY和AT_MOST,如果是AT_MOST,Size代表的是最大可获得的空间;如果是EXACTLY,Size代表的是精确的尺寸;如果是UNSPECIFIED,就是你想要多少就有多少。经过代码测试就知道,当我们设置width或height为fill_parent时,容器在布局时调用子 view的measure方法传入的模式是EXACTLY,因为子view会占据剩余容器的空间,所以它大小是确定的。而当设置为 wrap_content时,容器传进去的是AT_MOST, 表示子view的大小最多是多少,这样子view会根据这个上限来设置自己的尺寸。当子view的大小设置为精确值时,容器传入的是EXACTLY。
2. onLayout是干什么的?
与onMesaure相比,onLayout更加容易理解,它的作用就是调座位,就是把所有的子View根据不同的需要,通过View. layout(int l, int t, int r, int b)方法指定它所在的位置。
3. 解决问题
只要对onMeasure和onLayout加以理解,对于该篇所要实现的功能就不再难以实现,下面贴上代码,并在代码中讲解。
WaroLinearLayout.java
public class WarpLinearLayout extends ViewGroup { private Type mType; private List<WarpLine> mWarpLineGroup; public WarpLinearLayout(Context context) { this(context, null); } public WarpLinearLayout(Context context, AttributeSet attrs) { this(context, attrs, R.style.WarpLinearLayoutDefault); } public WarpLinearLayout(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); mType = new Type(context, attrs); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int withMode = MeasureSpec.getMode(widthMeasureSpec); int withSize = MeasureSpec.getSize(widthMeasureSpec); int heightMode = MeasureSpec.getMode(heightMeasureSpec); int heightSize = MeasureSpec.getSize(heightMeasureSpec); int with = 0; int height = 0; int childCount = getChildCount(); /** * 在调用childView。getMeasre之前必须先调用该行代码,用于对子View大小的测量 */ measureChildren(widthMeasureSpec, heightMeasureSpec); /** * 计算宽度 */ switch (withMode) { case MeasureSpec.EXACTLY: with = withSize; break; case MeasureSpec.AT_MOST: for (int i = 0; i < childCount; i++) { if (i != 0) { with += mType.horizontal_Space; } with += getChildAt(i).getMeasuredWidth(); } with += getPaddingLeft() + getPaddingRight(); with = with > withSize ? withSize : with; break; case MeasureSpec.UNSPECIFIED: for (int i = 0; i < childCount; i++) { if (i != 0) { with += mType.horizontal_Space; } with += getChildAt(i).getMeasuredWidth(); } with += getPaddingLeft() + getPaddingRight(); break; default: with = withSize; break; } /** * 根据计算出的宽度,计算出所需要的行数 */ WarpLine warpLine = new WarpLine(); /** * 不能够在定义属性时初始化,因为onMeasure方法会多次调用 */ mWarpLineGroup = new ArrayList<WarpLine>(); for (int i = 0; i < childCount; i++) { if (warpLine.lineWidth + getChildAt(i).getMeasuredWidth() + mType.horizontal_Space > with) { if (warpLine.lineView.size() == 0) { warpLine.addView(getChildAt(i)); mWarpLineGroup.add(warpLine); warpLine = new WarpLine(); } else { mWarpLineGroup.add(warpLine); warpLine = new WarpLine(); warpLine.addView(getChildAt(i)); } } else { warpLine.addView(getChildAt(i)); } } /** * 添加最后一行 */ if (warpLine.lineView.size() > 0 && !mWarpLineGroup.contains(warpLine)) { mWarpLineGroup.add(warpLine); } /** * 计算宽度 */ height = getPaddingTop() + getPaddingBottom(); for (int i = 0; i < mWarpLineGroup.size(); i++) { if (i != 0) { height += mType.vertical_Space; } height += mWarpLineGroup.get(i).height; } switch (heightMode) { case MeasureSpec.EXACTLY: height = heightSize; break; case MeasureSpec.AT_MOST: height = height > heightSize ? heightSize : height; break; case MeasureSpec.UNSPECIFIED: break; default: break; } setMeasuredDimension(with, height); } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { t = getPaddingTop(); for (int i = 0; i < mWarpLineGroup.size(); i++) { int left = getPaddingLeft(); WarpLine warpLine = mWarpLineGroup.get(i); int lastWidth = getMeasuredWidth() - warpLine.lineWidth; for (int j = 0; j < warpLine.lineView.size(); j++) { View view = warpLine.lineView.get(j); if (isFull()) {//需要充满当前行时 view.layout(left, t, left + view.getMeasuredWidth() + lastWidth / warpLine.lineView.size(), t + view.getMeasuredHeight()); left += view.getMeasuredWidth() + mType.horizontal_Space + lastWidth / warpLine.lineView.size(); } else { switch (getGrivate()) { case 0://右对齐 view.layout(left + lastWidth, t, left + lastWidth + view.getMeasuredWidth(), t + view.getMeasuredHeight()); break; case 2://居中对齐 view.layout(left + lastWidth / 2, t, left + lastWidth / 2 + view.getMeasuredWidth(), t + view.getMeasuredHeight()); break; default://左对齐 view.layout(left, t, left + view.getMeasuredWidth(), t + view.getMeasuredHeight()); break; } left += view.getMeasuredWidth() + mType.horizontal_Space; } } t += warpLine.height + mType.vertical_Space; } } /** * 用于存放一行子View */ private final class WarpLine { private List<View> lineView = new ArrayList<View>(); /** * 当前行中所需要占用的宽度 */ private int lineWidth = getPaddingLeft() + getPaddingRight(); /** * 该行View中所需要占用的最大高度 */ private int height = 0; private void addView(View view) { if (lineView.size() != 0) { lineWidth += mType.horizontal_Space; } height = height > view.getMeasuredHeight() ? height : view.getMeasuredHeight(); lineWidth += view.getMeasuredWidth(); lineView.add(view); } } /** * 对样式的初始化 */ private final static class Type { /* *对齐方式 right 0,left 1,center 2 */ private int grivate; /** * 水平间距,单位px */ private float horizontal_Space; /** * 垂直间距,单位px */ private float vertical_Space; /** * 是否自动填满 */ private boolean isFull; Type(Context context, AttributeSet attrs) { if (attrs == null) { return; } TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.WarpLinearLayout); grivate = typedArray.getInt(R.styleable.WarpLinearLayout_grivate, grivate); horizontal_Space = typedArray.getDimension(R.styleable.WarpLinearLayout_horizontal_Space, horizontal_Space); vertical_Space = typedArray.getDimension(R.styleable.WarpLinearLayout_vertical_Space, vertical_Space); isFull = typedArray.getBoolean(R.styleable.WarpLinearLayout_isFull, isFull); } } public int getGrivate() { return mType.grivate; } public float getHorizontal_Space() { return mType.horizontal_Space; } public float getVertical_Space() { return mType.vertical_Space; } public boolean isFull() { return mType.isFull; } public void setGrivate(int grivate) { mType.grivate = grivate; } public void setHorizontal_Space(float horizontal_Space) { mType.horizontal_Space = horizontal_Space; } public void setVertical_Space(float vertical_Space) { mType.vertical_Space = vertical_Space; } public void setIsFull(boolean isFull) { mType.isFull = isFull; } /** * 每行子View的对齐方式 */ public final static class Gravite { public final static int RIGHT = 0; public final static int LEFT = 1; public final static int CENTER = 2; } }
attrs.xml
<?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name="WarpLinearLayout"> <attr name="grivate" format="enum"><!--对齐方式 !--> <enum name="right" value="0"></enum> <enum name="left" value="1"></enum> <enum name="center" value="2"></enum> </attr> <attr name="horizontal_Space" format="dimension"></attr> <attr name="vertical_Space" format="dimension"></attr> <attr name="isFull" format="boolean"></attr> </declare-styleable> </resources>
WarpLinearLayoutDefault
<style name="WarpLinearLayoutDefault"> <item name="grivate">left</item> <item name="horizontal_Space">20dp</item> <item name="vertical_Space">20dp</item> <item name="isFull">false</item> </style>
MainActivity.java
public class MainActivity extends Activity { private Button btn; private WarpLinearLayout warpLinearLayout; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); btn = (Button) findViewById(R.id.btn); warpLinearLayout = (WarpLinearLayout) findViewById(R.id.warpLinearLayout); btn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { int n = new Random().nextInt(10) + 5; StringBuffer stringBuffer = new StringBuffer(); Random random = new Random(); Log.i("WarpLinearLayout","n="+n); for (int i = 0; i < n; i++) { stringBuffer.append((char)(65+random.nextInt(26))); Log.i("WarpLinearLayout", "StringBuffer=" + stringBuffer.toString()); } TextView tv = new TextView(MainActivity.this); tv.setText(stringBuffer.toString()+"000"); tv.setBackgroundResource(R.drawable.radius_backgroup_yellow); tv.setPadding(10,10,10,10); warpLinearLayout.addView(tv); } }); } }
activity_main.xml
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" tools:context=".MainActivity"> <Button android:id="@+id/btn" android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center" android:text="add" android:textSize="20dp" /> <com.example.customview.viewgroup.WarpLinearLayout android:id="@+id/warpLinearLayout" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_below="@id/btn" android:background="#FF00FF00" android:padding="10dp" app:grivate="right" app:horizontal_Space="10dp" app:isFull="false" app:vertical_Space="10dp"></com.example.customview.viewgroup.WarpLinearLayout> </RelativeLayout>
运行效果图如下: