在我们开发过程中会经常遇见一些客户要求但是Android系统又不提供的效果,这时我们只能自己动手去实现它,或者从网络上借鉴他人的资源,本着用别人不如自己会做的心态,在此我总结了一下Android中如何实现自动换行的LinearLayout。
在本文中,说是LinearLayout其实是继承自GroupView,在这里主要重写了两个方法,onMeasure、onLayout方法,下面我对此加以介绍。(代码中使用了AttributeSet,由于时间问题不再予以介绍)。
1. onMeasure是干什么的?
在ViewGroup的创建过程中,onMeasure是在onLayout之前的,所以在此先对onMeasure进行介绍,onMeasure方法是计算子控件与父控件在屏幕中所占长宽大小的,onMeasure传入两个参数——widthMeasureSpec和heightMeasureSpec. 这两个参数指明控件可获得的空间以及关于这个空间描述的元数据.
1
2
3
4int 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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245public 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
1
2
3
4
5
6
7
8
9
10
11
12
13<?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
1
2
3
4
5
6<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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30public 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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32<?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>
运行效果图如下:
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持靠谱客。
最后
以上就是直率发夹最近收集整理的关于Android LinearLayout实现自动换行效果的全部内容,更多相关Android内容请搜索靠谱客的其他文章。
发表评论 取消回复