TableRow.java revision c0053223bedf33581b0830fb87be32c1f26e5372
1/*
2 * Copyright (C) 2007 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.widget;
18
19import android.content.Context;
20import android.content.res.TypedArray;
21import android.util.AttributeSet;
22import android.util.SparseIntArray;
23import android.view.Gravity;
24import android.view.View;
25import android.view.ViewDebug;
26import android.view.ViewGroup;
27
28
29/**
30 * <p>A layout that arranges its children horizontally. A TableRow should
31 * always be used as a child of a {@link android.widget.TableLayout}. If a
32 * TableRow's parent is not a TableLayout, the TableRow will behave as
33 * an horizontal {@link android.widget.LinearLayout}.</p>
34 *
35 * <p>The children of a TableRow do not need to specify the
36 * <code>layout_width</code> and <code>layout_height</code> attributes in the
37 * XML file. TableRow always enforces those values to be respectively
38 * {@link android.widget.TableLayout.LayoutParams#MATCH_PARENT} and
39 * {@link android.widget.TableLayout.LayoutParams#WRAP_CONTENT}.</p>
40 *
41 * <p>
42 * Also see {@link TableRow.LayoutParams android.widget.TableRow.LayoutParams}
43 * for layout attributes </p>
44 */
45public class TableRow extends LinearLayout {
46    private int mNumColumns = 0;
47    private int[] mColumnWidths;
48    private int[] mConstrainedColumnWidths;
49    private SparseIntArray mColumnToChildIndex;
50
51    private ChildrenTracker mChildrenTracker;
52
53    /**
54     * <p>Creates a new TableRow for the given context.</p>
55     *
56     * @param context the application environment
57     */
58    public TableRow(Context context) {
59        super(context);
60        initTableRow();
61    }
62
63    /**
64     * <p>Creates a new TableRow for the given context and with the
65     * specified set attributes.</p>
66     *
67     * @param context the application environment
68     * @param attrs a collection of attributes
69     */
70    public TableRow(Context context, AttributeSet attrs) {
71        super(context, attrs);
72        initTableRow();
73    }
74
75    private void initTableRow() {
76        OnHierarchyChangeListener oldListener = mOnHierarchyChangeListener;
77        mChildrenTracker = new ChildrenTracker();
78        if (oldListener != null) {
79            mChildrenTracker.setOnHierarchyChangeListener(oldListener);
80        }
81        super.setOnHierarchyChangeListener(mChildrenTracker);
82    }
83
84    /**
85     * {@inheritDoc}
86     */
87    @Override
88    public void setOnHierarchyChangeListener(OnHierarchyChangeListener listener) {
89        mChildrenTracker.setOnHierarchyChangeListener(listener);
90    }
91
92    /**
93     * <p>Collapses or restores a given column.</p>
94     *
95     * @param columnIndex the index of the column
96     * @param collapsed true if the column must be collapsed, false otherwise
97     * {@hide}
98     */
99    void setColumnCollapsed(int columnIndex, boolean collapsed) {
100        View child = getVirtualChildAt(columnIndex);
101        if (child != null) {
102            child.setVisibility(collapsed ? GONE : VISIBLE);
103        }
104    }
105
106    /**
107     * {@inheritDoc}
108     */
109    @Override
110    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
111        // enforce horizontal layout
112        measureHorizontal(widthMeasureSpec, heightMeasureSpec);
113    }
114
115    /**
116     * {@inheritDoc}
117     */
118    @Override
119    protected void onLayout(boolean changed, int l, int t, int r, int b) {
120        // enforce horizontal layout
121        layoutHorizontal();
122    }
123
124    /**
125     * {@inheritDoc}
126     */
127    @Override
128    public View getVirtualChildAt(int i) {
129        if (mColumnToChildIndex == null) {
130            mapIndexAndColumns();
131        }
132
133        final int deflectedIndex = mColumnToChildIndex.get(i, -1);
134        if (deflectedIndex != -1) {
135            return getChildAt(deflectedIndex);
136        }
137
138        return null;
139    }
140
141    /**
142     * {@inheritDoc}
143     */
144    @Override
145    public int getVirtualChildCount() {
146        if (mColumnToChildIndex == null) {
147            mapIndexAndColumns();
148        }
149        return mNumColumns;
150    }
151
152    private void mapIndexAndColumns() {
153        if (mColumnToChildIndex == null) {
154            int virtualCount = 0;
155            final int count = getChildCount();
156
157            mColumnToChildIndex = new SparseIntArray();
158            final SparseIntArray columnToChild = mColumnToChildIndex;
159
160            for (int i = 0; i < count; i++) {
161                final View child = getChildAt(i);
162                final LayoutParams layoutParams = (LayoutParams) child.getLayoutParams();
163
164                if (layoutParams.column >= virtualCount) {
165                    virtualCount = layoutParams.column;
166                }
167
168                for (int j = 0; j < layoutParams.span; j++) {
169                    columnToChild.put(virtualCount++, i);
170                }
171            }
172
173            mNumColumns = virtualCount;
174        }
175    }
176
177    /**
178     * {@inheritDoc}
179     */
180    @Override
181    int measureNullChild(int childIndex) {
182        return mConstrainedColumnWidths[childIndex];
183    }
184
185    /**
186     * {@inheritDoc}
187     */
188    @Override
189    void measureChildBeforeLayout(View child, int childIndex,
190            int widthMeasureSpec, int totalWidth,
191            int heightMeasureSpec, int totalHeight) {
192        if (mConstrainedColumnWidths != null) {
193            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
194
195            int measureMode = MeasureSpec.EXACTLY;
196            int columnWidth = 0;
197
198            final int span = lp.span;
199            final int[] constrainedColumnWidths = mConstrainedColumnWidths;
200            for (int i = 0; i < span; i++) {
201                columnWidth += constrainedColumnWidths[childIndex + i];
202            }
203
204            final int gravity = lp.gravity;
205            final boolean isHorizontalGravity = Gravity.isHorizontal(gravity);
206
207            if (isHorizontalGravity) {
208                measureMode = MeasureSpec.AT_MOST;
209            }
210
211            // no need to care about padding here,
212            // ViewGroup.getChildMeasureSpec() would get rid of it anyway
213            // because of the EXACTLY measure spec we use
214            int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
215                    Math.max(0, columnWidth - lp.leftMargin - lp.rightMargin), measureMode
216            );
217            int childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec,
218                    mPaddingTop + mPaddingBottom + lp.topMargin +
219                    lp .bottomMargin + totalHeight, lp.height);
220
221            child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
222
223            if (isHorizontalGravity) {
224                final int childWidth = child.getMeasuredWidth();
225                lp.mOffset[LayoutParams.LOCATION_NEXT] = columnWidth - childWidth;
226
227                final int layoutDirection = getResolvedLayoutDirection();
228                final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
229                switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
230                    case Gravity.LEFT:
231                        // don't offset on X axis
232                        break;
233                    case Gravity.RIGHT:
234                        lp.mOffset[LayoutParams.LOCATION] = lp.mOffset[LayoutParams.LOCATION_NEXT];
235                        break;
236                    case Gravity.CENTER_HORIZONTAL:
237                        lp.mOffset[LayoutParams.LOCATION] = lp.mOffset[LayoutParams.LOCATION_NEXT] / 2;
238                        break;
239                }
240            } else {
241                lp.mOffset[LayoutParams.LOCATION] = lp.mOffset[LayoutParams.LOCATION_NEXT] = 0;
242            }
243        } else {
244            // fail silently when column widths are not available
245            super.measureChildBeforeLayout(child, childIndex, widthMeasureSpec,
246                    totalWidth, heightMeasureSpec, totalHeight);
247        }
248    }
249
250    /**
251     * {@inheritDoc}
252     */
253    @Override
254    int getChildrenSkipCount(View child, int index) {
255        LayoutParams layoutParams = (LayoutParams) child.getLayoutParams();
256
257        // when the span is 1 (default), we need to skip 0 child
258        return layoutParams.span - 1;
259    }
260
261    /**
262     * {@inheritDoc}
263     */
264    @Override
265    int getLocationOffset(View child) {
266        return ((TableRow.LayoutParams) child.getLayoutParams()).mOffset[LayoutParams.LOCATION];
267    }
268
269    /**
270     * {@inheritDoc}
271     */
272    @Override
273    int getNextLocationOffset(View child) {
274        return ((TableRow.LayoutParams) child.getLayoutParams()).mOffset[LayoutParams.LOCATION_NEXT];
275    }
276
277    /**
278     * <p>Measures the preferred width of each child, including its margins.</p>
279     *
280     * @param widthMeasureSpec the width constraint imposed by our parent
281     *
282     * @return an array of integers corresponding to the width of each cell, or
283     *         column, in this row
284     * {@hide}
285     */
286    int[] getColumnsWidths(int widthMeasureSpec) {
287        final int numColumns = getVirtualChildCount();
288        if (mColumnWidths == null || numColumns != mColumnWidths.length) {
289            mColumnWidths = new int[numColumns];
290        }
291
292        final int[] columnWidths = mColumnWidths;
293
294        for (int i = 0; i < numColumns; i++) {
295            final View child = getVirtualChildAt(i);
296            if (child != null && child.getVisibility() != GONE) {
297                final LayoutParams layoutParams = (LayoutParams) child.getLayoutParams();
298                if (layoutParams.span == 1) {
299                    int spec;
300                    switch (layoutParams.width) {
301                        case LayoutParams.WRAP_CONTENT:
302                            spec = getChildMeasureSpec(widthMeasureSpec, 0, LayoutParams.WRAP_CONTENT);
303                            break;
304                        case LayoutParams.MATCH_PARENT:
305                            spec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
306                            break;
307                        default:
308                            spec = MeasureSpec.makeMeasureSpec(layoutParams.width, MeasureSpec.EXACTLY);
309                    }
310                    child.measure(spec, spec);
311
312                    final int width = child.getMeasuredWidth() + layoutParams.leftMargin +
313                            layoutParams.rightMargin;
314                    columnWidths[i] = width;
315                } else {
316                    columnWidths[i] = 0;
317                }
318            } else {
319                columnWidths[i] = 0;
320            }
321        }
322
323        return columnWidths;
324    }
325
326    /**
327     * <p>Sets the width of all of the columns in this row. At layout time,
328     * this row sets a fixed width, as defined by <code>columnWidths</code>,
329     * on each child (or cell, or column.)</p>
330     *
331     * @param columnWidths the fixed width of each column that this row must
332     *                     honor
333     * @throws IllegalArgumentException when columnWidths' length is smaller
334     *         than the number of children in this row
335     * {@hide}
336     */
337    void setColumnsWidthConstraints(int[] columnWidths) {
338        if (columnWidths == null || columnWidths.length < getVirtualChildCount()) {
339            throw new IllegalArgumentException(
340                    "columnWidths should be >= getVirtualChildCount()");
341        }
342
343        mConstrainedColumnWidths = columnWidths;
344    }
345
346    /**
347     * {@inheritDoc}
348     */
349    @Override
350    public LayoutParams generateLayoutParams(AttributeSet attrs) {
351        return new TableRow.LayoutParams(getContext(), attrs);
352    }
353
354    /**
355     * Returns a set of layout parameters with a width of
356     * {@link android.view.ViewGroup.LayoutParams#MATCH_PARENT},
357     * a height of {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT} and no spanning.
358     */
359    @Override
360    protected LinearLayout.LayoutParams generateDefaultLayoutParams() {
361        return new LayoutParams();
362    }
363
364    /**
365     * {@inheritDoc}
366     */
367    @Override
368    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
369        return p instanceof TableRow.LayoutParams;
370    }
371
372    /**
373     * {@inheritDoc}
374     */
375    @Override
376    protected LinearLayout.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
377        return new LayoutParams(p);
378    }
379
380    /**
381     * <p>Set of layout parameters used in table rows.</p>
382     *
383     * @see android.widget.TableLayout.LayoutParams
384     *
385     * @attr ref android.R.styleable#TableRow_Cell_layout_column
386     * @attr ref android.R.styleable#TableRow_Cell_layout_span
387     */
388    public static class LayoutParams extends LinearLayout.LayoutParams {
389        /**
390         * <p>The column index of the cell represented by the widget.</p>
391         */
392        @ViewDebug.ExportedProperty(category = "layout")
393        public int column;
394
395        /**
396         * <p>The number of columns the widgets spans over.</p>
397         */
398        @ViewDebug.ExportedProperty(category = "layout")
399        public int span;
400
401        private static final int LOCATION = 0;
402        private static final int LOCATION_NEXT = 1;
403
404        private int[] mOffset = new int[2];
405
406        /**
407         * {@inheritDoc}
408         */
409        public LayoutParams(Context c, AttributeSet attrs) {
410            super(c, attrs);
411
412            TypedArray a =
413                    c.obtainStyledAttributes(attrs,
414                            com.android.internal.R.styleable.TableRow_Cell);
415
416            column = a.getInt(com.android.internal.R.styleable.TableRow_Cell_layout_column, -1);
417            span = a.getInt(com.android.internal.R.styleable.TableRow_Cell_layout_span, 1);
418            if (span <= 1) {
419                span = 1;
420            }
421
422            a.recycle();
423        }
424
425        /**
426         * <p>Sets the child width and the child height.</p>
427         *
428         * @param w the desired width
429         * @param h the desired height
430         */
431        public LayoutParams(int w, int h) {
432            super(w, h);
433            column = -1;
434            span = 1;
435        }
436
437        /**
438         * <p>Sets the child width, height and weight.</p>
439         *
440         * @param w the desired width
441         * @param h the desired height
442         * @param initWeight the desired weight
443         */
444        public LayoutParams(int w, int h, float initWeight) {
445            super(w, h, initWeight);
446            column = -1;
447            span = 1;
448        }
449
450        /**
451         * <p>Sets the child width to {@link android.view.ViewGroup.LayoutParams}
452         * and the child height to
453         * {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT}.</p>
454         */
455        public LayoutParams() {
456            super(MATCH_PARENT, WRAP_CONTENT);
457            column = -1;
458            span = 1;
459        }
460
461        /**
462         * <p>Puts the view in the specified column.</p>
463         *
464         * <p>Sets the child width to {@link android.view.ViewGroup.LayoutParams#MATCH_PARENT}
465         * and the child height to
466         * {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT}.</p>
467         *
468         * @param column the column index for the view
469         */
470        public LayoutParams(int column) {
471            this();
472            this.column = column;
473        }
474
475        /**
476         * {@inheritDoc}
477         */
478        public LayoutParams(ViewGroup.LayoutParams p) {
479            super(p);
480        }
481
482        /**
483         * {@inheritDoc}
484         */
485        public LayoutParams(MarginLayoutParams source) {
486            super(source);
487        }
488
489        @Override
490        protected void setBaseAttributes(TypedArray a, int widthAttr, int heightAttr) {
491            // We don't want to force users to specify a layout_width
492            if (a.hasValue(widthAttr)) {
493                width = a.getLayoutDimension(widthAttr, "layout_width");
494            } else {
495                width = MATCH_PARENT;
496            }
497
498            // We don't want to force users to specify a layout_height
499            if (a.hasValue(heightAttr)) {
500                height = a.getLayoutDimension(heightAttr, "layout_height");
501            } else {
502                height = WRAP_CONTENT;
503            }
504        }
505    }
506
507    // special transparent hierarchy change listener
508    private class ChildrenTracker implements OnHierarchyChangeListener {
509        private OnHierarchyChangeListener listener;
510
511        private void setOnHierarchyChangeListener(OnHierarchyChangeListener listener) {
512            this.listener = listener;
513        }
514
515        public void onChildViewAdded(View parent, View child) {
516            // dirties the index to column map
517            mColumnToChildIndex = null;
518
519            if (this.listener != null) {
520                this.listener.onChildViewAdded(parent, child);
521            }
522        }
523
524        public void onChildViewRemoved(View parent, View child) {
525            // dirties the index to column map
526            mColumnToChildIndex = null;
527
528            if (this.listener != null) {
529                this.listener.onChildViewRemoved(parent, child);
530            }
531        }
532    }
533}
534