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