TableRow.java revision 980a938c1c9a6a5791a8240e5a1e6638ab28dc77
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.ViewGroup;
26import android.view.ViewDebug;
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                switch (gravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
228                    case Gravity.LEFT:
229                        // don't offset on X axis
230                        break;
231                    case Gravity.RIGHT:
232                        lp.mOffset[LayoutParams.LOCATION] = lp.mOffset[LayoutParams.LOCATION_NEXT];
233                        break;
234                    case Gravity.CENTER_HORIZONTAL:
235                        lp.mOffset[LayoutParams.LOCATION] = lp.mOffset[LayoutParams.LOCATION_NEXT] / 2;
236                        break;
237                }
238            } else {
239                lp.mOffset[LayoutParams.LOCATION] = lp.mOffset[LayoutParams.LOCATION_NEXT] = 0;
240            }
241        } else {
242            // fail silently when column widths are not available
243            super.measureChildBeforeLayout(child, childIndex, widthMeasureSpec,
244                    totalWidth, heightMeasureSpec, totalHeight);
245        }
246    }
247
248    /**
249     * {@inheritDoc}
250     */
251    @Override
252    int getChildrenSkipCount(View child, int index) {
253        LayoutParams layoutParams = (LayoutParams) child.getLayoutParams();
254
255        // when the span is 1 (default), we need to skip 0 child
256        return layoutParams.span - 1;
257    }
258
259    /**
260     * {@inheritDoc}
261     */
262    @Override
263    int getLocationOffset(View child) {
264        return ((TableRow.LayoutParams) child.getLayoutParams()).mOffset[LayoutParams.LOCATION];
265    }
266
267    /**
268     * {@inheritDoc}
269     */
270    @Override
271    int getNextLocationOffset(View child) {
272        return ((TableRow.LayoutParams) child.getLayoutParams()).mOffset[LayoutParams.LOCATION_NEXT];
273    }
274
275    /**
276     * <p>Measures the preferred width of each child, including its margins.</p>
277     *
278     * @param widthMeasureSpec the width constraint imposed by our parent
279     *
280     * @return an array of integers corresponding to the width of each cell, or
281     *         column, in this row
282     * {@hide}
283     */
284    int[] getColumnsWidths(int widthMeasureSpec) {
285        final int numColumns = getVirtualChildCount();
286        if (mColumnWidths == null || numColumns != mColumnWidths.length) {
287            mColumnWidths = new int[numColumns];
288        }
289
290        final int[] columnWidths = mColumnWidths;
291
292        for (int i = 0; i < numColumns; i++) {
293            final View child = getVirtualChildAt(i);
294            if (child != null && child.getVisibility() != GONE) {
295                final LayoutParams layoutParams = (LayoutParams) child.getLayoutParams();
296                if (layoutParams.span == 1) {
297                    int spec;
298                    switch (layoutParams.width) {
299                        case LayoutParams.WRAP_CONTENT:
300                            spec = getChildMeasureSpec(widthMeasureSpec, 0, LayoutParams.WRAP_CONTENT);
301                            break;
302                        case LayoutParams.MATCH_PARENT:
303                            spec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
304                            break;
305                        default:
306                            spec = MeasureSpec.makeMeasureSpec(layoutParams.width, MeasureSpec.EXACTLY);
307                    }
308                    child.measure(spec, spec);
309
310                    final int width = child.getMeasuredWidth() + layoutParams.leftMargin +
311                            layoutParams.rightMargin;
312                    columnWidths[i] = width;
313                } else {
314                    columnWidths[i] = 0;
315                }
316            } else {
317                columnWidths[i] = 0;
318            }
319        }
320
321        return columnWidths;
322    }
323
324    /**
325     * <p>Sets the width of all of the columns in this row. At layout time,
326     * this row sets a fixed width, as defined by <code>columnWidths</code>,
327     * on each child (or cell, or column.)</p>
328     *
329     * @param columnWidths the fixed width of each column that this row must
330     *                     honor
331     * @throws IllegalArgumentException when columnWidths' length is smaller
332     *         than the number of children in this row
333     * {@hide}
334     */
335    void setColumnsWidthConstraints(int[] columnWidths) {
336        if (columnWidths == null || columnWidths.length < getVirtualChildCount()) {
337            throw new IllegalArgumentException(
338                    "columnWidths should be >= getVirtualChildCount()");
339        }
340
341        mConstrainedColumnWidths = columnWidths;
342    }
343
344    /**
345     * {@inheritDoc}
346     */
347    @Override
348    public LayoutParams generateLayoutParams(AttributeSet attrs) {
349        return new TableRow.LayoutParams(getContext(), attrs);
350    }
351
352    /**
353     * Returns a set of layout parameters with a width of
354     * {@link android.view.ViewGroup.LayoutParams#MATCH_PARENT},
355     * a height of {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT} and no spanning.
356     */
357    @Override
358    protected LinearLayout.LayoutParams generateDefaultLayoutParams() {
359        return new LayoutParams();
360    }
361
362    /**
363     * {@inheritDoc}
364     */
365    @Override
366    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
367        return p instanceof TableRow.LayoutParams;
368    }
369
370    /**
371     * {@inheritDoc}
372     */
373    @Override
374    protected LinearLayout.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
375        return new LayoutParams(p);
376    }
377
378    /**
379     * <p>Set of layout parameters used in table rows.</p>
380     *
381     * @see android.widget.TableLayout.LayoutParams
382     *
383     * @attr ref android.R.styleable#TableRow_Cell_layout_column
384     * @attr ref android.R.styleable#TableRow_Cell_layout_span
385     */
386    public static class LayoutParams extends LinearLayout.LayoutParams {
387        /**
388         * <p>The column index of the cell represented by the widget.</p>
389         */
390        @ViewDebug.ExportedProperty
391        public int column;
392
393        /**
394         * <p>The number of columns the widgets spans over.</p>
395         */
396        @ViewDebug.ExportedProperty
397        public int span;
398
399        private static final int LOCATION = 0;
400        private static final int LOCATION_NEXT = 1;
401
402        private int[] mOffset = new int[2];
403
404        /**
405         * {@inheritDoc}
406         */
407        public LayoutParams(Context c, AttributeSet attrs) {
408            super(c, attrs);
409
410            TypedArray a =
411                    c.obtainStyledAttributes(attrs,
412                            com.android.internal.R.styleable.TableRow_Cell);
413
414            column = a.getInt(com.android.internal.R.styleable.TableRow_Cell_layout_column, -1);
415            span = a.getInt(com.android.internal.R.styleable.TableRow_Cell_layout_span, 1);
416            if (span <= 1) {
417                span = 1;
418            }
419
420            a.recycle();
421        }
422
423        /**
424         * <p>Sets the child width and the child height.</p>
425         *
426         * @param w the desired width
427         * @param h the desired height
428         */
429        public LayoutParams(int w, int h) {
430            super(w, h);
431            column = -1;
432            span = 1;
433        }
434
435        /**
436         * <p>Sets the child width, height and weight.</p>
437         *
438         * @param w the desired width
439         * @param h the desired height
440         * @param initWeight the desired weight
441         */
442        public LayoutParams(int w, int h, float initWeight) {
443            super(w, h, initWeight);
444            column = -1;
445            span = 1;
446        }
447
448        /**
449         * <p>Sets the child width to {@link android.view.ViewGroup.LayoutParams}
450         * and the child height to
451         * {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT}.</p>
452         */
453        public LayoutParams() {
454            super(MATCH_PARENT, WRAP_CONTENT);
455            column = -1;
456            span = 1;
457        }
458
459        /**
460         * <p>Puts the view in the specified column.</p>
461         *
462         * <p>Sets the child width to {@link android.view.ViewGroup.LayoutParams#MATCH_PARENT}
463         * and the child height to
464         * {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT}.</p>
465         *
466         * @param column the column index for the view
467         */
468        public LayoutParams(int column) {
469            this();
470            this.column = column;
471        }
472
473        /**
474         * {@inheritDoc}
475         */
476        public LayoutParams(ViewGroup.LayoutParams p) {
477            super(p);
478        }
479
480        /**
481         * {@inheritDoc}
482         */
483        public LayoutParams(MarginLayoutParams source) {
484            super(source);
485        }
486
487        @Override
488        protected void setBaseAttributes(TypedArray a, int widthAttr, int heightAttr) {
489            // We don't want to force users to specify a layout_width
490            if (a.hasValue(widthAttr)) {
491                width = a.getLayoutDimension(widthAttr, "layout_width");
492            } else {
493                width = MATCH_PARENT;
494            }
495
496            // We don't want to force users to specify a layout_height
497            if (a.hasValue(heightAttr)) {
498                height = a.getLayoutDimension(heightAttr, "layout_height");
499            } else {
500                height = WRAP_CONTENT;
501            }
502        }
503    }
504
505    // special transparent hierarchy change listener
506    private class ChildrenTracker implements OnHierarchyChangeListener {
507        private OnHierarchyChangeListener listener;
508
509        private void setOnHierarchyChangeListener(OnHierarchyChangeListener listener) {
510            this.listener = listener;
511        }
512
513        public void onChildViewAdded(View parent, View child) {
514            // dirties the index to column map
515            mColumnToChildIndex = null;
516
517            if (this.listener != null) {
518                this.listener.onChildViewAdded(parent, child);
519            }
520        }
521
522        public void onChildViewRemoved(View parent, View child) {
523            // dirties the index to column map
524            mColumnToChildIndex = null;
525
526            if (this.listener != null) {
527                this.listener.onChildViewRemoved(parent, child);
528            }
529        }
530    }
531}
532