From a35879c633690f445b8b62fd3da51a236458bd6c Mon Sep 17 00:00:00 2001 From: Julian Winkler Date: Fri, 1 Sep 2023 13:18:02 +0200 Subject: [PATCH] RelativeLayout: copy AOSP implementation As we already support custom ViewGroups, there is no need to reimplement this highly complex class. --- meson.build | 1 - .../android_widget_RelativeLayout.h | 229 --- .../widgets/android_widget_RelativeLayout.c | 32 - .../android/widget/RelativeLayout.java | 1694 ++++++++++++++++- 4 files changed, 1673 insertions(+), 283 deletions(-) delete mode 100644 src/api-impl-jni/generated_headers/android_widget_RelativeLayout.h delete mode 100644 src/api-impl-jni/widgets/android_widget_RelativeLayout.c diff --git a/meson.build b/meson.build index 034ab871..e7114698 100644 --- a/meson.build +++ b/meson.build @@ -74,7 +74,6 @@ libtranslationlayer_so = shared_library('translation_layer_main', [ 'src/api-impl-jni/widgets/android_widget_Button.c', 'src/api-impl-jni/widgets/android_widget_EditText.c', 'src/api-impl-jni/widgets/android_widget_ImageButton.c', - 'src/api-impl-jni/widgets/android_widget_RelativeLayout.c', 'src/api-impl-jni/widgets/android_widget_ScrollView.c', 'src/api-impl-jni/widgets/android_opengl_GLSurfaceView.c', 'src/api-impl-jni/widgets/android_widget_ImageView.c', diff --git a/src/api-impl-jni/generated_headers/android_widget_RelativeLayout.h b/src/api-impl-jni/generated_headers/android_widget_RelativeLayout.h deleted file mode 100644 index b90e01c9..00000000 --- a/src/api-impl-jni/generated_headers/android_widget_RelativeLayout.h +++ /dev/null @@ -1,229 +0,0 @@ -/* DO NOT EDIT THIS FILE - it is machine generated */ -#include -/* Header for class android_widget_RelativeLayout */ - -#ifndef _Included_android_widget_RelativeLayout -#define _Included_android_widget_RelativeLayout -#ifdef __cplusplus -extern "C" { -#endif -#undef android_widget_RelativeLayout_NO_ID -#define android_widget_RelativeLayout_NO_ID -1L -#undef android_widget_RelativeLayout_NOT_FOCUSABLE -#define android_widget_RelativeLayout_NOT_FOCUSABLE 0L -#undef android_widget_RelativeLayout_FOCUSABLE -#define android_widget_RelativeLayout_FOCUSABLE 1L -#undef android_widget_RelativeLayout_FOCUSABLE_MASK -#define android_widget_RelativeLayout_FOCUSABLE_MASK 1L -#undef android_widget_RelativeLayout_FITS_SYSTEM_WINDOWS -#define android_widget_RelativeLayout_FITS_SYSTEM_WINDOWS 2L -#undef android_widget_RelativeLayout_VISIBLE -#define android_widget_RelativeLayout_VISIBLE 0L -#undef android_widget_RelativeLayout_INVISIBLE -#define android_widget_RelativeLayout_INVISIBLE 4L -#undef android_widget_RelativeLayout_GONE -#define android_widget_RelativeLayout_GONE 8L -#undef android_widget_RelativeLayout_VISIBILITY_MASK -#define android_widget_RelativeLayout_VISIBILITY_MASK 12L -#undef android_widget_RelativeLayout_ENABLED -#define android_widget_RelativeLayout_ENABLED 0L -#undef android_widget_RelativeLayout_DISABLED -#define android_widget_RelativeLayout_DISABLED 32L -#undef android_widget_RelativeLayout_ENABLED_MASK -#define android_widget_RelativeLayout_ENABLED_MASK 32L -#undef android_widget_RelativeLayout_WILL_NOT_DRAW -#define android_widget_RelativeLayout_WILL_NOT_DRAW 128L -#undef android_widget_RelativeLayout_DRAW_MASK -#define android_widget_RelativeLayout_DRAW_MASK 128L -#undef android_widget_RelativeLayout_SCROLLBARS_NONE -#define android_widget_RelativeLayout_SCROLLBARS_NONE 0L -#undef android_widget_RelativeLayout_SCROLLBARS_HORIZONTAL -#define android_widget_RelativeLayout_SCROLLBARS_HORIZONTAL 256L -#undef android_widget_RelativeLayout_SCROLLBARS_VERTICAL -#define android_widget_RelativeLayout_SCROLLBARS_VERTICAL 512L -#undef android_widget_RelativeLayout_SCROLLBARS_MASK -#define android_widget_RelativeLayout_SCROLLBARS_MASK 768L -#undef android_widget_RelativeLayout_FILTER_TOUCHES_WHEN_OBSCURED -#define android_widget_RelativeLayout_FILTER_TOUCHES_WHEN_OBSCURED 1024L -#undef android_widget_RelativeLayout_OPTIONAL_FITS_SYSTEM_WINDOWS -#define android_widget_RelativeLayout_OPTIONAL_FITS_SYSTEM_WINDOWS 2048L -#undef android_widget_RelativeLayout_FADING_EDGE_NONE -#define android_widget_RelativeLayout_FADING_EDGE_NONE 0L -#undef android_widget_RelativeLayout_FADING_EDGE_HORIZONTAL -#define android_widget_RelativeLayout_FADING_EDGE_HORIZONTAL 4096L -#undef android_widget_RelativeLayout_FADING_EDGE_VERTICAL -#define android_widget_RelativeLayout_FADING_EDGE_VERTICAL 8192L -#undef android_widget_RelativeLayout_FADING_EDGE_MASK -#define android_widget_RelativeLayout_FADING_EDGE_MASK 12288L -#undef android_widget_RelativeLayout_CLICKABLE -#define android_widget_RelativeLayout_CLICKABLE 16384L -#undef android_widget_RelativeLayout_DRAWING_CACHE_ENABLED -#define android_widget_RelativeLayout_DRAWING_CACHE_ENABLED 32768L -#undef android_widget_RelativeLayout_SAVE_DISABLED -#define android_widget_RelativeLayout_SAVE_DISABLED 65536L -#undef android_widget_RelativeLayout_SAVE_DISABLED_MASK -#define android_widget_RelativeLayout_SAVE_DISABLED_MASK 65536L -#undef android_widget_RelativeLayout_WILL_NOT_CACHE_DRAWING -#define android_widget_RelativeLayout_WILL_NOT_CACHE_DRAWING 131072L -#undef android_widget_RelativeLayout_FOCUSABLE_IN_TOUCH_MODE -#define android_widget_RelativeLayout_FOCUSABLE_IN_TOUCH_MODE 262144L -#undef android_widget_RelativeLayout_DRAWING_CACHE_QUALITY_LOW -#define android_widget_RelativeLayout_DRAWING_CACHE_QUALITY_LOW 524288L -#undef android_widget_RelativeLayout_DRAWING_CACHE_QUALITY_HIGH -#define android_widget_RelativeLayout_DRAWING_CACHE_QUALITY_HIGH 1048576L -#undef android_widget_RelativeLayout_DRAWING_CACHE_QUALITY_AUTO -#define android_widget_RelativeLayout_DRAWING_CACHE_QUALITY_AUTO 0L -#undef android_widget_RelativeLayout_DRAWING_CACHE_QUALITY_MASK -#define android_widget_RelativeLayout_DRAWING_CACHE_QUALITY_MASK 1572864L -#undef android_widget_RelativeLayout_LONG_CLICKABLE -#define android_widget_RelativeLayout_LONG_CLICKABLE 2097152L -#undef android_widget_RelativeLayout_DUPLICATE_PARENT_STATE -#define android_widget_RelativeLayout_DUPLICATE_PARENT_STATE 4194304L -#undef android_widget_RelativeLayout_SCROLLBARS_INSIDE_OVERLAY -#define android_widget_RelativeLayout_SCROLLBARS_INSIDE_OVERLAY 0L -#undef android_widget_RelativeLayout_SCROLLBARS_INSIDE_INSET -#define android_widget_RelativeLayout_SCROLLBARS_INSIDE_INSET 16777216L -#undef android_widget_RelativeLayout_SCROLLBARS_OUTSIDE_OVERLAY -#define android_widget_RelativeLayout_SCROLLBARS_OUTSIDE_OVERLAY 33554432L -#undef android_widget_RelativeLayout_SCROLLBARS_OUTSIDE_INSET -#define android_widget_RelativeLayout_SCROLLBARS_OUTSIDE_INSET 50331648L -#undef android_widget_RelativeLayout_SCROLLBARS_INSET_MASK -#define android_widget_RelativeLayout_SCROLLBARS_INSET_MASK 16777216L -#undef android_widget_RelativeLayout_SCROLLBARS_OUTSIDE_MASK -#define android_widget_RelativeLayout_SCROLLBARS_OUTSIDE_MASK 33554432L -#undef android_widget_RelativeLayout_SCROLLBARS_STYLE_MASK -#define android_widget_RelativeLayout_SCROLLBARS_STYLE_MASK 50331648L -#undef android_widget_RelativeLayout_KEEP_SCREEN_ON -#define android_widget_RelativeLayout_KEEP_SCREEN_ON 67108864L -#undef android_widget_RelativeLayout_SOUND_EFFECTS_ENABLED -#define android_widget_RelativeLayout_SOUND_EFFECTS_ENABLED 134217728L -#undef android_widget_RelativeLayout_HAPTIC_FEEDBACK_ENABLED -#define android_widget_RelativeLayout_HAPTIC_FEEDBACK_ENABLED 268435456L -#undef android_widget_RelativeLayout_PARENT_SAVE_DISABLED -#define android_widget_RelativeLayout_PARENT_SAVE_DISABLED 536870912L -#undef android_widget_RelativeLayout_PARENT_SAVE_DISABLED_MASK -#define android_widget_RelativeLayout_PARENT_SAVE_DISABLED_MASK 536870912L -#undef android_widget_RelativeLayout_FOCUSABLES_ALL -#define android_widget_RelativeLayout_FOCUSABLES_ALL 0L -#undef android_widget_RelativeLayout_FOCUSABLES_TOUCH_MODE -#define android_widget_RelativeLayout_FOCUSABLES_TOUCH_MODE 1L -#undef android_widget_RelativeLayout_FOCUS_BACKWARD -#define android_widget_RelativeLayout_FOCUS_BACKWARD 1L -#undef android_widget_RelativeLayout_FOCUS_FORWARD -#define android_widget_RelativeLayout_FOCUS_FORWARD 2L -#undef android_widget_RelativeLayout_FOCUS_LEFT -#define android_widget_RelativeLayout_FOCUS_LEFT 17L -#undef android_widget_RelativeLayout_FOCUS_UP -#define android_widget_RelativeLayout_FOCUS_UP 33L -#undef android_widget_RelativeLayout_FOCUS_RIGHT -#define android_widget_RelativeLayout_FOCUS_RIGHT 66L -#undef android_widget_RelativeLayout_FOCUS_DOWN -#define android_widget_RelativeLayout_FOCUS_DOWN 130L -#undef android_widget_RelativeLayout_MEASURED_SIZE_MASK -#define android_widget_RelativeLayout_MEASURED_SIZE_MASK 16777215L -#undef android_widget_RelativeLayout_MEASURED_STATE_MASK -#define android_widget_RelativeLayout_MEASURED_STATE_MASK -16777216L -#undef android_widget_RelativeLayout_MEASURED_HEIGHT_STATE_SHIFT -#define android_widget_RelativeLayout_MEASURED_HEIGHT_STATE_SHIFT 16L -#undef android_widget_RelativeLayout_MEASURED_STATE_TOO_SMALL -#define android_widget_RelativeLayout_MEASURED_STATE_TOO_SMALL 16777216L -#undef android_widget_RelativeLayout_PFLAG2_DRAG_CAN_ACCEPT -#define android_widget_RelativeLayout_PFLAG2_DRAG_CAN_ACCEPT 1L -#undef android_widget_RelativeLayout_PFLAG2_DRAG_HOVERED -#define android_widget_RelativeLayout_PFLAG2_DRAG_HOVERED 2L -#undef android_widget_RelativeLayout_LAYOUT_DIRECTION_LTR -#define android_widget_RelativeLayout_LAYOUT_DIRECTION_LTR 0L -#undef android_widget_RelativeLayout_LAYOUT_DIRECTION_RTL -#define android_widget_RelativeLayout_LAYOUT_DIRECTION_RTL 1L -#undef android_widget_RelativeLayout_LAYOUT_DIRECTION_INHERIT -#define android_widget_RelativeLayout_LAYOUT_DIRECTION_INHERIT 2L -#undef android_widget_RelativeLayout_LAYOUT_DIRECTION_LOCALE -#define android_widget_RelativeLayout_LAYOUT_DIRECTION_LOCALE 3L -#undef android_widget_RelativeLayout_PFLAG2_LAYOUT_DIRECTION_MASK_SHIFT -#define android_widget_RelativeLayout_PFLAG2_LAYOUT_DIRECTION_MASK_SHIFT 2L -#undef android_widget_RelativeLayout_PFLAG2_LAYOUT_DIRECTION_MASK -#define android_widget_RelativeLayout_PFLAG2_LAYOUT_DIRECTION_MASK 12L -#undef android_widget_RelativeLayout_PFLAG2_LAYOUT_DIRECTION_RESOLVED_RTL -#define android_widget_RelativeLayout_PFLAG2_LAYOUT_DIRECTION_RESOLVED_RTL 16L -#undef android_widget_RelativeLayout_PFLAG2_LAYOUT_DIRECTION_RESOLVED -#define android_widget_RelativeLayout_PFLAG2_LAYOUT_DIRECTION_RESOLVED 32L -#undef android_widget_RelativeLayout_PFLAG2_LAYOUT_DIRECTION_RESOLVED_MASK -#define android_widget_RelativeLayout_PFLAG2_LAYOUT_DIRECTION_RESOLVED_MASK 48L -#undef android_widget_RelativeLayout_STATUS_BAR_HIDDEN -#define android_widget_RelativeLayout_STATUS_BAR_HIDDEN 1L -#undef android_widget_RelativeLayout_STATUS_BAR_VISIBLE -#define android_widget_RelativeLayout_STATUS_BAR_VISIBLE 0L -#undef android_widget_RelativeLayout_SYSTEM_UI_FLAG_FULLSCREEN -#define android_widget_RelativeLayout_SYSTEM_UI_FLAG_FULLSCREEN 4L -#undef android_widget_RelativeLayout_SYSTEM_UI_FLAG_HIDE_NAVIGATION -#define android_widget_RelativeLayout_SYSTEM_UI_FLAG_HIDE_NAVIGATION 2L -#undef android_widget_RelativeLayout_SYSTEM_UI_FLAG_IMMERSIVE -#define android_widget_RelativeLayout_SYSTEM_UI_FLAG_IMMERSIVE 2048L -#undef android_widget_RelativeLayout_SYSTEM_UI_FLAG_IMMERSIVE_STICKY -#define android_widget_RelativeLayout_SYSTEM_UI_FLAG_IMMERSIVE_STICKY 4096L -#undef android_widget_RelativeLayout_SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN -#define android_widget_RelativeLayout_SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN 1024L -#undef android_widget_RelativeLayout_SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION -#define android_widget_RelativeLayout_SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION 512L -#undef android_widget_RelativeLayout_SYSTEM_UI_FLAG_LAYOUT_STABLE -#define android_widget_RelativeLayout_SYSTEM_UI_FLAG_LAYOUT_STABLE 256L -#undef android_widget_RelativeLayout_SYSTEM_UI_FLAG_LOW_PROFILE -#define android_widget_RelativeLayout_SYSTEM_UI_FLAG_LOW_PROFILE 1L -#undef android_widget_RelativeLayout_SYSTEM_UI_FLAG_VISIBLE -#define android_widget_RelativeLayout_SYSTEM_UI_FLAG_VISIBLE 0L -#undef android_widget_RelativeLayout_SYSTEM_UI_LAYOUT_FLAGS -#define android_widget_RelativeLayout_SYSTEM_UI_LAYOUT_FLAGS 1536L -#undef android_widget_RelativeLayout_TEXT_ALIGNMENT_CENTER -#define android_widget_RelativeLayout_TEXT_ALIGNMENT_CENTER 4L -#undef android_widget_RelativeLayout_TEXT_ALIGNMENT_GRAVITY -#define android_widget_RelativeLayout_TEXT_ALIGNMENT_GRAVITY 1L -#undef android_widget_RelativeLayout_TEXT_ALIGNMENT_INHERIT -#define android_widget_RelativeLayout_TEXT_ALIGNMENT_INHERIT 0L -#undef android_widget_RelativeLayout_TEXT_ALIGNMENT_TEXT_END -#define android_widget_RelativeLayout_TEXT_ALIGNMENT_TEXT_END 3L -#undef android_widget_RelativeLayout_TEXT_ALIGNMENT_TEXT_START -#define android_widget_RelativeLayout_TEXT_ALIGNMENT_TEXT_START 2L -#undef android_widget_RelativeLayout_TEXT_ALIGNMENT_VIEW_END -#define android_widget_RelativeLayout_TEXT_ALIGNMENT_VIEW_END 6L -#undef android_widget_RelativeLayout_TEXT_ALIGNMENT_VIEW_START -#define android_widget_RelativeLayout_TEXT_ALIGNMENT_VIEW_START 5L -#undef android_widget_RelativeLayout_TEXT_DIRECTION_ANY_RTL -#define android_widget_RelativeLayout_TEXT_DIRECTION_ANY_RTL 2L -#undef android_widget_RelativeLayout_TEXT_DIRECTION_FIRST_STRONG -#define android_widget_RelativeLayout_TEXT_DIRECTION_FIRST_STRONG 1L -#undef android_widget_RelativeLayout_TEXT_DIRECTION_INHERIT -#define android_widget_RelativeLayout_TEXT_DIRECTION_INHERIT 0L -#undef android_widget_RelativeLayout_TEXT_DIRECTION_LOCALE -#define android_widget_RelativeLayout_TEXT_DIRECTION_LOCALE 5L -#undef android_widget_RelativeLayout_TEXT_DIRECTION_LTR -#define android_widget_RelativeLayout_TEXT_DIRECTION_LTR 3L -#undef android_widget_RelativeLayout_TEXT_DIRECTION_RTL -#define android_widget_RelativeLayout_TEXT_DIRECTION_RTL 4L -/* - * Class: android_widget_RelativeLayout - * Method: native_constructor - * Signature: (Landroid/content/Context;Landroid/util/AttributeSet;)J - */ -JNIEXPORT jlong JNICALL Java_android_widget_RelativeLayout_native_1constructor - (JNIEnv *, jobject, jobject, jobject); - -/* - * Class: android_widget_RelativeLayout - * Method: native_addView - * Signature: (JJILandroid/view/ViewGroup/LayoutParams;)V - */ -JNIEXPORT void JNICALL Java_android_widget_RelativeLayout_native_1addView - (JNIEnv *, jobject, jlong, jlong, jint, jobject); - -/* - * Class: android_widget_RelativeLayout - * Method: native_removeView - * Signature: (JJ)V - */ -JNIEXPORT void JNICALL Java_android_widget_RelativeLayout_native_1removeView - (JNIEnv *, jobject, jlong, jlong); - -#ifdef __cplusplus -} -#endif -#endif diff --git a/src/api-impl-jni/widgets/android_widget_RelativeLayout.c b/src/api-impl-jni/widgets/android_widget_RelativeLayout.c deleted file mode 100644 index 619b7857..00000000 --- a/src/api-impl-jni/widgets/android_widget_RelativeLayout.c +++ /dev/null @@ -1,32 +0,0 @@ -#include - -#include "../defines.h" -#include "../util.h" - -#include "WrapperWidget.h" - -#include "../generated_headers/android_widget_RelativeLayout.h" -#include "../generated_headers/android_view_ViewGroup.h" - -// FIXME not a relative layout - -JNIEXPORT jlong JNICALL Java_android_widget_RelativeLayout_native_1constructor(JNIEnv *env, jobject this, jobject context, jobject attrs) -{ - int orientation = attribute_set_get_int(env, attrs, "orientation", NULL, 1); - - GtkWidget *wrapper = g_object_ref(wrapper_widget_new()); - GtkWidget *box = gtk_box_new(orientation ? GTK_ORIENTATION_VERTICAL : GTK_ORIENTATION_HORIZONTAL, 1); // spacing of 1 - wrapper_widget_set_child(WRAPPER_WIDGET(wrapper), box); - gtk_widget_set_name(GTK_WIDGET(box), "RelativeLayout"); - return _INTPTR(box); -} - -JNIEXPORT void JNICALL Java_android_widget_RelativeLayout_native_1addView(JNIEnv *env, jobject this, jlong widget, jlong child, jint index, jobject layout_params) -{ - gtk_box_append(GTK_BOX(_PTR(widget)), gtk_widget_get_parent(GTK_WIDGET(_PTR(child)))); // FIXME - ignores index argument -} - -JNIEXPORT void JNICALL Java_android_widget_RelativeLayout_native_1removeView(JNIEnv *env, jobject this, jlong widget, jlong child) -{ - gtk_box_remove(GTK_BOX(_PTR(widget)), gtk_widget_get_parent(GTK_WIDGET(_PTR(child)))); -} diff --git a/src/api-impl/android/widget/RelativeLayout.java b/src/api-impl/android/widget/RelativeLayout.java index 1668661b..f872b67f 100644 --- a/src/api-impl/android/widget/RelativeLayout.java +++ b/src/api-impl/android/widget/RelativeLayout.java @@ -1,44 +1,1696 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package android.widget; +import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1; import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Rect; +import android.os.Build; +import android.util.ArrayMap; import android.util.AttributeSet; +import android.util.SparseArray; +import android.view.Gravity; import android.view.View; import android.view.ViewGroup; -import android.view.ViewGroup.LayoutParams; +import com.android.internal.R; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.SortedSet; +/** + * A Layout where the positions of the children can be described in relation to each other or to the + * parent. + * + *

+ * Note that you cannot have a circular dependency between the size of the RelativeLayout and the + * position of its children. For example, you cannot have a RelativeLayout whose height is set to + * {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT WRAP_CONTENT} and a child set to + * {@link #ALIGN_PARENT_BOTTOM}. + *

+ * + *

Note: In platform version 17 and lower, RelativeLayout was affected by + * a measurement bug that could cause child views to be measured with incorrect + * {@link android.view.View.MeasureSpec MeasureSpec} values. (See + * {@link android.view.View.MeasureSpec#makeMeasureSpec(int, int) MeasureSpec.makeMeasureSpec} + * for more details.) This was triggered when a RelativeLayout container was placed in + * a scrolling container, such as a ScrollView or HorizontalScrollView. If a custom view + * not equipped to properly measure with the MeasureSpec mode + * {@link android.view.View.MeasureSpec#UNSPECIFIED UNSPECIFIED} was placed in a RelativeLayout, + * this would silently work anyway as RelativeLayout would pass a very large + * {@link android.view.View.MeasureSpec#AT_MOST AT_MOST} MeasureSpec instead.

+ * + *

This behavior has been preserved for apps that set android:targetSdkVersion="17" + * or older in their manifest's uses-sdk tag for compatibility. Apps targeting SDK + * version 18 or newer will receive the correct behavior.

+ * + *

See the Relative + * Layout guide.

+ * + *

+ * Also see {@link android.widget.RelativeLayout.LayoutParams RelativeLayout.LayoutParams} for + * layout attributes + *

+ * + * @attr ref android.R.styleable#RelativeLayout_gravity + * @attr ref android.R.styleable#RelativeLayout_ignoreGravity + */ public class RelativeLayout extends ViewGroup { - - boolean orientation; - - public RelativeLayout(Context context, AttributeSet attrs) { - super(context, attrs); - } + public static final int TRUE = -1; + /** + * Rule that aligns a child's right edge with another child's left edge. + */ + public static final int LEFT_OF = 0; + /** + * Rule that aligns a child's left edge with another child's right edge. + */ + public static final int RIGHT_OF = 1; + /** + * Rule that aligns a child's bottom edge with another child's top edge. + */ + public static final int ABOVE = 2; + /** + * Rule that aligns a child's top edge with another child's bottom edge. + */ + public static final int BELOW = 3; + /** + * Rule that aligns a child's baseline with another child's baseline. + */ + public static final int ALIGN_BASELINE = 4; + /** + * Rule that aligns a child's left edge with another child's left edge. + */ + public static final int ALIGN_LEFT = 5; + /** + * Rule that aligns a child's top edge with another child's top edge. + */ + public static final int ALIGN_TOP = 6; + /** + * Rule that aligns a child's right edge with another child's right edge. + */ + public static final int ALIGN_RIGHT = 7; + /** + * Rule that aligns a child's bottom edge with another child's bottom edge. + */ + public static final int ALIGN_BOTTOM = 8; + /** + * Rule that aligns the child's left edge with its RelativeLayout + * parent's left edge. + */ + public static final int ALIGN_PARENT_LEFT = 9; + /** + * Rule that aligns the child's top edge with its RelativeLayout + * parent's top edge. + */ + public static final int ALIGN_PARENT_TOP = 10; + /** + * Rule that aligns the child's right edge with its RelativeLayout + * parent's right edge. + */ + public static final int ALIGN_PARENT_RIGHT = 11; + /** + * Rule that aligns the child's bottom edge with its RelativeLayout + * parent's bottom edge. + */ + public static final int ALIGN_PARENT_BOTTOM = 12; + /** + * Rule that centers the child with respect to the bounds of its + * RelativeLayout parent. + */ + public static final int CENTER_IN_PARENT = 13; + /** + * Rule that centers the child horizontally with respect to the + * bounds of its RelativeLayout parent. + */ + public static final int CENTER_HORIZONTAL = 14; + /** + * Rule that centers the child vertically with respect to the + * bounds of its RelativeLayout parent. + */ + public static final int CENTER_VERTICAL = 15; + /** + * Rule that aligns a child's end edge with another child's start edge. + */ + public static final int START_OF = 16; + /** + * Rule that aligns a child's start edge with another child's end edge. + */ + public static final int END_OF = 17; + /** + * Rule that aligns a child's start edge with another child's start edge. + */ + public static final int ALIGN_START = 18; + /** + * Rule that aligns a child's end edge with another child's end edge. + */ + public static final int ALIGN_END = 19; + /** + * Rule that aligns the child's start edge with its RelativeLayout + * parent's start edge. + */ + public static final int ALIGN_PARENT_START = 20; + /** + * Rule that aligns the child's end edge with its RelativeLayout + * parent's end edge. + */ + public static final int ALIGN_PARENT_END = 21; + private static final int VERB_COUNT = 22; + private static final int[] RULES_VERTICAL = { + ABOVE, BELOW, ALIGN_BASELINE, ALIGN_TOP, ALIGN_BOTTOM + }; + private static final int[] RULES_HORIZONTAL = { + LEFT_OF, RIGHT_OF, ALIGN_LEFT, ALIGN_RIGHT, START_OF, END_OF, ALIGN_START, ALIGN_END + }; + /** + * Used to indicate left/right/top/bottom should be inferred from constraints + */ + private static final int VALUE_NOT_SET = Integer.MIN_VALUE; + private View mBaselineView = null; + private int mGravity = Gravity.START | Gravity.TOP; + private final Rect mContentBounds = new Rect(); + private final Rect mSelfBounds = new Rect(); + private int mIgnoreGravity; + private SortedSet mTopToBottomLeftToRightSet = null; + private boolean mDirtyHierarchy; + private View[] mSortedHorizontalChildren; + private View[] mSortedVerticalChildren; + private final DependencyGraph mGraph = new DependencyGraph(); + // Compatibility hack. Old versions of the platform had problems + // with MeasureSpec value overflow and RelativeLayout was one source of them. + // Some apps came to rely on them. :( + private boolean mAllowBrokenMeasureSpecs = false; + // Compatibility hack. Old versions of the platform would not take + // margins and padding into account when generating the height measure spec + // for children during the horizontal measure pass. + private boolean mMeasureVerticalWithPaddingMargin = false; + // A default width used for RTL measure pass + /** + * Value reduced so as not to interfere with View's measurement spec. flags. See: + * {@link View#MEASURED_SIZE_MASK}. + * {@link View#MEASURED_STATE_TOO_SMALL}. + **/ + private static final int DEFAULT_WIDTH = 0x00010000; public RelativeLayout(Context context) { - super(context); + this(context, null); + } + + public RelativeLayout(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public RelativeLayout(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public RelativeLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr); + initFromAttributes(context, attrs, defStyleAttr, defStyleRes); + queryCompatibilityModes(context); + } + + private void initFromAttributes( + Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + final TypedArray a = context.obtainStyledAttributes( + attrs, R.styleable.RelativeLayout, defStyleAttr, defStyleRes); + mIgnoreGravity = a.getResourceId(R.styleable.RelativeLayout_ignoreGravity, View.NO_ID); + mGravity = a.getInt(R.styleable.RelativeLayout_gravity, mGravity); + a.recycle(); + } + + private void queryCompatibilityModes(Context context) { + int version = context.getApplicationInfo().targetSdkVersion; + mAllowBrokenMeasureSpecs = version <= Build.VERSION_CODES.JELLY_BEAN_MR1; + mMeasureVerticalWithPaddingMargin = version >= Build.VERSION_CODES.JELLY_BEAN_MR2; + } + + public boolean shouldDelayChildPressedState() { + return false; + } + + /** + * Defines which View is ignored when the gravity is applied. This setting has no + * effect if the gravity is Gravity.START | Gravity.TOP. + * + * @param viewId The id of the View to be ignored by gravity, or 0 if no View + * should be ignored. + * + * @see #setGravity(int) + * + * @attr ref android.R.styleable#RelativeLayout_ignoreGravity + */ + public void setIgnoreGravity(int viewId) { + mIgnoreGravity = viewId; + } + + /** + * Get the id of the View to be ignored by gravity + * + * @attr ref android.R.styleable#RelativeLayout_ignoreGravity + */ + public int getIgnoreGravity() { + return mIgnoreGravity; + } + + /** + * Describes how the child views are positioned. + * + * @return the gravity. + * + * @see #setGravity(int) + * @see android.view.Gravity + * + * @attr ref android.R.styleable#RelativeLayout_gravity + */ + public int getGravity() { + return mGravity; + } + + /** + * Describes how the child views are positioned. Defaults to + * Gravity.START | Gravity.TOP. + * + *

Note that since RelativeLayout considers the positioning of each child + * relative to one another to be significant, setting gravity will affect + * the positioning of all children as a single unit within the parent. + * This happens after children have been relatively positioned.

+ * + * @param gravity See {@link android.view.Gravity} + * + * @see #setHorizontalGravity(int) + * @see #setVerticalGravity(int) + * + * @attr ref android.R.styleable#RelativeLayout_gravity + */ + public void setGravity(int gravity) { + if (mGravity != gravity) { + if ((gravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) == 0) { + gravity |= Gravity.START; + } + if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == 0) { + gravity |= Gravity.TOP; + } + mGravity = gravity; + requestLayout(); + } + } + + public void setHorizontalGravity(int horizontalGravity) { + final int gravity = horizontalGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK; + if ((mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) != gravity) { + mGravity = (mGravity & ~Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) | gravity; + requestLayout(); + } + } + + public void setVerticalGravity(int verticalGravity) { + final int gravity = verticalGravity & Gravity.VERTICAL_GRAVITY_MASK; + if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != gravity) { + mGravity = (mGravity & ~Gravity.VERTICAL_GRAVITY_MASK) | gravity; + requestLayout(); + } } @Override - protected native long native_constructor(Context context, AttributeSet attrs); - @Override - protected native void native_addView(long widget, long child, int index, ViewGroup.LayoutParams params); - @Override - protected native void native_removeView(long widget, long child); + public int getBaseline() { + return mBaselineView != null ? mBaselineView.getBaseline() : super.getBaseline(); + } - public static class LayoutParams extends ViewGroup.LayoutParams { - public LayoutParams(int width, int height) { - super(width, height); + @Override + public void requestLayout() { + super.requestLayout(); + mDirtyHierarchy = true; + } + + private void sortChildren() { + final int count = getChildCount(); + if (mSortedVerticalChildren == null || mSortedVerticalChildren.length != count) { + mSortedVerticalChildren = new View[count]; + } + if (mSortedHorizontalChildren == null || mSortedHorizontalChildren.length != count) { + mSortedHorizontalChildren = new View[count]; + } + final DependencyGraph graph = mGraph; + graph.clear(); + for (int i = 0; i < count; i++) { + graph.add(getChildAt(i)); + } + graph.getSortedViews(mSortedVerticalChildren, RULES_VERTICAL); + graph.getSortedViews(mSortedHorizontalChildren, RULES_HORIZONTAL); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + if (mDirtyHierarchy) { + mDirtyHierarchy = false; + sortChildren(); + } + int myWidth = -1; + int myHeight = -1; + int width = 0; + int height = 0; + final int widthMode = MeasureSpec.getMode(widthMeasureSpec); + final int heightMode = MeasureSpec.getMode(heightMeasureSpec); + final int widthSize = MeasureSpec.getSize(widthMeasureSpec); + final int heightSize = MeasureSpec.getSize(heightMeasureSpec); + // Record our dimensions if they are known; + if (widthMode != MeasureSpec.UNSPECIFIED) { + myWidth = widthSize; + } + if (heightMode != MeasureSpec.UNSPECIFIED) { + myHeight = heightSize; + } + if (widthMode == MeasureSpec.EXACTLY) { + width = myWidth; + } + if (heightMode == MeasureSpec.EXACTLY) { + height = myHeight; + } + View ignore = null; + int gravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK; + final boolean horizontalGravity = gravity != Gravity.START && gravity != 0; + gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK; + final boolean verticalGravity = gravity != Gravity.TOP && gravity != 0; + int left = Integer.MAX_VALUE; + int top = Integer.MAX_VALUE; + int right = Integer.MIN_VALUE; + int bottom = Integer.MIN_VALUE; + boolean offsetHorizontalAxis = false; + boolean offsetVerticalAxis = false; + if ((horizontalGravity || verticalGravity) && mIgnoreGravity != View.NO_ID) { + ignore = findViewById(mIgnoreGravity); + } + final boolean isWrapContentWidth = widthMode != MeasureSpec.EXACTLY; + final boolean isWrapContentHeight = heightMode != MeasureSpec.EXACTLY; + // We need to know our size for doing the correct computation of children positioning in RTL + // mode but there is no practical way to get it instead of running the code below. + // So, instead of running the code twice, we just set the width to a "default display width" + // before the computation and then, as a last pass, we will update their real position with + // an offset equals to "DEFAULT_WIDTH - width". + final int layoutDirection = View.LAYOUT_DIRECTION_LTR; + if (/*isLayoutRtl()*/false && myWidth == -1) { + myWidth = DEFAULT_WIDTH; + } + View[] views = mSortedHorizontalChildren; + int count = views.length; + for (int i = 0; i < count; i++) { + View child = views[i]; + if (child.getVisibility() != GONE) { + LayoutParams params = (LayoutParams) child.getLayoutParams(); + int[] rules = params.getRules(layoutDirection); + applyHorizontalSizeRules(params, myWidth, rules); + measureChildHorizontal(child, params, myWidth, myHeight); + if (positionChildHorizontal(child, params, myWidth, isWrapContentWidth)) { + offsetHorizontalAxis = true; + } + } + } + views = mSortedVerticalChildren; + count = views.length; + final int targetSdkVersion = getContext().getApplicationInfo().targetSdkVersion; + for (int i = 0; i < count; i++) { + final View child = views[i]; + if (child.getVisibility() != GONE) { + final LayoutParams params = (LayoutParams) child.getLayoutParams(); + applyVerticalSizeRules(params, myHeight, child.getBaseline()); + measureChild(child, params, myWidth, myHeight); + if (positionChildVertical(child, params, myHeight, isWrapContentHeight)) { + offsetVerticalAxis = true; + } + if (isWrapContentWidth) { + if (/*isLayoutRtl()*/false) { + if (targetSdkVersion < Build.VERSION_CODES.KITKAT) { + width = Math.max(width, myWidth - params.mLeft); + } else { + width = Math.max(width, myWidth - params.mLeft + params.leftMargin); + } + } else { + if (targetSdkVersion < Build.VERSION_CODES.KITKAT) { + width = Math.max(width, params.mRight); + } else { + width = Math.max(width, params.mRight + params.rightMargin); + } + } + } + if (isWrapContentHeight) { + if (targetSdkVersion < Build.VERSION_CODES.KITKAT) { + height = Math.max(height, params.mBottom); + } else { + height = Math.max(height, params.mBottom + params.bottomMargin); + } + } + if (child != ignore || verticalGravity) { + left = Math.min(left, params.mLeft - params.leftMargin); + top = Math.min(top, params.mTop - params.topMargin); + } + if (child != ignore || horizontalGravity) { + right = Math.max(right, params.mRight + params.rightMargin); + bottom = Math.max(bottom, params.mBottom + params.bottomMargin); + } + } + } + // Use the top-start-most laid out view as the baseline. RTL offsets are + // applied later, so we can use the left-most edge as the starting edge. + View baselineView = null; + LayoutParams baselineParams = null; + for (int i = 0; i < count; i++) { + final View child = views[i]; + if (child.getVisibility() != GONE) { + final LayoutParams childParams = (LayoutParams) child.getLayoutParams(); + if (baselineView == null || baselineParams == null + || compareLayoutPosition(childParams, baselineParams) < 0) { + baselineView = child; + baselineParams = childParams; + } + } + } + mBaselineView = baselineView; + if (isWrapContentWidth) { + // Width already has left padding in it since it was calculated by looking at + // the right of each child view + width += getPaddingRight(); + if (layout_params != null && layout_params.width >= 0) { + width = Math.max(width, layout_params.width); + } + width = Math.max(width, getSuggestedMinimumWidth()); + width = resolveSize(width, widthMeasureSpec); + if (offsetHorizontalAxis) { + for (int i = 0; i < count; i++) { + final View child = views[i]; + if (child.getVisibility() != GONE) { + final LayoutParams params = (LayoutParams) child.getLayoutParams(); + final int[] rules = params.getRules(layoutDirection); + if (rules[CENTER_IN_PARENT] != 0 || rules[CENTER_HORIZONTAL] != 0) { + centerHorizontal(child, params, width); + } else if (rules[ALIGN_PARENT_RIGHT] != 0) { + final int childWidth = child.getMeasuredWidth(); + params.mLeft = width - getPaddingRight() - childWidth; + params.mRight = params.mLeft + childWidth; + } + } + } + } + } + if (isWrapContentHeight) { + // Height already has top padding in it since it was calculated by looking at + // the bottom of each child view + height += getPaddingBottom(); + if (layout_params != null && layout_params.height >= 0) { + height = Math.max(height, layout_params.height); + } + height = Math.max(height, getSuggestedMinimumHeight()); + height = resolveSize(height, heightMeasureSpec); + if (offsetVerticalAxis) { + for (int i = 0; i < count; i++) { + final View child = views[i]; + if (child.getVisibility() != GONE) { + final LayoutParams params = (LayoutParams) child.getLayoutParams(); + final int[] rules = params.getRules(layoutDirection); + if (rules[CENTER_IN_PARENT] != 0 || rules[CENTER_VERTICAL] != 0) { + centerVertical(child, params, height); + } else if (rules[ALIGN_PARENT_BOTTOM] != 0) { + final int childHeight = child.getMeasuredHeight(); + params.mTop = height - getPaddingBottom() - childHeight; + params.mBottom = params.mTop + childHeight; + } + } + } + } + } + if (horizontalGravity || verticalGravity) { + final Rect selfBounds = mSelfBounds; + selfBounds.set(getPaddingLeft(), getPaddingTop(), width - getPaddingRight(), + height - getPaddingBottom()); + final Rect contentBounds = mContentBounds; + Gravity.apply(mGravity, right - left, bottom - top, selfBounds, contentBounds, + layoutDirection); + final int horizontalOffset = contentBounds.left - left; + final int verticalOffset = contentBounds.top - top; + if (horizontalOffset != 0 || verticalOffset != 0) { + for (int i = 0; i < count; i++) { + final View child = views[i]; + if (child.getVisibility() != GONE && child != ignore) { + final LayoutParams params = (LayoutParams) child.getLayoutParams(); + if (horizontalGravity) { + params.mLeft += horizontalOffset; + params.mRight += horizontalOffset; + } + if (verticalGravity) { + params.mTop += verticalOffset; + params.mBottom += verticalOffset; + } + } + } + } + } + if (/*isLayoutRtl()*/false) { + final int offsetWidth = myWidth - width; + for (int i = 0; i < count; i++) { + final View child = views[i]; + if (child.getVisibility() != GONE) { + final LayoutParams params = (LayoutParams) child.getLayoutParams(); + params.mLeft -= offsetWidth; + params.mRight -= offsetWidth; + } + } + } + setMeasuredDimension(width, height); + } + + /** + * @return a negative number if the top of {@code p1} is above the top of + * {@code p2} or if they have identical top values and the left of + * {@code p1} is to the left of {@code p2}, or a positive number + * otherwise + */ + private int compareLayoutPosition(LayoutParams p1, LayoutParams p2) { + final int topDiff = p1.mTop - p2.mTop; + if (topDiff != 0) { + return topDiff; + } + return p1.mLeft - p2.mLeft; + } + + /** + * Measure a child. The child should have left, top, right and bottom information + * stored in its LayoutParams. If any of these values is VALUE_NOT_SET it means + * that the view can extend up to the corresponding edge. + * + * @param child Child to measure + * @param params LayoutParams associated with child + * @param myWidth Width of the the RelativeLayout + * @param myHeight Height of the RelativeLayout + */ + private void measureChild(View child, LayoutParams params, int myWidth, int myHeight) { + int childWidthMeasureSpec = getChildMeasureSpec(params.mLeft, + params.mRight, params.width, + params.leftMargin, params.rightMargin, + getPaddingLeft(), getPaddingRight(), + myWidth); + int childHeightMeasureSpec = getChildMeasureSpec(params.mTop, + params.mBottom, params.height, + params.topMargin, params.bottomMargin, + getPaddingTop(), getPaddingBottom(), + myHeight); + child.measure(childWidthMeasureSpec, childHeightMeasureSpec); + } + + private void measureChildHorizontal( + View child, LayoutParams params, int myWidth, int myHeight) { + final int childWidthMeasureSpec = getChildMeasureSpec(params.mLeft, params.mRight, + params.width, params.leftMargin, params.rightMargin, getPaddingLeft(), getPaddingRight(), + myWidth); + final int childHeightMeasureSpec; + if (myHeight < 0 && !mAllowBrokenMeasureSpecs) { + if (params.height >= 0) { + childHeightMeasureSpec = MeasureSpec.makeMeasureSpec( + params.height, MeasureSpec.EXACTLY); + } else { + // Negative values in a mySize/myWidth/myWidth value in + // RelativeLayout measurement is code for, "we got an + // unspecified mode in the RelativeLayout's measure spec." + // Carry it forward. + childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); + } + } else { + final int maxHeight; + if (mMeasureVerticalWithPaddingMargin) { + maxHeight = Math.max(0, myHeight - getPaddingTop() - getPaddingBottom() + - params.topMargin - params.bottomMargin); + } else { + maxHeight = Math.max(0, myHeight); + } + final int heightMode; + if (params.height == LayoutParams.MATCH_PARENT) { + heightMode = MeasureSpec.EXACTLY; + } else { + heightMode = MeasureSpec.AT_MOST; + } + childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(maxHeight, heightMode); + } + child.measure(childWidthMeasureSpec, childHeightMeasureSpec); + } + + /** + * Get a measure spec that accounts for all of the constraints on this view. + * This includes size constraints imposed by the RelativeLayout as well as + * the View's desired dimension. + * + * @param childStart The left or top field of the child's layout params + * @param childEnd The right or bottom field of the child's layout params + * @param childSize The child's desired size (the width or height field of + * the child's layout params) + * @param startMargin The left or top margin + * @param endMargin The right or bottom margin + * @param startPadding getPaddingLeft() or getPaddingTop() + * @param endPadding getPaddingRight() or getPaddingBottom() + * @param mySize The width or height of this view (the RelativeLayout) + * @return MeasureSpec for the child + */ + private int getChildMeasureSpec(int childStart, int childEnd, + int childSize, int startMargin, int endMargin, int startPadding, + int endPadding, int mySize) { + int childSpecMode = 0; + int childSpecSize = 0; + // Negative values in a mySize value in RelativeLayout + // measurement is code for, "we got an unspecified mode in the + // RelativeLayout's measure spec." + final boolean isUnspecified = mySize < 0; + if (isUnspecified && !mAllowBrokenMeasureSpecs) { + if (childStart != VALUE_NOT_SET && childEnd != VALUE_NOT_SET) { + // Constraints fixed both edges, so child has an exact size. + childSpecSize = Math.max(0, childEnd - childStart); + childSpecMode = MeasureSpec.EXACTLY; + } else if (childSize >= 0) { + // The child specified an exact size. + childSpecSize = childSize; + childSpecMode = MeasureSpec.EXACTLY; + } else { + // Allow the child to be whatever size it wants. + childSpecSize = 0; + childSpecMode = MeasureSpec.UNSPECIFIED; + } + return MeasureSpec.makeMeasureSpec(childSpecSize, childSpecMode); + } + // Figure out start and end bounds. + int tempStart = childStart; + int tempEnd = childEnd; + // If the view did not express a layout constraint for an edge, use + // view's margins and our padding + if (tempStart == VALUE_NOT_SET) { + tempStart = startPadding + startMargin; + } + if (tempEnd == VALUE_NOT_SET) { + tempEnd = mySize - endPadding - endMargin; + } + // Figure out maximum size available to this view + final int maxAvailable = tempEnd - tempStart; + if (childStart != VALUE_NOT_SET && childEnd != VALUE_NOT_SET) { + // Constraints fixed both edges, so child must be an exact size. + childSpecMode = isUnspecified ? MeasureSpec.UNSPECIFIED : MeasureSpec.EXACTLY; + childSpecSize = Math.max(0, maxAvailable); + } else { + if (childSize >= 0) { + // Child wanted an exact size. Give as much as possible. + childSpecMode = MeasureSpec.EXACTLY; + if (maxAvailable >= 0) { + // We have a maximum size in this dimension. + childSpecSize = Math.min(maxAvailable, childSize); + } else { + // We can grow in this dimension. + childSpecSize = childSize; + } + } else if (childSize == LayoutParams.MATCH_PARENT) { + // Child wanted to be as big as possible. Give all available + // space. + childSpecMode = isUnspecified ? MeasureSpec.UNSPECIFIED : MeasureSpec.EXACTLY; + childSpecSize = Math.max(0, maxAvailable); + } else if (childSize == LayoutParams.WRAP_CONTENT) { + // Child wants to wrap content. Use AT_MOST to communicate + // available space if we know our max size. + if (maxAvailable >= 0) { + // We have a maximum size in this dimension. + childSpecMode = MeasureSpec.AT_MOST; + childSpecSize = maxAvailable; + } else { + // We can grow in this dimension. Child can be as big as it + // wants. + childSpecMode = MeasureSpec.UNSPECIFIED; + childSpecSize = 0; + } + } + } + return MeasureSpec.makeMeasureSpec(childSpecSize, childSpecMode); + } + + private boolean positionChildHorizontal(View child, LayoutParams params, int myWidth, + boolean wrapContent) { + final int layoutDirection = /*getLayoutDirection()*/View.LAYOUT_DIRECTION_LTR; + int[] rules = params.getRules(layoutDirection); + if (params.mLeft == VALUE_NOT_SET && params.mRight != VALUE_NOT_SET) { + // Right is fixed, but left varies + params.mLeft = params.mRight - child.getMeasuredWidth(); + } else if (params.mLeft != VALUE_NOT_SET && params.mRight == VALUE_NOT_SET) { + // Left is fixed, but right varies + params.mRight = params.mLeft + child.getMeasuredWidth(); + } else if (params.mLeft == VALUE_NOT_SET && params.mRight == VALUE_NOT_SET) { + // Both left and right vary + if (rules[CENTER_IN_PARENT] != 0 || rules[CENTER_HORIZONTAL] != 0) { + if (!wrapContent) { + centerHorizontal(child, params, myWidth); + } else { + positionAtEdge(child, params, myWidth); + } + return true; + } else { + // This is the default case. For RTL we start from the right and for LTR we start + // from the left. This will give LEFT/TOP for LTR and RIGHT/TOP for RTL. + positionAtEdge(child, params, myWidth); + } + } + return rules[ALIGN_PARENT_END] != 0; + } + + private void positionAtEdge(View child, LayoutParams params, int myWidth) { + if (/*isLayoutRtl()*/false) { + params.mRight = myWidth - getPaddingRight() - params.rightMargin; + params.mLeft = params.mRight - child.getMeasuredWidth(); + } else { + params.mLeft = getPaddingLeft() + params.leftMargin; + params.mRight = params.mLeft + child.getMeasuredWidth(); + } + } + + private boolean positionChildVertical(View child, LayoutParams params, int myHeight, + boolean wrapContent) { + int[] rules = params.getRules(); + if (params.mTop == VALUE_NOT_SET && params.mBottom != VALUE_NOT_SET) { + // Bottom is fixed, but top varies + params.mTop = params.mBottom - child.getMeasuredHeight(); + } else if (params.mTop != VALUE_NOT_SET && params.mBottom == VALUE_NOT_SET) { + // Top is fixed, but bottom varies + params.mBottom = params.mTop + child.getMeasuredHeight(); + } else if (params.mTop == VALUE_NOT_SET && params.mBottom == VALUE_NOT_SET) { + // Both top and bottom vary + if (rules[CENTER_IN_PARENT] != 0 || rules[CENTER_VERTICAL] != 0) { + if (!wrapContent) { + centerVertical(child, params, myHeight); + } else { + params.mTop = getPaddingTop() + params.topMargin; + params.mBottom = params.mTop + child.getMeasuredHeight(); + } + return true; + } else { + params.mTop = getPaddingTop() + params.topMargin; + params.mBottom = params.mTop + child.getMeasuredHeight(); + } + } + return rules[ALIGN_PARENT_BOTTOM] != 0; + } + + private void applyHorizontalSizeRules(LayoutParams childParams, int myWidth, int[] rules) { + RelativeLayout.LayoutParams anchorParams; + // VALUE_NOT_SET indicates a "soft requirement" in that direction. For example: + // left=10, right=VALUE_NOT_SET means the view must start at 10, but can go as far as it + // wants to the right + // left=VALUE_NOT_SET, right=10 means the view must end at 10, but can go as far as it + // wants to the left + // left=10, right=20 means the left and right ends are both fixed + childParams.mLeft = VALUE_NOT_SET; + childParams.mRight = VALUE_NOT_SET; + anchorParams = getRelatedViewParams(rules, LEFT_OF); + if (anchorParams != null) { + childParams.mRight = anchorParams.mLeft - (anchorParams.leftMargin + + childParams.rightMargin); + } else if (childParams.alignWithParent && rules[LEFT_OF] != 0) { + if (myWidth >= 0) { + childParams.mRight = myWidth - getPaddingRight() - childParams.rightMargin; + } + } + anchorParams = getRelatedViewParams(rules, RIGHT_OF); + if (anchorParams != null) { + childParams.mLeft = anchorParams.mRight + (anchorParams.rightMargin + + childParams.leftMargin); + } else if (childParams.alignWithParent && rules[RIGHT_OF] != 0) { + childParams.mLeft = getPaddingLeft() + childParams.leftMargin; + } + anchorParams = getRelatedViewParams(rules, ALIGN_LEFT); + if (anchorParams != null) { + childParams.mLeft = anchorParams.mLeft + childParams.leftMargin; + } else if (childParams.alignWithParent && rules[ALIGN_LEFT] != 0) { + childParams.mLeft = getPaddingLeft() + childParams.leftMargin; + } + anchorParams = getRelatedViewParams(rules, ALIGN_RIGHT); + if (anchorParams != null) { + childParams.mRight = anchorParams.mRight - childParams.rightMargin; + } else if (childParams.alignWithParent && rules[ALIGN_RIGHT] != 0) { + if (myWidth >= 0) { + childParams.mRight = myWidth - getPaddingRight() - childParams.rightMargin; + } + } + if (0 != rules[ALIGN_PARENT_LEFT]) { + childParams.mLeft = getPaddingLeft() + childParams.leftMargin; + } + if (0 != rules[ALIGN_PARENT_RIGHT]) { + if (myWidth >= 0) { + childParams.mRight = myWidth - getPaddingRight() - childParams.rightMargin; + } + } + } + + private void applyVerticalSizeRules(LayoutParams childParams, int myHeight, int myBaseline) { + final int[] rules = childParams.getRules(); + // Baseline alignment overrides any explicitly specified top or bottom. + int baselineOffset = getRelatedViewBaselineOffset(rules); + if (baselineOffset != -1) { + if (myBaseline != -1) { + baselineOffset -= myBaseline; + } + childParams.mTop = baselineOffset; + childParams.mBottom = VALUE_NOT_SET; + return; + } + RelativeLayout.LayoutParams anchorParams; + childParams.mTop = VALUE_NOT_SET; + childParams.mBottom = VALUE_NOT_SET; + anchorParams = getRelatedViewParams(rules, ABOVE); + if (anchorParams != null) { + childParams.mBottom = anchorParams.mTop - (anchorParams.topMargin + + childParams.bottomMargin); + } else if (childParams.alignWithParent && rules[ABOVE] != 0) { + if (myHeight >= 0) { + childParams.mBottom = myHeight - getPaddingBottom() - childParams.bottomMargin; + } + } + anchorParams = getRelatedViewParams(rules, BELOW); + if (anchorParams != null) { + childParams.mTop = anchorParams.mBottom + (anchorParams.bottomMargin + + childParams.topMargin); + } else if (childParams.alignWithParent && rules[BELOW] != 0) { + childParams.mTop = getPaddingTop() + childParams.topMargin; + } + anchorParams = getRelatedViewParams(rules, ALIGN_TOP); + if (anchorParams != null) { + childParams.mTop = anchorParams.mTop + childParams.topMargin; + } else if (childParams.alignWithParent && rules[ALIGN_TOP] != 0) { + childParams.mTop = getPaddingTop() + childParams.topMargin; + } + anchorParams = getRelatedViewParams(rules, ALIGN_BOTTOM); + if (anchorParams != null) { + childParams.mBottom = anchorParams.mBottom - childParams.bottomMargin; + } else if (childParams.alignWithParent && rules[ALIGN_BOTTOM] != 0) { + if (myHeight >= 0) { + childParams.mBottom = myHeight - getPaddingBottom() - childParams.bottomMargin; + } + } + if (0 != rules[ALIGN_PARENT_TOP]) { + childParams.mTop = getPaddingTop() + childParams.topMargin; + } + if (0 != rules[ALIGN_PARENT_BOTTOM]) { + if (myHeight >= 0) { + childParams.mBottom = myHeight - getPaddingBottom() - childParams.bottomMargin; + } + } + } + + private View getRelatedView(int[] rules, int relation) { + int id = rules[relation]; + if (id != 0) { + DependencyGraph.Node node = mGraph.mKeyNodes.get(id); + if (node == null) return null; + View v = node.view; + // Find the first non-GONE view up the chain + while (v.getVisibility() == View.GONE) { + rules = ((LayoutParams) v.getLayoutParams()).getRules(/*v.getLayoutDirection()*/View.LAYOUT_DIRECTION_LTR); + node = mGraph.mKeyNodes.get((rules[relation])); + // ignore self dependency. for more info look in git commit: da3003 + if (node == null || v == node.view) return null; + v = node.view; + } + return v; + } + return null; + } + + private LayoutParams getRelatedViewParams(int[] rules, int relation) { + View v = getRelatedView(rules, relation); + if (v != null) { + ViewGroup.LayoutParams params = v.getLayoutParams(); + if (params instanceof LayoutParams) { + return (LayoutParams) v.getLayoutParams(); + } + } + return null; + } + + private int getRelatedViewBaselineOffset(int[] rules) { + final View v = getRelatedView(rules, ALIGN_BASELINE); + if (v != null) { + final int baseline = v.getBaseline(); + if (baseline != -1) { + final ViewGroup.LayoutParams params = v.getLayoutParams(); + if (params instanceof LayoutParams) { + final LayoutParams anchorParams = (LayoutParams) v.getLayoutParams(); + return anchorParams.mTop + baseline; + } + } + } + return -1; + } + + private static void centerHorizontal(View child, LayoutParams params, int myWidth) { + int childWidth = child.getMeasuredWidth(); + int left = (myWidth - childWidth) / 2; + params.mLeft = left; + params.mRight = left + childWidth; + } + + private static void centerVertical(View child, LayoutParams params, int myHeight) { + int childHeight = child.getMeasuredHeight(); + int top = (myHeight - childHeight) / 2; + params.mTop = top; + params.mBottom = top + childHeight; + } + + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + // The layout has actually already been performed and the positions + // cached. Apply the cached values to the children. + final int count = getChildCount(); + for (int i = 0; i < count; i++) { + View child = getChildAt(i); + if (child.getVisibility() != GONE) { + RelativeLayout.LayoutParams st = + (RelativeLayout.LayoutParams) child.getLayoutParams(); + child.layout(st.mLeft, st.mTop, st.mRight, st.mBottom); + } + } + } + + @Override + public LayoutParams generateLayoutParams(AttributeSet attrs) { + return new RelativeLayout.LayoutParams(getContext(), attrs); + } + + /** + * Returns a set of layout parameters with a width of + * {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT}, + * a height of {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT} and no spanning. + */ + @Override + protected ViewGroup.LayoutParams generateDefaultLayoutParams() { + return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); + } + + // Override to allow type-checking of LayoutParams. + @Override + protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { + return p instanceof RelativeLayout.LayoutParams; + } + + @Override + protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams lp) { + // if (sPreserveMarginParamsInLayoutParamConversion) { + if (lp instanceof LayoutParams) { + return new LayoutParams((LayoutParams) lp); + } else if (lp instanceof MarginLayoutParams) { + return new LayoutParams((MarginLayoutParams) lp); + } + // } + return new LayoutParams(lp); + } + + public CharSequence getAccessibilityClassName() { + return RelativeLayout.class.getName(); + } + + /** + * Compares two views in left-to-right and top-to-bottom fashion. + */ + private class TopToBottomLeftToRightComparator implements Comparator { + public int compare(View first, View second) { + // top - bottom + int topDifference = first.getTop() - second.getTop(); + if (topDifference != 0) { + return topDifference; + } + // left - right + int leftDifference = first.getLeft() - second.getLeft(); + if (leftDifference != 0) { + return leftDifference; + } + // break tie by height + int heightDiference = first.getHeight() - second.getHeight(); + if (heightDiference != 0) { + return heightDiference; + } + // break tie by width + int widthDiference = first.getWidth() - second.getWidth(); + if (widthDiference != 0) { + return widthDiference; + } + return 0; + } + } + + /** + * Specifies how a view is positioned within a {@link RelativeLayout}. + * The relative layout containing the view uses the value of these layout parameters to + * determine where to position the view on the screen. If the view is not contained + * within a relative layout, these attributes are ignored. + * + * See the Relative + * Layout guide for example code demonstrating how to use relative layout's + * layout parameters in a layout XML. + * + * To learn more about layout parameters and how they differ from typical view attributes, + * see the Layouts + * guide. + * + * + * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignWithParentIfMissing + * @attr ref android.R.styleable#RelativeLayout_Layout_layout_toLeftOf + * @attr ref android.R.styleable#RelativeLayout_Layout_layout_toRightOf + * @attr ref android.R.styleable#RelativeLayout_Layout_layout_above + * @attr ref android.R.styleable#RelativeLayout_Layout_layout_below + * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignBaseline + * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignLeft + * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignTop + * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignRight + * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignBottom + * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignParentLeft + * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignParentTop + * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignParentRight + * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignParentBottom + * @attr ref android.R.styleable#RelativeLayout_Layout_layout_centerInParent + * @attr ref android.R.styleable#RelativeLayout_Layout_layout_centerHorizontal + * @attr ref android.R.styleable#RelativeLayout_Layout_layout_centerVertical + * @attr ref android.R.styleable#RelativeLayout_Layout_layout_toStartOf + * @attr ref android.R.styleable#RelativeLayout_Layout_layout_toEndOf + * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignStart + * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignEnd + * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignParentStart + * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignParentEnd + */ + public static class LayoutParams extends ViewGroup.MarginLayoutParams { + private int[] mRules = new int[VERB_COUNT]; + private int[] mInitialRules = new int[VERB_COUNT]; + private int mLeft; + private int mTop; + private int mRight; + private int mBottom; + /** + * Whether this view had any relative rules modified following the most + * recent resolution of layout direction. + */ + private boolean mNeedsLayoutResolution; + private boolean mRulesChanged = false; + private boolean mIsRtlCompatibilityMode = false; + /** + * When true, uses the parent as the anchor if the anchor doesn't exist or if + * the anchor's visibility is GONE. + */ + public boolean alignWithParent; + public LayoutParams(Context c, AttributeSet attrs) { + super(c, attrs); + TypedArray a = c.obtainStyledAttributes(attrs, + com.android.internal.R.styleable.RelativeLayout_Layout); + final int targetSdkVersion = c.getApplicationInfo().targetSdkVersion; + mIsRtlCompatibilityMode = (targetSdkVersion < JELLY_BEAN_MR1 || + !c.getApplicationInfo().hasRtlSupport()); + final int[] rules = mRules; + //noinspection MismatchedReadAndWriteOfArray + final int[] initialRules = mInitialRules; + final int N = a.getIndexCount(); + for (int i = 0; i < N; i++) { + int attr = a.getIndex(i); + switch (attr) { + case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignWithParentIfMissing: + alignWithParent = a.getBoolean(attr, false); + break; + case com.android.internal.R.styleable.RelativeLayout_Layout_layout_toLeftOf: + rules[LEFT_OF] = a.getResourceId(attr, 0); + break; + case com.android.internal.R.styleable.RelativeLayout_Layout_layout_toRightOf: + rules[RIGHT_OF] = a.getResourceId(attr, 0); + break; + case com.android.internal.R.styleable.RelativeLayout_Layout_layout_above: + rules[ABOVE] = a.getResourceId(attr, 0); + break; + case com.android.internal.R.styleable.RelativeLayout_Layout_layout_below: + rules[BELOW] = a.getResourceId(attr, 0); + break; + case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignBaseline: + rules[ALIGN_BASELINE] = a.getResourceId(attr, 0); + break; + case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignLeft: + rules[ALIGN_LEFT] = a.getResourceId(attr, 0); + break; + case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignTop: + rules[ALIGN_TOP] = a.getResourceId(attr, 0); + break; + case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignRight: + rules[ALIGN_RIGHT] = a.getResourceId(attr, 0); + break; + case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignBottom: + rules[ALIGN_BOTTOM] = a.getResourceId(attr, 0); + break; + case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignParentLeft: + rules[ALIGN_PARENT_LEFT] = a.getBoolean(attr, false) ? TRUE : 0; + break; + case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignParentTop: + rules[ALIGN_PARENT_TOP] = a.getBoolean(attr, false) ? TRUE : 0; + break; + case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignParentRight: + rules[ALIGN_PARENT_RIGHT] = a.getBoolean(attr, false) ? TRUE : 0; + break; + case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignParentBottom: + rules[ALIGN_PARENT_BOTTOM] = a.getBoolean(attr, false) ? TRUE : 0; + break; + case com.android.internal.R.styleable.RelativeLayout_Layout_layout_centerInParent: + rules[CENTER_IN_PARENT] = a.getBoolean(attr, false) ? TRUE : 0; + break; + case com.android.internal.R.styleable.RelativeLayout_Layout_layout_centerHorizontal: + rules[CENTER_HORIZONTAL] = a.getBoolean(attr, false) ? TRUE : 0; + break; + case com.android.internal.R.styleable.RelativeLayout_Layout_layout_centerVertical: + rules[CENTER_VERTICAL] = a.getBoolean(attr, false) ? TRUE : 0; + break; + case com.android.internal.R.styleable.RelativeLayout_Layout_layout_toStartOf: + rules[START_OF] = a.getResourceId(attr, 0); + break; + case com.android.internal.R.styleable.RelativeLayout_Layout_layout_toEndOf: + rules[END_OF] = a.getResourceId(attr, 0); + break; + case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignStart: + rules[ALIGN_START] = a.getResourceId(attr, 0); + break; + case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignEnd: + rules[ALIGN_END] = a.getResourceId(attr, 0); + break; + case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignParentStart: + rules[ALIGN_PARENT_START] = a.getBoolean(attr, false) ? TRUE : 0; + break; + case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignParentEnd: + rules[ALIGN_PARENT_END] = a.getBoolean(attr, false) ? TRUE : 0; + break; + } + } + mRulesChanged = true; + System.arraycopy(rules, LEFT_OF, initialRules, LEFT_OF, VERB_COUNT); + a.recycle(); } - public LayoutParams(int width, int height, float weight) { - super(width, height, weight); + public LayoutParams(int w, int h) { + super(w, h); } + /** + * {@inheritDoc} + */ + public LayoutParams(ViewGroup.LayoutParams source) { + super(source); + } + + /** + * {@inheritDoc} + */ public LayoutParams(ViewGroup.MarginLayoutParams source) { - super(source.width, source.height, source.weight); + super(source); } - public void addRule(int verb) {} - public void addRule(int verb, int anchor) {} + /** + * Copy constructor. Clones the width, height, margin values, and rules + * of the source. + * + * @param source The layout params to copy from. + */ + public LayoutParams(LayoutParams source) { + super(source); + this.mIsRtlCompatibilityMode = source.mIsRtlCompatibilityMode; + this.mRulesChanged = source.mRulesChanged; + this.alignWithParent = source.alignWithParent; + System.arraycopy(source.mRules, LEFT_OF, this.mRules, LEFT_OF, VERB_COUNT); + System.arraycopy( + source.mInitialRules, LEFT_OF, this.mInitialRules, LEFT_OF, VERB_COUNT); + } + + /** + * Adds a layout rule to be interpreted by the RelativeLayout. + *

+ * This method should only be used for verbs that don't refer to a + * sibling (ex. {@link #ALIGN_RIGHT}) or take a boolean + * value ({@link #TRUE} for true or 0 for false). To + * specify a verb that takes a subject, use {@link #addRule(int, int)}. + *

+ * If the rule is relative to the layout direction (ex. + * {@link #ALIGN_PARENT_START}), then the layout direction must be + * resolved using {@link #resolveLayoutDirection(int)} before calling + * {@link #getRule(int)} an absolute rule (ex. + * {@link #ALIGN_PARENT_LEFT}. + * + * @param verb a layout verb, such as {@link #ALIGN_PARENT_LEFT} + * @see #addRule(int, int) + * @see #removeRule(int) + * @see #getRule(int) + */ + public void addRule(int verb) { + addRule(verb, TRUE); + } + + /** + * Adds a layout rule to be interpreted by the RelativeLayout. + *

+ * Use this for verbs that refer to a sibling (ex. + * {@link #ALIGN_RIGHT}) or take a boolean value (ex. + * {@link #CENTER_IN_PARENT}). + *

+ * If the rule is relative to the layout direction (ex. + * {@link #START_OF}), then the layout direction must be resolved using + * {@link #resolveLayoutDirection(int)} before calling + * {@link #getRule(int)} with an absolute rule (ex. {@link #LEFT_OF}. + * + * @param verb a layout verb, such as {@link #ALIGN_RIGHT} + * @param subject the ID of another view to use as an anchor, or a + * boolean value (represented as {@link #TRUE} for true + * or 0 for false) + * @see #addRule(int) + * @see #removeRule(int) + * @see #getRule(int) + */ + public void addRule(int verb, int subject) { + // If we're removing a relative rule, we'll need to force layout + // resolution the next time it's requested. + if (!mNeedsLayoutResolution && isRelativeRule(verb) + && mInitialRules[verb] != 0 && subject == 0) { + mNeedsLayoutResolution = true; + } + mRules[verb] = subject; + mInitialRules[verb] = subject; + mRulesChanged = true; + } + + /** + * Removes a layout rule to be interpreted by the RelativeLayout. + *

+ * If the rule is relative to the layout direction (ex. + * {@link #START_OF}, {@link #ALIGN_PARENT_START}, etc.) then the + * layout direction must be resolved using + * {@link #resolveLayoutDirection(int)} before before calling + * {@link #getRule(int)} with an absolute rule (ex. {@link #LEFT_OF}. + * + * @param verb One of the verbs defined by + * {@link android.widget.RelativeLayout RelativeLayout}, such as + * ALIGN_WITH_PARENT_LEFT. + * @see #addRule(int) + * @see #addRule(int, int) + * @see #getRule(int) + */ + public void removeRule(int verb) { + addRule(verb, 0); + } + + /** + * Returns the layout rule associated with a specific verb. + * + * @param verb one of the verbs defined by {@link RelativeLayout}, such + * as ALIGN_WITH_PARENT_LEFT + * @return the id of another view to use as an anchor, a boolean value + * (represented as {@link RelativeLayout#TRUE} for true + * or 0 for false), or -1 for verbs that don't refer to another + * sibling (for example, ALIGN_WITH_PARENT_BOTTOM) + * @see #addRule(int) + * @see #addRule(int, int) + */ + public int getRule(int verb) { + return mRules[verb]; + } + + private boolean hasRelativeRules() { + return (mInitialRules[START_OF] != 0 || mInitialRules[END_OF] != 0 || + mInitialRules[ALIGN_START] != 0 || mInitialRules[ALIGN_END] != 0 || + mInitialRules[ALIGN_PARENT_START] != 0 || mInitialRules[ALIGN_PARENT_END] != 0); + } + + private boolean isRelativeRule(int rule) { + return rule == START_OF || rule == END_OF + || rule == ALIGN_START || rule == ALIGN_END + || rule == ALIGN_PARENT_START || rule == ALIGN_PARENT_END; + } + + // The way we are resolving rules depends on the layout direction and if we are pre JB MR1 + // or not. + // + // If we are pre JB MR1 (said as "RTL compatibility mode"), "left"/"right" rules are having + // predominance over any "start/end" rules that could have been defined. A special case: + // if no "left"/"right" rule has been defined and "start"/"end" rules are defined then we + // resolve those "start"/"end" rules to "left"/"right" respectively. + // + // If we are JB MR1+, then "start"/"end" rules are having predominance over "left"/"right" + // rules. If no "start"/"end" rule is defined then we use "left"/"right" rules. + // + // In all cases, the result of the resolution should clear the "start"/"end" rules to leave + // only the "left"/"right" rules at the end. + private void resolveRules(int layoutDirection) { + final boolean isLayoutRtl = (layoutDirection == View.LAYOUT_DIRECTION_RTL); + // Reset to initial state + System.arraycopy(mInitialRules, LEFT_OF, mRules, LEFT_OF, VERB_COUNT); + // Apply rules depending on direction and if we are in RTL compatibility mode + if (mIsRtlCompatibilityMode) { + if (mRules[ALIGN_START] != 0) { + if (mRules[ALIGN_LEFT] == 0) { + // "left" rule is not defined but "start" rule is: use the "start" rule as + // the "left" rule + mRules[ALIGN_LEFT] = mRules[ALIGN_START]; + } + mRules[ALIGN_START] = 0; + } + if (mRules[ALIGN_END] != 0) { + if (mRules[ALIGN_RIGHT] == 0) { + // "right" rule is not defined but "end" rule is: use the "end" rule as the + // "right" rule + mRules[ALIGN_RIGHT] = mRules[ALIGN_END]; + } + mRules[ALIGN_END] = 0; + } + if (mRules[START_OF] != 0) { + if (mRules[LEFT_OF] == 0) { + // "left" rule is not defined but "start" rule is: use the "start" rule as + // the "left" rule + mRules[LEFT_OF] = mRules[START_OF]; + } + mRules[START_OF] = 0; + } + if (mRules[END_OF] != 0) { + if (mRules[RIGHT_OF] == 0) { + // "right" rule is not defined but "end" rule is: use the "end" rule as the + // "right" rule + mRules[RIGHT_OF] = mRules[END_OF]; + } + mRules[END_OF] = 0; + } + if (mRules[ALIGN_PARENT_START] != 0) { + if (mRules[ALIGN_PARENT_LEFT] == 0) { + // "left" rule is not defined but "start" rule is: use the "start" rule as + // the "left" rule + mRules[ALIGN_PARENT_LEFT] = mRules[ALIGN_PARENT_START]; + } + mRules[ALIGN_PARENT_START] = 0; + } + if (mRules[ALIGN_PARENT_END] != 0) { + if (mRules[ALIGN_PARENT_RIGHT] == 0) { + // "right" rule is not defined but "end" rule is: use the "end" rule as the + // "right" rule + mRules[ALIGN_PARENT_RIGHT] = mRules[ALIGN_PARENT_END]; + } + mRules[ALIGN_PARENT_END] = 0; + } + } else { + // JB MR1+ case + if ((mRules[ALIGN_START] != 0 || mRules[ALIGN_END] != 0) && + (mRules[ALIGN_LEFT] != 0 || mRules[ALIGN_RIGHT] != 0)) { + // "start"/"end" rules take precedence over "left"/"right" rules + mRules[ALIGN_LEFT] = 0; + mRules[ALIGN_RIGHT] = 0; + } + if (mRules[ALIGN_START] != 0) { + // "start" rule resolved to "left" or "right" depending on the direction + mRules[isLayoutRtl ? ALIGN_RIGHT : ALIGN_LEFT] = mRules[ALIGN_START]; + mRules[ALIGN_START] = 0; + } + if (mRules[ALIGN_END] != 0) { + // "end" rule resolved to "left" or "right" depending on the direction + mRules[isLayoutRtl ? ALIGN_LEFT : ALIGN_RIGHT] = mRules[ALIGN_END]; + mRules[ALIGN_END] = 0; + } + if ((mRules[START_OF] != 0 || mRules[END_OF] != 0) && + (mRules[LEFT_OF] != 0 || mRules[RIGHT_OF] != 0)) { + // "start"/"end" rules take precedence over "left"/"right" rules + mRules[LEFT_OF] = 0; + mRules[RIGHT_OF] = 0; + } + if (mRules[START_OF] != 0) { + // "start" rule resolved to "left" or "right" depending on the direction + mRules[isLayoutRtl ? RIGHT_OF : LEFT_OF] = mRules[START_OF]; + mRules[START_OF] = 0; + } + if (mRules[END_OF] != 0) { + // "end" rule resolved to "left" or "right" depending on the direction + mRules[isLayoutRtl ? LEFT_OF : RIGHT_OF] = mRules[END_OF]; + mRules[END_OF] = 0; + } + if ((mRules[ALIGN_PARENT_START] != 0 || mRules[ALIGN_PARENT_END] != 0) && + (mRules[ALIGN_PARENT_LEFT] != 0 || mRules[ALIGN_PARENT_RIGHT] != 0)) { + // "start"/"end" rules take precedence over "left"/"right" rules + mRules[ALIGN_PARENT_LEFT] = 0; + mRules[ALIGN_PARENT_RIGHT] = 0; + } + if (mRules[ALIGN_PARENT_START] != 0) { + // "start" rule resolved to "left" or "right" depending on the direction + mRules[isLayoutRtl ? ALIGN_PARENT_RIGHT : ALIGN_PARENT_LEFT] = mRules[ALIGN_PARENT_START]; + mRules[ALIGN_PARENT_START] = 0; + } + if (mRules[ALIGN_PARENT_END] != 0) { + // "end" rule resolved to "left" or "right" depending on the direction + mRules[isLayoutRtl ? ALIGN_PARENT_LEFT : ALIGN_PARENT_RIGHT] = mRules[ALIGN_PARENT_END]; + mRules[ALIGN_PARENT_END] = 0; + } + } + mRulesChanged = false; + mNeedsLayoutResolution = false; + } + + /** + * Retrieves a complete list of all supported rules, where the index is the rule + * verb, and the element value is the value specified, or "false" if it was never + * set. If there are relative rules defined (*_START / *_END), they will be resolved + * depending on the layout direction. + * + * @param layoutDirection the direction of the layout. + * Should be either {@link View#LAYOUT_DIRECTION_LTR} + * or {@link View#LAYOUT_DIRECTION_RTL} + * @return the supported rules + * @see #addRule(int, int) + * + * @hide + */ + public int[] getRules(int layoutDirection) { + resolveLayoutDirection(layoutDirection); + return mRules; + } + + /** + * Retrieves a complete list of all supported rules, where the index is the rule + * verb, and the element value is the value specified, or "false" if it was never + * set. There will be no resolution of relative rules done. + * + * @return the supported rules + * @see #addRule(int, int) + */ + public int[] getRules() { + return mRules; + } + + /** + * This will be called by {@link android.view.View#requestLayout()} to + * resolve layout parameters that are relative to the layout direction. + *

+ * After this method is called, any rules using layout-relative verbs + * (ex. {@link #START_OF}) previously added via {@link #addRule(int)} + * may only be accessed via their resolved absolute verbs (ex. + * {@link #LEFT_OF}). + */ + public void resolveLayoutDirection(int layoutDirection) { + if (shouldResolveLayoutDirection(layoutDirection)) { + resolveRules(layoutDirection); + } + // This will set the layout direction. + // super.resolveLayoutDirection(layoutDirection); + } + + private boolean shouldResolveLayoutDirection(int layoutDirection) { + return (mNeedsLayoutResolution || hasRelativeRules()) + && (mRulesChanged || layoutDirection != /*getLayoutDirection()*/View.LAYOUT_DIRECTION_LTR); + } + } + private static class DependencyGraph { + /** + * List of all views in the graph. + */ + private ArrayList mNodes = new ArrayList(); + /** + * List of nodes in the graph. Each node is identified by its + * view id (see View#getId()). + */ + private SparseArray mKeyNodes = new SparseArray(); + /** + * Temporary data structure used to build the list of roots + * for this graph. + */ + private ArrayDeque mRoots = new ArrayDeque(); + + /** + * Clears the graph. + */ + void clear() { + final ArrayList nodes = mNodes; + final int count = nodes.size(); + for (int i = 0; i < count; i++) { + nodes.get(i).release(); + } + nodes.clear(); + mKeyNodes.clear(); + mRoots.clear(); + } + + /** + * Adds a view to the graph. + * + * @param view The view to be added as a node to the graph. + */ + void add(View view) { + final int id = view.getId(); + final Node node = Node.acquire(view); + if (id != View.NO_ID) { + mKeyNodes.put(id, node); + } + mNodes.add(node); + } + + /** + * Builds a sorted list of views. The sorting order depends on the dependencies + * between the view. For instance, if view C needs view A to be processed first + * and view A needs view B to be processed first, the dependency graph + * is: B -> A -> C. The sorted array will contain views B, A and C in this order. + * + * @param sorted The sorted list of views. The length of this array must + * be equal to getChildCount(). + * @param rules The list of rules to take into account. + */ + void getSortedViews(View[] sorted, int... rules) { + final ArrayDeque roots = findRoots(rules); + int index = 0; + Node node; + while ((node = roots.pollLast()) != null) { + final View view = node.view; + final int key = view.getId(); + sorted[index++] = view; + final ArrayMap dependents = node.dependents; + final int count = dependents.size(); + for (int i = 0; i < count; i++) { + final Node dependent = dependents.keyAt(i); + final SparseArray dependencies = dependent.dependencies; + dependencies.remove(key); + if (dependencies.size() == 0) { + roots.add(dependent); + } + } + } + if (index < sorted.length) { + throw new IllegalStateException("Circular dependencies cannot exist" + + " in RelativeLayout"); + } + } + + /** + * Finds the roots of the graph. A root is a node with no dependency and + * with [0..n] dependents. + * + * @param rulesFilter The list of rules to consider when building the + * dependencies + * + * @return A list of node, each being a root of the graph + */ + private ArrayDeque findRoots(int[] rulesFilter) { + final SparseArray keyNodes = mKeyNodes; + final ArrayList nodes = mNodes; + final int count = nodes.size(); + // Find roots can be invoked several times, so make sure to clear + // all dependents and dependencies before running the algorithm + for (int i = 0; i < count; i++) { + final Node node = nodes.get(i); + node.dependents.clear(); + node.dependencies.clear(); + } + // Builds up the dependents and dependencies for each node of the graph + for (int i = 0; i < count; i++) { + final Node node = nodes.get(i); + final LayoutParams layoutParams = (LayoutParams) node.view.getLayoutParams(); + final int[] rules = layoutParams.mRules; + final int rulesCount = rulesFilter.length; + // Look only the the rules passed in parameter, this way we build only the + // dependencies for a specific set of rules + for (int j = 0; j < rulesCount; j++) { + final int rule = rules[rulesFilter[j]]; + if (rule != -1) { + // The node this node depends on + final Node dependency = keyNodes.get(rule); + // Skip unknowns and self dependencies + if (dependency == null || dependency == node) { + continue; + } + // Add the current node as a dependent + dependency.dependents.put(node, this); + // Add a dependency to the current node + node.dependencies.put(rule, dependency); + } + } + } + final ArrayDeque roots = mRoots; + roots.clear(); + // Finds all the roots in the graph: all nodes with no dependencies + for (int i = 0; i < count; i++) { + final Node node = nodes.get(i); + if (node.dependencies.size() == 0) roots.addLast(node); + } + return roots; + } + + /** + * A node in the dependency graph. A node is a view, its list of dependencies + * and its list of dependents. + * + * A node with no dependent is considered a root of the graph. + */ + static class Node { + Node() { + } + /** + * The view representing this node in the layout. + */ + View view; + /** + * The list of dependents for this node; a dependent is a node + * that needs this node to be processed first. + */ + final ArrayMap dependents = + new ArrayMap(); + /** + * The list of dependencies for this node. + */ + final SparseArray dependencies = new SparseArray(); + /* + * START POOL IMPLEMENTATION + */ + // The pool is static, so all nodes instances are shared across + // activities, that's why we give it a rather high limit + private static final int POOL_LIMIT = 100; + // private static final SynchronizedPool sPool = + // new SynchronizedPool(POOL_LIMIT); + + static Node acquire(View view) { + Node node;// = sPool.acquire(); + // if (node == null) { + node = new Node(); + // } + node.view = view; + return node; + } + + void release() { + view = null; + dependents.clear(); + dependencies.clear(); + // sPool.release(this); + } + /* + * END POOL IMPLEMENTATION + */ + } } }