From 332d3d1137b6dbae579583b8fb8cb607795b925a Mon Sep 17 00:00:00 2001 From: Julian Winkler Date: Sat, 23 Nov 2024 09:36:42 +0100 Subject: [PATCH] copy AdapterView, AbsSpinner and Gallery from AOSP copied from tag android-6.0.1_r81 existing GTK based implementation was moved from AbsSpinner to Spinner --- meson.build | 2 +- .../android_widget_AbsListView.h | 14 + .../android_widget_AbsSpinner.h | 229 --- .../android_widget_Spinner.h | 243 +++ ..._AbsSpinner.c => android_widget_Spinner.c} | 8 +- src/api-impl/android/view/ViewGroup.java | 4 +- src/api-impl/android/widget/AbsListView.java | 9 +- src/api-impl/android/widget/AbsSpinner.java | 412 ++++- src/api-impl/android/widget/AdapterView.java | 1087 +++++++++++++- src/api-impl/android/widget/Gallery.java | 1322 ++++++++++++++++- src/api-impl/android/widget/ListView.java | 11 + src/api-impl/android/widget/Spinner.java | 45 + 12 files changed, 3065 insertions(+), 321 deletions(-) delete mode 100644 src/api-impl-jni/generated_headers/android_widget_AbsSpinner.h create mode 100644 src/api-impl-jni/generated_headers/android_widget_Spinner.h rename src/api-impl-jni/widgets/{android_widget_AbsSpinner.c => android_widget_Spinner.c} (86%) diff --git a/meson.build b/meson.build index ad051c6d..ac1f7508 100644 --- a/meson.build +++ b/meson.build @@ -129,7 +129,6 @@ libtranslationlayer_so = shared_library('translation_layer_main', [ 'src/api-impl-jni/widgets/android_view_SurfaceView.c', 'src/api-impl-jni/widgets/android_webkit_WebView.c', 'src/api-impl-jni/widgets/android_widget_AbsListView.c', - 'src/api-impl-jni/widgets/android_widget_AbsSpinner.c', 'src/api-impl-jni/widgets/android_widget_Button.c', 'src/api-impl-jni/widgets/android_widget_CheckBox.c', 'src/api-impl-jni/widgets/android_widget_CompoundButton.c', @@ -141,6 +140,7 @@ libtranslationlayer_so = shared_library('translation_layer_main', [ 'src/api-impl-jni/widgets/android_widget_Progressbar.c', 'src/api-impl-jni/widgets/android_widget_RadioButton.c', 'src/api-impl-jni/widgets/android_widget_ScrollView.c', + 'src/api-impl-jni/widgets/android_widget_Spinner.c', 'src/api-impl-jni/widgets/android_widget_SeekBar.c', 'src/api-impl-jni/widgets/android_widget_TextView.c', 'src/sk_area/sk_area.c', diff --git a/src/api-impl-jni/generated_headers/android_widget_AbsListView.h b/src/api-impl-jni/generated_headers/android_widget_AbsListView.h index 5554ae98..ca99658c 100644 --- a/src/api-impl-jni/generated_headers/android_widget_AbsListView.h +++ b/src/api-impl-jni/generated_headers/android_widget_AbsListView.h @@ -199,6 +199,20 @@ extern "C" { #define android_widget_AbsListView_TEXT_DIRECTION_LTR 3L #undef android_widget_AbsListView_TEXT_DIRECTION_RTL #define android_widget_AbsListView_TEXT_DIRECTION_RTL 4L +#undef android_widget_AbsListView_ITEM_VIEW_TYPE_IGNORE +#define android_widget_AbsListView_ITEM_VIEW_TYPE_IGNORE -1L +#undef android_widget_AbsListView_ITEM_VIEW_TYPE_HEADER_OR_FOOTER +#define android_widget_AbsListView_ITEM_VIEW_TYPE_HEADER_OR_FOOTER -2L +#undef android_widget_AbsListView_SYNC_SELECTED_POSITION +#define android_widget_AbsListView_SYNC_SELECTED_POSITION 0L +#undef android_widget_AbsListView_SYNC_FIRST_POSITION +#define android_widget_AbsListView_SYNC_FIRST_POSITION 1L +#undef android_widget_AbsListView_SYNC_MAX_DURATION_MILLIS +#define android_widget_AbsListView_SYNC_MAX_DURATION_MILLIS 100L +#undef android_widget_AbsListView_INVALID_POSITION +#define android_widget_AbsListView_INVALID_POSITION -1L +#undef android_widget_AbsListView_INVALID_ROW_ID +#define android_widget_AbsListView_INVALID_ROW_ID -9223372036854775808LL /* * Class: android_widget_AbsListView * Method: native_constructor diff --git a/src/api-impl-jni/generated_headers/android_widget_AbsSpinner.h b/src/api-impl-jni/generated_headers/android_widget_AbsSpinner.h deleted file mode 100644 index fc91cd74..00000000 --- a/src/api-impl-jni/generated_headers/android_widget_AbsSpinner.h +++ /dev/null @@ -1,229 +0,0 @@ -/* DO NOT EDIT THIS FILE - it is machine generated */ -#include -/* Header for class android_widget_AbsSpinner */ - -#ifndef _Included_android_widget_AbsSpinner -#define _Included_android_widget_AbsSpinner -#ifdef __cplusplus -extern "C" { -#endif -#undef android_widget_AbsSpinner_NO_ID -#define android_widget_AbsSpinner_NO_ID -1L -#undef android_widget_AbsSpinner_NOT_FOCUSABLE -#define android_widget_AbsSpinner_NOT_FOCUSABLE 0L -#undef android_widget_AbsSpinner_FOCUSABLE -#define android_widget_AbsSpinner_FOCUSABLE 1L -#undef android_widget_AbsSpinner_FOCUSABLE_MASK -#define android_widget_AbsSpinner_FOCUSABLE_MASK 1L -#undef android_widget_AbsSpinner_FITS_SYSTEM_WINDOWS -#define android_widget_AbsSpinner_FITS_SYSTEM_WINDOWS 2L -#undef android_widget_AbsSpinner_VISIBLE -#define android_widget_AbsSpinner_VISIBLE 0L -#undef android_widget_AbsSpinner_INVISIBLE -#define android_widget_AbsSpinner_INVISIBLE 4L -#undef android_widget_AbsSpinner_GONE -#define android_widget_AbsSpinner_GONE 8L -#undef android_widget_AbsSpinner_VISIBILITY_MASK -#define android_widget_AbsSpinner_VISIBILITY_MASK 12L -#undef android_widget_AbsSpinner_ENABLED -#define android_widget_AbsSpinner_ENABLED 0L -#undef android_widget_AbsSpinner_DISABLED -#define android_widget_AbsSpinner_DISABLED 32L -#undef android_widget_AbsSpinner_ENABLED_MASK -#define android_widget_AbsSpinner_ENABLED_MASK 32L -#undef android_widget_AbsSpinner_WILL_NOT_DRAW -#define android_widget_AbsSpinner_WILL_NOT_DRAW 128L -#undef android_widget_AbsSpinner_DRAW_MASK -#define android_widget_AbsSpinner_DRAW_MASK 128L -#undef android_widget_AbsSpinner_SCROLLBARS_NONE -#define android_widget_AbsSpinner_SCROLLBARS_NONE 0L -#undef android_widget_AbsSpinner_SCROLLBARS_HORIZONTAL -#define android_widget_AbsSpinner_SCROLLBARS_HORIZONTAL 256L -#undef android_widget_AbsSpinner_SCROLLBARS_VERTICAL -#define android_widget_AbsSpinner_SCROLLBARS_VERTICAL 512L -#undef android_widget_AbsSpinner_SCROLLBARS_MASK -#define android_widget_AbsSpinner_SCROLLBARS_MASK 768L -#undef android_widget_AbsSpinner_FILTER_TOUCHES_WHEN_OBSCURED -#define android_widget_AbsSpinner_FILTER_TOUCHES_WHEN_OBSCURED 1024L -#undef android_widget_AbsSpinner_OPTIONAL_FITS_SYSTEM_WINDOWS -#define android_widget_AbsSpinner_OPTIONAL_FITS_SYSTEM_WINDOWS 2048L -#undef android_widget_AbsSpinner_FADING_EDGE_NONE -#define android_widget_AbsSpinner_FADING_EDGE_NONE 0L -#undef android_widget_AbsSpinner_FADING_EDGE_HORIZONTAL -#define android_widget_AbsSpinner_FADING_EDGE_HORIZONTAL 4096L -#undef android_widget_AbsSpinner_FADING_EDGE_VERTICAL -#define android_widget_AbsSpinner_FADING_EDGE_VERTICAL 8192L -#undef android_widget_AbsSpinner_FADING_EDGE_MASK -#define android_widget_AbsSpinner_FADING_EDGE_MASK 12288L -#undef android_widget_AbsSpinner_CLICKABLE -#define android_widget_AbsSpinner_CLICKABLE 16384L -#undef android_widget_AbsSpinner_DRAWING_CACHE_ENABLED -#define android_widget_AbsSpinner_DRAWING_CACHE_ENABLED 32768L -#undef android_widget_AbsSpinner_SAVE_DISABLED -#define android_widget_AbsSpinner_SAVE_DISABLED 65536L -#undef android_widget_AbsSpinner_SAVE_DISABLED_MASK -#define android_widget_AbsSpinner_SAVE_DISABLED_MASK 65536L -#undef android_widget_AbsSpinner_WILL_NOT_CACHE_DRAWING -#define android_widget_AbsSpinner_WILL_NOT_CACHE_DRAWING 131072L -#undef android_widget_AbsSpinner_FOCUSABLE_IN_TOUCH_MODE -#define android_widget_AbsSpinner_FOCUSABLE_IN_TOUCH_MODE 262144L -#undef android_widget_AbsSpinner_DRAWING_CACHE_QUALITY_LOW -#define android_widget_AbsSpinner_DRAWING_CACHE_QUALITY_LOW 524288L -#undef android_widget_AbsSpinner_DRAWING_CACHE_QUALITY_HIGH -#define android_widget_AbsSpinner_DRAWING_CACHE_QUALITY_HIGH 1048576L -#undef android_widget_AbsSpinner_DRAWING_CACHE_QUALITY_AUTO -#define android_widget_AbsSpinner_DRAWING_CACHE_QUALITY_AUTO 0L -#undef android_widget_AbsSpinner_DRAWING_CACHE_QUALITY_MASK -#define android_widget_AbsSpinner_DRAWING_CACHE_QUALITY_MASK 1572864L -#undef android_widget_AbsSpinner_LONG_CLICKABLE -#define android_widget_AbsSpinner_LONG_CLICKABLE 2097152L -#undef android_widget_AbsSpinner_DUPLICATE_PARENT_STATE -#define android_widget_AbsSpinner_DUPLICATE_PARENT_STATE 4194304L -#undef android_widget_AbsSpinner_SCROLLBARS_INSIDE_OVERLAY -#define android_widget_AbsSpinner_SCROLLBARS_INSIDE_OVERLAY 0L -#undef android_widget_AbsSpinner_SCROLLBARS_INSIDE_INSET -#define android_widget_AbsSpinner_SCROLLBARS_INSIDE_INSET 16777216L -#undef android_widget_AbsSpinner_SCROLLBARS_OUTSIDE_OVERLAY -#define android_widget_AbsSpinner_SCROLLBARS_OUTSIDE_OVERLAY 33554432L -#undef android_widget_AbsSpinner_SCROLLBARS_OUTSIDE_INSET -#define android_widget_AbsSpinner_SCROLLBARS_OUTSIDE_INSET 50331648L -#undef android_widget_AbsSpinner_SCROLLBARS_INSET_MASK -#define android_widget_AbsSpinner_SCROLLBARS_INSET_MASK 16777216L -#undef android_widget_AbsSpinner_SCROLLBARS_OUTSIDE_MASK -#define android_widget_AbsSpinner_SCROLLBARS_OUTSIDE_MASK 33554432L -#undef android_widget_AbsSpinner_SCROLLBARS_STYLE_MASK -#define android_widget_AbsSpinner_SCROLLBARS_STYLE_MASK 50331648L -#undef android_widget_AbsSpinner_KEEP_SCREEN_ON -#define android_widget_AbsSpinner_KEEP_SCREEN_ON 67108864L -#undef android_widget_AbsSpinner_SOUND_EFFECTS_ENABLED -#define android_widget_AbsSpinner_SOUND_EFFECTS_ENABLED 134217728L -#undef android_widget_AbsSpinner_HAPTIC_FEEDBACK_ENABLED -#define android_widget_AbsSpinner_HAPTIC_FEEDBACK_ENABLED 268435456L -#undef android_widget_AbsSpinner_PARENT_SAVE_DISABLED -#define android_widget_AbsSpinner_PARENT_SAVE_DISABLED 536870912L -#undef android_widget_AbsSpinner_PARENT_SAVE_DISABLED_MASK -#define android_widget_AbsSpinner_PARENT_SAVE_DISABLED_MASK 536870912L -#undef android_widget_AbsSpinner_FOCUSABLES_ALL -#define android_widget_AbsSpinner_FOCUSABLES_ALL 0L -#undef android_widget_AbsSpinner_FOCUSABLES_TOUCH_MODE -#define android_widget_AbsSpinner_FOCUSABLES_TOUCH_MODE 1L -#undef android_widget_AbsSpinner_FOCUS_BACKWARD -#define android_widget_AbsSpinner_FOCUS_BACKWARD 1L -#undef android_widget_AbsSpinner_FOCUS_FORWARD -#define android_widget_AbsSpinner_FOCUS_FORWARD 2L -#undef android_widget_AbsSpinner_FOCUS_LEFT -#define android_widget_AbsSpinner_FOCUS_LEFT 17L -#undef android_widget_AbsSpinner_FOCUS_UP -#define android_widget_AbsSpinner_FOCUS_UP 33L -#undef android_widget_AbsSpinner_FOCUS_RIGHT -#define android_widget_AbsSpinner_FOCUS_RIGHT 66L -#undef android_widget_AbsSpinner_FOCUS_DOWN -#define android_widget_AbsSpinner_FOCUS_DOWN 130L -#undef android_widget_AbsSpinner_MEASURED_SIZE_MASK -#define android_widget_AbsSpinner_MEASURED_SIZE_MASK 16777215L -#undef android_widget_AbsSpinner_MEASURED_STATE_MASK -#define android_widget_AbsSpinner_MEASURED_STATE_MASK -16777216L -#undef android_widget_AbsSpinner_MEASURED_HEIGHT_STATE_SHIFT -#define android_widget_AbsSpinner_MEASURED_HEIGHT_STATE_SHIFT 16L -#undef android_widget_AbsSpinner_MEASURED_STATE_TOO_SMALL -#define android_widget_AbsSpinner_MEASURED_STATE_TOO_SMALL 16777216L -#undef android_widget_AbsSpinner_PFLAG2_DRAG_CAN_ACCEPT -#define android_widget_AbsSpinner_PFLAG2_DRAG_CAN_ACCEPT 1L -#undef android_widget_AbsSpinner_PFLAG2_DRAG_HOVERED -#define android_widget_AbsSpinner_PFLAG2_DRAG_HOVERED 2L -#undef android_widget_AbsSpinner_LAYOUT_DIRECTION_LTR -#define android_widget_AbsSpinner_LAYOUT_DIRECTION_LTR 0L -#undef android_widget_AbsSpinner_LAYOUT_DIRECTION_RTL -#define android_widget_AbsSpinner_LAYOUT_DIRECTION_RTL 1L -#undef android_widget_AbsSpinner_LAYOUT_DIRECTION_INHERIT -#define android_widget_AbsSpinner_LAYOUT_DIRECTION_INHERIT 2L -#undef android_widget_AbsSpinner_LAYOUT_DIRECTION_LOCALE -#define android_widget_AbsSpinner_LAYOUT_DIRECTION_LOCALE 3L -#undef android_widget_AbsSpinner_PFLAG2_LAYOUT_DIRECTION_MASK_SHIFT -#define android_widget_AbsSpinner_PFLAG2_LAYOUT_DIRECTION_MASK_SHIFT 2L -#undef android_widget_AbsSpinner_PFLAG2_LAYOUT_DIRECTION_MASK -#define android_widget_AbsSpinner_PFLAG2_LAYOUT_DIRECTION_MASK 12L -#undef android_widget_AbsSpinner_PFLAG2_LAYOUT_DIRECTION_RESOLVED_RTL -#define android_widget_AbsSpinner_PFLAG2_LAYOUT_DIRECTION_RESOLVED_RTL 16L -#undef android_widget_AbsSpinner_PFLAG2_LAYOUT_DIRECTION_RESOLVED -#define android_widget_AbsSpinner_PFLAG2_LAYOUT_DIRECTION_RESOLVED 32L -#undef android_widget_AbsSpinner_PFLAG2_LAYOUT_DIRECTION_RESOLVED_MASK -#define android_widget_AbsSpinner_PFLAG2_LAYOUT_DIRECTION_RESOLVED_MASK 48L -#undef android_widget_AbsSpinner_STATUS_BAR_HIDDEN -#define android_widget_AbsSpinner_STATUS_BAR_HIDDEN 1L -#undef android_widget_AbsSpinner_STATUS_BAR_VISIBLE -#define android_widget_AbsSpinner_STATUS_BAR_VISIBLE 0L -#undef android_widget_AbsSpinner_SYSTEM_UI_FLAG_FULLSCREEN -#define android_widget_AbsSpinner_SYSTEM_UI_FLAG_FULLSCREEN 4L -#undef android_widget_AbsSpinner_SYSTEM_UI_FLAG_HIDE_NAVIGATION -#define android_widget_AbsSpinner_SYSTEM_UI_FLAG_HIDE_NAVIGATION 2L -#undef android_widget_AbsSpinner_SYSTEM_UI_FLAG_IMMERSIVE -#define android_widget_AbsSpinner_SYSTEM_UI_FLAG_IMMERSIVE 2048L -#undef android_widget_AbsSpinner_SYSTEM_UI_FLAG_IMMERSIVE_STICKY -#define android_widget_AbsSpinner_SYSTEM_UI_FLAG_IMMERSIVE_STICKY 4096L -#undef android_widget_AbsSpinner_SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN -#define android_widget_AbsSpinner_SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN 1024L -#undef android_widget_AbsSpinner_SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION -#define android_widget_AbsSpinner_SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION 512L -#undef android_widget_AbsSpinner_SYSTEM_UI_FLAG_LAYOUT_STABLE -#define android_widget_AbsSpinner_SYSTEM_UI_FLAG_LAYOUT_STABLE 256L -#undef android_widget_AbsSpinner_SYSTEM_UI_FLAG_LOW_PROFILE -#define android_widget_AbsSpinner_SYSTEM_UI_FLAG_LOW_PROFILE 1L -#undef android_widget_AbsSpinner_SYSTEM_UI_FLAG_VISIBLE -#define android_widget_AbsSpinner_SYSTEM_UI_FLAG_VISIBLE 0L -#undef android_widget_AbsSpinner_SYSTEM_UI_LAYOUT_FLAGS -#define android_widget_AbsSpinner_SYSTEM_UI_LAYOUT_FLAGS 1536L -#undef android_widget_AbsSpinner_TEXT_ALIGNMENT_CENTER -#define android_widget_AbsSpinner_TEXT_ALIGNMENT_CENTER 4L -#undef android_widget_AbsSpinner_TEXT_ALIGNMENT_GRAVITY -#define android_widget_AbsSpinner_TEXT_ALIGNMENT_GRAVITY 1L -#undef android_widget_AbsSpinner_TEXT_ALIGNMENT_INHERIT -#define android_widget_AbsSpinner_TEXT_ALIGNMENT_INHERIT 0L -#undef android_widget_AbsSpinner_TEXT_ALIGNMENT_TEXT_END -#define android_widget_AbsSpinner_TEXT_ALIGNMENT_TEXT_END 3L -#undef android_widget_AbsSpinner_TEXT_ALIGNMENT_TEXT_START -#define android_widget_AbsSpinner_TEXT_ALIGNMENT_TEXT_START 2L -#undef android_widget_AbsSpinner_TEXT_ALIGNMENT_VIEW_END -#define android_widget_AbsSpinner_TEXT_ALIGNMENT_VIEW_END 6L -#undef android_widget_AbsSpinner_TEXT_ALIGNMENT_VIEW_START -#define android_widget_AbsSpinner_TEXT_ALIGNMENT_VIEW_START 5L -#undef android_widget_AbsSpinner_TEXT_DIRECTION_ANY_RTL -#define android_widget_AbsSpinner_TEXT_DIRECTION_ANY_RTL 2L -#undef android_widget_AbsSpinner_TEXT_DIRECTION_FIRST_STRONG -#define android_widget_AbsSpinner_TEXT_DIRECTION_FIRST_STRONG 1L -#undef android_widget_AbsSpinner_TEXT_DIRECTION_INHERIT -#define android_widget_AbsSpinner_TEXT_DIRECTION_INHERIT 0L -#undef android_widget_AbsSpinner_TEXT_DIRECTION_LOCALE -#define android_widget_AbsSpinner_TEXT_DIRECTION_LOCALE 5L -#undef android_widget_AbsSpinner_TEXT_DIRECTION_LTR -#define android_widget_AbsSpinner_TEXT_DIRECTION_LTR 3L -#undef android_widget_AbsSpinner_TEXT_DIRECTION_RTL -#define android_widget_AbsSpinner_TEXT_DIRECTION_RTL 4L -/* - * Class: android_widget_AbsSpinner - * Method: native_constructor - * Signature: (Landroid/content/Context;Landroid/util/AttributeSet;)J - */ -JNIEXPORT jlong JNICALL Java_android_widget_AbsSpinner_native_1constructor - (JNIEnv *, jobject, jobject, jobject); - -/* - * Class: android_widget_AbsSpinner - * Method: native_setAdapter - * Signature: (JLandroid/widget/SpinnerAdapter;)V - */ -JNIEXPORT void JNICALL Java_android_widget_AbsSpinner_native_1setAdapter - (JNIEnv *, jobject, jlong, jobject); - -/* - * Class: android_widget_AbsSpinner - * Method: setOnItemSelectedListener - * Signature: (Landroid/widget/AdapterView/OnItemSelectedListener;)V - */ -JNIEXPORT void JNICALL Java_android_widget_AbsSpinner_setOnItemSelectedListener - (JNIEnv *, jobject, jobject); - -#ifdef __cplusplus -} -#endif -#endif diff --git a/src/api-impl-jni/generated_headers/android_widget_Spinner.h b/src/api-impl-jni/generated_headers/android_widget_Spinner.h new file mode 100644 index 00000000..99f4037b --- /dev/null +++ b/src/api-impl-jni/generated_headers/android_widget_Spinner.h @@ -0,0 +1,243 @@ +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include +/* Header for class android_widget_Spinner */ + +#ifndef _Included_android_widget_Spinner +#define _Included_android_widget_Spinner +#ifdef __cplusplus +extern "C" { +#endif +#undef android_widget_Spinner_NO_ID +#define android_widget_Spinner_NO_ID -1L +#undef android_widget_Spinner_NOT_FOCUSABLE +#define android_widget_Spinner_NOT_FOCUSABLE 0L +#undef android_widget_Spinner_FOCUSABLE +#define android_widget_Spinner_FOCUSABLE 1L +#undef android_widget_Spinner_FOCUSABLE_MASK +#define android_widget_Spinner_FOCUSABLE_MASK 1L +#undef android_widget_Spinner_FITS_SYSTEM_WINDOWS +#define android_widget_Spinner_FITS_SYSTEM_WINDOWS 2L +#undef android_widget_Spinner_VISIBLE +#define android_widget_Spinner_VISIBLE 0L +#undef android_widget_Spinner_INVISIBLE +#define android_widget_Spinner_INVISIBLE 4L +#undef android_widget_Spinner_GONE +#define android_widget_Spinner_GONE 8L +#undef android_widget_Spinner_VISIBILITY_MASK +#define android_widget_Spinner_VISIBILITY_MASK 12L +#undef android_widget_Spinner_ENABLED +#define android_widget_Spinner_ENABLED 0L +#undef android_widget_Spinner_DISABLED +#define android_widget_Spinner_DISABLED 32L +#undef android_widget_Spinner_ENABLED_MASK +#define android_widget_Spinner_ENABLED_MASK 32L +#undef android_widget_Spinner_WILL_NOT_DRAW +#define android_widget_Spinner_WILL_NOT_DRAW 128L +#undef android_widget_Spinner_DRAW_MASK +#define android_widget_Spinner_DRAW_MASK 128L +#undef android_widget_Spinner_SCROLLBARS_NONE +#define android_widget_Spinner_SCROLLBARS_NONE 0L +#undef android_widget_Spinner_SCROLLBARS_HORIZONTAL +#define android_widget_Spinner_SCROLLBARS_HORIZONTAL 256L +#undef android_widget_Spinner_SCROLLBARS_VERTICAL +#define android_widget_Spinner_SCROLLBARS_VERTICAL 512L +#undef android_widget_Spinner_SCROLLBARS_MASK +#define android_widget_Spinner_SCROLLBARS_MASK 768L +#undef android_widget_Spinner_FILTER_TOUCHES_WHEN_OBSCURED +#define android_widget_Spinner_FILTER_TOUCHES_WHEN_OBSCURED 1024L +#undef android_widget_Spinner_OPTIONAL_FITS_SYSTEM_WINDOWS +#define android_widget_Spinner_OPTIONAL_FITS_SYSTEM_WINDOWS 2048L +#undef android_widget_Spinner_FADING_EDGE_NONE +#define android_widget_Spinner_FADING_EDGE_NONE 0L +#undef android_widget_Spinner_FADING_EDGE_HORIZONTAL +#define android_widget_Spinner_FADING_EDGE_HORIZONTAL 4096L +#undef android_widget_Spinner_FADING_EDGE_VERTICAL +#define android_widget_Spinner_FADING_EDGE_VERTICAL 8192L +#undef android_widget_Spinner_FADING_EDGE_MASK +#define android_widget_Spinner_FADING_EDGE_MASK 12288L +#undef android_widget_Spinner_CLICKABLE +#define android_widget_Spinner_CLICKABLE 16384L +#undef android_widget_Spinner_DRAWING_CACHE_ENABLED +#define android_widget_Spinner_DRAWING_CACHE_ENABLED 32768L +#undef android_widget_Spinner_SAVE_DISABLED +#define android_widget_Spinner_SAVE_DISABLED 65536L +#undef android_widget_Spinner_SAVE_DISABLED_MASK +#define android_widget_Spinner_SAVE_DISABLED_MASK 65536L +#undef android_widget_Spinner_WILL_NOT_CACHE_DRAWING +#define android_widget_Spinner_WILL_NOT_CACHE_DRAWING 131072L +#undef android_widget_Spinner_FOCUSABLE_IN_TOUCH_MODE +#define android_widget_Spinner_FOCUSABLE_IN_TOUCH_MODE 262144L +#undef android_widget_Spinner_DRAWING_CACHE_QUALITY_LOW +#define android_widget_Spinner_DRAWING_CACHE_QUALITY_LOW 524288L +#undef android_widget_Spinner_DRAWING_CACHE_QUALITY_HIGH +#define android_widget_Spinner_DRAWING_CACHE_QUALITY_HIGH 1048576L +#undef android_widget_Spinner_DRAWING_CACHE_QUALITY_AUTO +#define android_widget_Spinner_DRAWING_CACHE_QUALITY_AUTO 0L +#undef android_widget_Spinner_DRAWING_CACHE_QUALITY_MASK +#define android_widget_Spinner_DRAWING_CACHE_QUALITY_MASK 1572864L +#undef android_widget_Spinner_LONG_CLICKABLE +#define android_widget_Spinner_LONG_CLICKABLE 2097152L +#undef android_widget_Spinner_DUPLICATE_PARENT_STATE +#define android_widget_Spinner_DUPLICATE_PARENT_STATE 4194304L +#undef android_widget_Spinner_SCROLLBARS_INSIDE_OVERLAY +#define android_widget_Spinner_SCROLLBARS_INSIDE_OVERLAY 0L +#undef android_widget_Spinner_SCROLLBARS_INSIDE_INSET +#define android_widget_Spinner_SCROLLBARS_INSIDE_INSET 16777216L +#undef android_widget_Spinner_SCROLLBARS_OUTSIDE_OVERLAY +#define android_widget_Spinner_SCROLLBARS_OUTSIDE_OVERLAY 33554432L +#undef android_widget_Spinner_SCROLLBARS_OUTSIDE_INSET +#define android_widget_Spinner_SCROLLBARS_OUTSIDE_INSET 50331648L +#undef android_widget_Spinner_SCROLLBARS_INSET_MASK +#define android_widget_Spinner_SCROLLBARS_INSET_MASK 16777216L +#undef android_widget_Spinner_SCROLLBARS_OUTSIDE_MASK +#define android_widget_Spinner_SCROLLBARS_OUTSIDE_MASK 33554432L +#undef android_widget_Spinner_SCROLLBARS_STYLE_MASK +#define android_widget_Spinner_SCROLLBARS_STYLE_MASK 50331648L +#undef android_widget_Spinner_KEEP_SCREEN_ON +#define android_widget_Spinner_KEEP_SCREEN_ON 67108864L +#undef android_widget_Spinner_SOUND_EFFECTS_ENABLED +#define android_widget_Spinner_SOUND_EFFECTS_ENABLED 134217728L +#undef android_widget_Spinner_HAPTIC_FEEDBACK_ENABLED +#define android_widget_Spinner_HAPTIC_FEEDBACK_ENABLED 268435456L +#undef android_widget_Spinner_PARENT_SAVE_DISABLED +#define android_widget_Spinner_PARENT_SAVE_DISABLED 536870912L +#undef android_widget_Spinner_PARENT_SAVE_DISABLED_MASK +#define android_widget_Spinner_PARENT_SAVE_DISABLED_MASK 536870912L +#undef android_widget_Spinner_FOCUSABLES_ALL +#define android_widget_Spinner_FOCUSABLES_ALL 0L +#undef android_widget_Spinner_FOCUSABLES_TOUCH_MODE +#define android_widget_Spinner_FOCUSABLES_TOUCH_MODE 1L +#undef android_widget_Spinner_FOCUS_BACKWARD +#define android_widget_Spinner_FOCUS_BACKWARD 1L +#undef android_widget_Spinner_FOCUS_FORWARD +#define android_widget_Spinner_FOCUS_FORWARD 2L +#undef android_widget_Spinner_FOCUS_LEFT +#define android_widget_Spinner_FOCUS_LEFT 17L +#undef android_widget_Spinner_FOCUS_UP +#define android_widget_Spinner_FOCUS_UP 33L +#undef android_widget_Spinner_FOCUS_RIGHT +#define android_widget_Spinner_FOCUS_RIGHT 66L +#undef android_widget_Spinner_FOCUS_DOWN +#define android_widget_Spinner_FOCUS_DOWN 130L +#undef android_widget_Spinner_MEASURED_SIZE_MASK +#define android_widget_Spinner_MEASURED_SIZE_MASK 16777215L +#undef android_widget_Spinner_MEASURED_STATE_MASK +#define android_widget_Spinner_MEASURED_STATE_MASK -16777216L +#undef android_widget_Spinner_MEASURED_HEIGHT_STATE_SHIFT +#define android_widget_Spinner_MEASURED_HEIGHT_STATE_SHIFT 16L +#undef android_widget_Spinner_MEASURED_STATE_TOO_SMALL +#define android_widget_Spinner_MEASURED_STATE_TOO_SMALL 16777216L +#undef android_widget_Spinner_PFLAG2_DRAG_CAN_ACCEPT +#define android_widget_Spinner_PFLAG2_DRAG_CAN_ACCEPT 1L +#undef android_widget_Spinner_PFLAG2_DRAG_HOVERED +#define android_widget_Spinner_PFLAG2_DRAG_HOVERED 2L +#undef android_widget_Spinner_LAYOUT_DIRECTION_LTR +#define android_widget_Spinner_LAYOUT_DIRECTION_LTR 0L +#undef android_widget_Spinner_LAYOUT_DIRECTION_RTL +#define android_widget_Spinner_LAYOUT_DIRECTION_RTL 1L +#undef android_widget_Spinner_LAYOUT_DIRECTION_INHERIT +#define android_widget_Spinner_LAYOUT_DIRECTION_INHERIT 2L +#undef android_widget_Spinner_LAYOUT_DIRECTION_LOCALE +#define android_widget_Spinner_LAYOUT_DIRECTION_LOCALE 3L +#undef android_widget_Spinner_PFLAG2_LAYOUT_DIRECTION_MASK_SHIFT +#define android_widget_Spinner_PFLAG2_LAYOUT_DIRECTION_MASK_SHIFT 2L +#undef android_widget_Spinner_PFLAG2_LAYOUT_DIRECTION_MASK +#define android_widget_Spinner_PFLAG2_LAYOUT_DIRECTION_MASK 12L +#undef android_widget_Spinner_PFLAG2_LAYOUT_DIRECTION_RESOLVED_RTL +#define android_widget_Spinner_PFLAG2_LAYOUT_DIRECTION_RESOLVED_RTL 16L +#undef android_widget_Spinner_PFLAG2_LAYOUT_DIRECTION_RESOLVED +#define android_widget_Spinner_PFLAG2_LAYOUT_DIRECTION_RESOLVED 32L +#undef android_widget_Spinner_PFLAG2_LAYOUT_DIRECTION_RESOLVED_MASK +#define android_widget_Spinner_PFLAG2_LAYOUT_DIRECTION_RESOLVED_MASK 48L +#undef android_widget_Spinner_STATUS_BAR_HIDDEN +#define android_widget_Spinner_STATUS_BAR_HIDDEN 1L +#undef android_widget_Spinner_STATUS_BAR_VISIBLE +#define android_widget_Spinner_STATUS_BAR_VISIBLE 0L +#undef android_widget_Spinner_SYSTEM_UI_FLAG_FULLSCREEN +#define android_widget_Spinner_SYSTEM_UI_FLAG_FULLSCREEN 4L +#undef android_widget_Spinner_SYSTEM_UI_FLAG_HIDE_NAVIGATION +#define android_widget_Spinner_SYSTEM_UI_FLAG_HIDE_NAVIGATION 2L +#undef android_widget_Spinner_SYSTEM_UI_FLAG_IMMERSIVE +#define android_widget_Spinner_SYSTEM_UI_FLAG_IMMERSIVE 2048L +#undef android_widget_Spinner_SYSTEM_UI_FLAG_IMMERSIVE_STICKY +#define android_widget_Spinner_SYSTEM_UI_FLAG_IMMERSIVE_STICKY 4096L +#undef android_widget_Spinner_SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN +#define android_widget_Spinner_SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN 1024L +#undef android_widget_Spinner_SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION +#define android_widget_Spinner_SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION 512L +#undef android_widget_Spinner_SYSTEM_UI_FLAG_LAYOUT_STABLE +#define android_widget_Spinner_SYSTEM_UI_FLAG_LAYOUT_STABLE 256L +#undef android_widget_Spinner_SYSTEM_UI_FLAG_LOW_PROFILE +#define android_widget_Spinner_SYSTEM_UI_FLAG_LOW_PROFILE 1L +#undef android_widget_Spinner_SYSTEM_UI_FLAG_VISIBLE +#define android_widget_Spinner_SYSTEM_UI_FLAG_VISIBLE 0L +#undef android_widget_Spinner_SYSTEM_UI_LAYOUT_FLAGS +#define android_widget_Spinner_SYSTEM_UI_LAYOUT_FLAGS 1536L +#undef android_widget_Spinner_TEXT_ALIGNMENT_CENTER +#define android_widget_Spinner_TEXT_ALIGNMENT_CENTER 4L +#undef android_widget_Spinner_TEXT_ALIGNMENT_GRAVITY +#define android_widget_Spinner_TEXT_ALIGNMENT_GRAVITY 1L +#undef android_widget_Spinner_TEXT_ALIGNMENT_INHERIT +#define android_widget_Spinner_TEXT_ALIGNMENT_INHERIT 0L +#undef android_widget_Spinner_TEXT_ALIGNMENT_TEXT_END +#define android_widget_Spinner_TEXT_ALIGNMENT_TEXT_END 3L +#undef android_widget_Spinner_TEXT_ALIGNMENT_TEXT_START +#define android_widget_Spinner_TEXT_ALIGNMENT_TEXT_START 2L +#undef android_widget_Spinner_TEXT_ALIGNMENT_VIEW_END +#define android_widget_Spinner_TEXT_ALIGNMENT_VIEW_END 6L +#undef android_widget_Spinner_TEXT_ALIGNMENT_VIEW_START +#define android_widget_Spinner_TEXT_ALIGNMENT_VIEW_START 5L +#undef android_widget_Spinner_TEXT_DIRECTION_ANY_RTL +#define android_widget_Spinner_TEXT_DIRECTION_ANY_RTL 2L +#undef android_widget_Spinner_TEXT_DIRECTION_FIRST_STRONG +#define android_widget_Spinner_TEXT_DIRECTION_FIRST_STRONG 1L +#undef android_widget_Spinner_TEXT_DIRECTION_INHERIT +#define android_widget_Spinner_TEXT_DIRECTION_INHERIT 0L +#undef android_widget_Spinner_TEXT_DIRECTION_LOCALE +#define android_widget_Spinner_TEXT_DIRECTION_LOCALE 5L +#undef android_widget_Spinner_TEXT_DIRECTION_LTR +#define android_widget_Spinner_TEXT_DIRECTION_LTR 3L +#undef android_widget_Spinner_TEXT_DIRECTION_RTL +#define android_widget_Spinner_TEXT_DIRECTION_RTL 4L +#undef android_widget_Spinner_ITEM_VIEW_TYPE_IGNORE +#define android_widget_Spinner_ITEM_VIEW_TYPE_IGNORE -1L +#undef android_widget_Spinner_ITEM_VIEW_TYPE_HEADER_OR_FOOTER +#define android_widget_Spinner_ITEM_VIEW_TYPE_HEADER_OR_FOOTER -2L +#undef android_widget_Spinner_SYNC_SELECTED_POSITION +#define android_widget_Spinner_SYNC_SELECTED_POSITION 0L +#undef android_widget_Spinner_SYNC_FIRST_POSITION +#define android_widget_Spinner_SYNC_FIRST_POSITION 1L +#undef android_widget_Spinner_SYNC_MAX_DURATION_MILLIS +#define android_widget_Spinner_SYNC_MAX_DURATION_MILLIS 100L +#undef android_widget_Spinner_INVALID_POSITION +#define android_widget_Spinner_INVALID_POSITION -1L +#undef android_widget_Spinner_INVALID_ROW_ID +#define android_widget_Spinner_INVALID_ROW_ID -9223372036854775808LL +/* + * Class: android_widget_Spinner + * Method: native_constructor + * Signature: (Landroid/content/Context;Landroid/util/AttributeSet;)J + */ +JNIEXPORT jlong JNICALL Java_android_widget_Spinner_native_1constructor + (JNIEnv *, jobject, jobject, jobject); + +/* + * Class: android_widget_Spinner + * Method: native_setAdapter + * Signature: (JLandroid/widget/SpinnerAdapter;)V + */ +JNIEXPORT void JNICALL Java_android_widget_Spinner_native_1setAdapter + (JNIEnv *, jobject, jlong, jobject); + +/* + * Class: android_widget_Spinner + * Method: setOnItemSelectedListener + * Signature: (Landroid/widget/AdapterView/OnItemSelectedListener;)V + */ +JNIEXPORT void JNICALL Java_android_widget_Spinner_setOnItemSelectedListener + (JNIEnv *, jobject, jobject); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/src/api-impl-jni/widgets/android_widget_AbsSpinner.c b/src/api-impl-jni/widgets/android_widget_Spinner.c similarity index 86% rename from src/api-impl-jni/widgets/android_widget_AbsSpinner.c rename to src/api-impl-jni/widgets/android_widget_Spinner.c index b1f3d402..f06378e1 100644 --- a/src/api-impl-jni/widgets/android_widget_AbsSpinner.c +++ b/src/api-impl-jni/widgets/android_widget_Spinner.c @@ -6,7 +6,7 @@ #include "WrapperWidget.h" #include "AdapterView.h" -#include "../generated_headers/android_widget_AbsSpinner.h" +#include "../generated_headers/android_widget_Spinner.h" static void bind_listitem_cb(GtkListItemFactory *factory, GtkListItem *list_item) { @@ -27,7 +27,7 @@ static void bind_listitem_cb(GtkListItemFactory *factory, GtkListItem *list_item gtk_list_item_set_child(list_item, child); } -JNIEXPORT jlong JNICALL Java_android_widget_AbsSpinner_native_1constructor(JNIEnv *env, jobject this, jobject context, jobject attrs) +JNIEXPORT jlong JNICALL Java_android_widget_Spinner_native_1constructor(JNIEnv *env, jobject this, jobject context, jobject attrs) { GtkWidget *wrapper = g_object_ref(wrapper_widget_new()); GtkListItemFactory *factory = gtk_signal_list_item_factory_new(); @@ -42,7 +42,7 @@ JNIEXPORT jlong JNICALL Java_android_widget_AbsSpinner_native_1constructor(JNIEn return _INTPTR(dropdown); } -JNIEXPORT void JNICALL Java_android_widget_AbsSpinner_native_1setAdapter(JNIEnv *env, jobject this, jlong widget_ptr, jobject adapter) +JNIEXPORT void JNICALL Java_android_widget_Spinner_native_1setAdapter(JNIEnv *env, jobject this, jlong widget_ptr, jobject adapter) { GtkDropDown *dropdown = GTK_DROP_DOWN(_PTR(widget_ptr)); RangeListModel *model = RANGE_LIST_MODEL(gtk_drop_down_get_model(dropdown)); @@ -64,7 +64,7 @@ static void on_selected_changed(GtkDropDown *dropdown, GParamSpec *pspec, jobjec (*env)->CallVoidMethod(env, listener, onItemSelected, model->jobject, NULL, index, (long)0); } -JNIEXPORT void JNICALL Java_android_widget_AbsSpinner_setOnItemSelectedListener(JNIEnv *env, jobject this, jobject listener) +JNIEXPORT void JNICALL Java_android_widget_Spinner_setOnItemSelectedListener(JNIEnv *env, jobject this, jobject listener) { GtkDropDown *dropdown = GTK_DROP_DOWN(_PTR(_GET_LONG_FIELD(this, "widget"))); g_signal_connect(dropdown, "notify::selected", G_CALLBACK(on_selected_changed), _REF(listener)); diff --git a/src/api-impl/android/view/ViewGroup.java b/src/api-impl/android/view/ViewGroup.java index 06a9c999..2f64f93c 100644 --- a/src/api-impl/android/view/ViewGroup.java +++ b/src/api-impl/android/view/ViewGroup.java @@ -127,7 +127,7 @@ public class ViewGroup extends View implements ViewParent, ViewManager { } public void detachViewFromParent(int index) { - removeViewAt(index); + removeViewInternal(children.get(index)); } public void attachViewToParent(View view, int index, LayoutParams params) { @@ -367,7 +367,7 @@ public class ViewGroup extends View implements ViewParent, ViewManager { public void setClipChildren(boolean clipChildren) {} - public void dispatchSetPressed(boolean pressed) {} + protected void dispatchSetPressed(boolean pressed) {} @Override public View findViewWithTag(Object tag) { diff --git a/src/api-impl/android/widget/AbsListView.java b/src/api-impl/android/widget/AbsListView.java index 21adbfa1..72249e45 100644 --- a/src/api-impl/android/widget/AbsListView.java +++ b/src/api-impl/android/widget/AbsListView.java @@ -5,10 +5,11 @@ import android.database.DataSetObserver; import android.util.AttributeSet; import android.view.ViewGroup; -public abstract class AbsListView extends AdapterView { +public abstract class AbsListView extends AdapterView { public boolean mIsChildViewEnabled = false; // this field gets directly accessed by androidx DropDownListView protected Observer observer = new Observer(); + private ListAdapter adapter; public AbsListView(Context context) { super(context); @@ -35,7 +36,7 @@ public abstract class AbsListView extends AdapterView { ListAdapter oldAdapter = getAdapter(); if (oldAdapter != null) oldAdapter.unregisterDataSetObserver(observer); - super.setAdapter(adapter); + this.adapter = adapter; if (adapter != null) adapter.registerDataSetObserver(observer); native_setAdapter(this.widget, adapter); @@ -55,7 +56,7 @@ public abstract class AbsListView extends AdapterView { public int getListPaddingBottom() {return paddingBottom;} public ListAdapter getAdapter() { - return (ListAdapter) super.getAdapter(); + return adapter; } public int pointToPosition(int x, int y) { @@ -79,9 +80,7 @@ public abstract class AbsListView extends AdapterView { } private int pendingSelection = -1; - @Override public void setSelection(int position, boolean animate) { - super.setSelection(position, animate); native_scrollTo(this.widget, position); if (getWidth() > 0 && getHeight() > 0) native_scrollTo(AbsListView.this.widget, position); diff --git a/src/api-impl/android/widget/AbsSpinner.java b/src/api-impl/android/widget/AbsSpinner.java index 2de2deac..b6aafa47 100644 --- a/src/api-impl/android/widget/AbsSpinner.java +++ b/src/api-impl/android/widget/AbsSpinner.java @@ -1,57 +1,391 @@ +/* + * 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 android.content.Context; -import android.database.DataSetObserver; -import android.util.AttributeSet; +import com.android.internal.R; -public abstract class AbsSpinner extends AdapterView { - private Observer observer = new Observer(); +import android.content.Context; +import android.content.res.TypedArray; +import android.database.DataSetObserver; +import android.graphics.Rect; +import android.util.AttributeSet; +import android.util.SparseArray; +import android.view.View; +import android.view.ViewGroup; + +/** + * An abstract base class for spinner widgets. SDK users will probably not + * need to use this class. + * + * @attr ref android.R.styleable#AbsSpinner_entries + */ +public abstract class AbsSpinner extends AdapterView { + SpinnerAdapter mAdapter; + + int mHeightMeasureSpec; + int mWidthMeasureSpec; + + int mSelectionLeftPadding = 0; + int mSelectionTopPadding = 0; + int mSelectionRightPadding = 0; + int mSelectionBottomPadding = 0; + final Rect mSpinnerPadding = new Rect(); + + final RecycleBin mRecycler = new RecycleBin(); + private DataSetObserver mDataSetObserver; + + /** Temporary frame to hold a child View's frame rectangle */ + private Rect mTouchFrame; public AbsSpinner(Context context) { super(context); - haveCustomMeasure = false; + initAbsSpinner(); } - public AbsSpinner(Context context, AttributeSet attributeSet) { - super(context, attributeSet); - haveCustomMeasure = false; + public AbsSpinner(Context context, AttributeSet attrs) { + this(context, attrs, 0); } - public AbsSpinner(Context context, AttributeSet attributeSet, int defStyle) { - super(context, attributeSet, defStyle); - haveCustomMeasure = false; + public AbsSpinner(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); } - @Override - protected native long native_constructor(Context context, AttributeSet attrs); - protected native void native_setAdapter(long widget, SpinnerAdapter adapter); + public AbsSpinner(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + initAbsSpinner(); - public void setAdapter(SpinnerAdapter adapter) { - SpinnerAdapter oldAdapter = getAdapter(); - if (oldAdapter != null) - oldAdapter.unregisterDataSetObserver(observer); - super.setAdapter(adapter); - if (adapter != null) - adapter.registerDataSetObserver(observer); - native_setAdapter(this.widget, adapter); - } + final TypedArray a = context.obtainStyledAttributes( + attrs, R.styleable.AbsSpinner, defStyleAttr, defStyleRes); - public SpinnerAdapter getAdapter() { - return (SpinnerAdapter) super.getAdapter(); - } - - @Override - public native void setOnItemSelectedListener(AdapterView.OnItemSelectedListener listener); - - private class Observer extends DataSetObserver { - - @Override - public void onChanged() { - AbsSpinner.this.native_setAdapter(widget, getAdapter()); + final CharSequence[] entries = a.getTextArray(R.styleable.AbsSpinner_entries); + if (entries != null) { + final ArrayAdapter adapter = new ArrayAdapter( + context, R.layout.simple_spinner_item, entries); + adapter.setDropDownViewResource(R.layout.simple_spinner_dropdown_item); + setAdapter(adapter); } - @Override - public void onInvalidated() { - AbsSpinner.this.native_setAdapter(widget, getAdapter()); + + a.recycle(); + } + + /** + * Common code for different constructor flavors + */ + private void initAbsSpinner() { + setFocusable(true); + setWillNotDraw(false); + } + + /** + * The Adapter is used to provide the data which backs this Spinner. + * It also provides methods to transform spinner items based on their position + * relative to the selected item. + * @param adapter The SpinnerAdapter to use for this Spinner + */ + @Override + public void setAdapter(SpinnerAdapter adapter) { + if (null != mAdapter) { + mAdapter.unregisterDataSetObserver(mDataSetObserver); + resetList(); + } + + mAdapter = adapter; + + mOldSelectedPosition = INVALID_POSITION; + mOldSelectedRowId = INVALID_ROW_ID; + + if (mAdapter != null) { + mOldItemCount = mItemCount; + mItemCount = mAdapter.getCount(); + checkFocus(); + + mDataSetObserver = new AdapterDataSetObserver(); + mAdapter.registerDataSetObserver(mDataSetObserver); + + int position = mItemCount > 0 ? 0 : INVALID_POSITION; + + setSelectedPositionInt(position); + setNextSelectedPositionInt(position); + + if (mItemCount == 0) { + // Nothing selected + checkSelectionChanged(); + } + + } else { + checkFocus(); + resetList(); + // Nothing selected + checkSelectionChanged(); + } + + requestLayout(); + } + + /** + * Clear out all children from the list + */ + void resetList() { + mDataChanged = false; + mNeedSync = false; + + // removeAllViewsInLayout(); + for (int i = getChildCount() - 1; i >= 0; i--) { + removeViewInLayout(getChildAt(i)); + } + mOldSelectedPosition = INVALID_POSITION; + mOldSelectedRowId = INVALID_ROW_ID; + + setSelectedPositionInt(INVALID_POSITION); + setNextSelectedPositionInt(INVALID_POSITION); + invalidate(); + } + + /** + * @see android.view.View#measure(int, int) + * + * Figure out the dimensions of this Spinner. The width comes from + * the widthMeasureSpec as Spinnners can't have their width set to + * UNSPECIFIED. The height is based on the height of the selected item + * plus padding. + */ + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int widthMode = MeasureSpec.getMode(widthMeasureSpec); + int widthSize; + int heightSize; + + mSpinnerPadding.left = paddingLeft > mSelectionLeftPadding ? paddingLeft + : mSelectionLeftPadding; + mSpinnerPadding.top = paddingTop > mSelectionTopPadding ? paddingTop + : mSelectionTopPadding; + mSpinnerPadding.right = paddingRight > mSelectionRightPadding ? paddingRight + : mSelectionRightPadding; + mSpinnerPadding.bottom = paddingBottom > mSelectionBottomPadding ? paddingBottom + : mSelectionBottomPadding; + + if (mDataChanged) { + handleDataChanged(); + } + + int preferredHeight = 0; + int preferredWidth = 0; + boolean needsMeasuring = true; + + int selectedPosition = getSelectedItemPosition(); + if (selectedPosition >= 0 && mAdapter != null && selectedPosition < mAdapter.getCount()) { + // Try looking in the recycler. (Maybe we were measured once already) + View view = mRecycler.get(selectedPosition); + if (view == null) { + // Make a new one + view = mAdapter.getView(selectedPosition, null, this); + } + + if (view != null) { + // Put in recycler for re-measuring and/or layout + mRecycler.put(selectedPosition, view); + + if (view.getLayoutParams() == null) { + mBlockLayoutRequests = true; + view.setLayoutParams(generateDefaultLayoutParams()); + mBlockLayoutRequests = false; + } + measureChild(view, widthMeasureSpec, heightMeasureSpec); + + preferredHeight = getChildHeight(view) + mSpinnerPadding.top + mSpinnerPadding.bottom; + preferredWidth = getChildWidth(view) + mSpinnerPadding.left + mSpinnerPadding.right; + + needsMeasuring = false; + } + } + + if (needsMeasuring) { + // No views -- just use padding + preferredHeight = mSpinnerPadding.top + mSpinnerPadding.bottom; + if (widthMode == MeasureSpec.UNSPECIFIED) { + preferredWidth = mSpinnerPadding.left + mSpinnerPadding.right; + } + } + + preferredHeight = Math.max(preferredHeight, getSuggestedMinimumHeight()); + preferredWidth = Math.max(preferredWidth, getSuggestedMinimumWidth()); + + heightSize = resolveSizeAndState(preferredHeight, heightMeasureSpec, 0); + widthSize = resolveSizeAndState(preferredWidth, widthMeasureSpec, 0); + + setMeasuredDimension(widthSize, heightSize); + mHeightMeasureSpec = heightMeasureSpec; + mWidthMeasureSpec = widthMeasureSpec; + } + + int getChildHeight(View child) { + return child.getMeasuredHeight(); + } + + int getChildWidth(View child) { + return child.getMeasuredWidth(); + } + + @Override + protected ViewGroup.LayoutParams generateDefaultLayoutParams() { + return new ViewGroup.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT); + } + + void recycleAllViews() { + final int childCount = getChildCount(); + final AbsSpinner.RecycleBin recycleBin = mRecycler; + final int position = mFirstPosition; + + // All views go in recycler + for (int i = 0; i < childCount; i++) { + View v = getChildAt(i); + int index = position + i; + recycleBin.put(index, v); + } + } + + /** + * Jump directly to a specific item in the adapter data. + */ + public void setSelection(int position, boolean animate) { + // Animate only if requested position is already on screen somewhere + boolean shouldAnimate = animate && mFirstPosition <= position && + position <= mFirstPosition + getChildCount() - 1; + setSelectionInt(position, shouldAnimate); + } + + @Override + public void setSelection(int position) { + setNextSelectedPositionInt(position); + requestLayout(); + invalidate(); + } + + + /** + * Makes the item at the supplied position selected. + * + * @param position Position to select + * @param animate Should the transition be animated + * + */ + void setSelectionInt(int position, boolean animate) { + if (position != mOldSelectedPosition) { + mBlockLayoutRequests = true; + int delta = position - mSelectedPosition; + setNextSelectedPositionInt(position); + layout(delta, animate); + mBlockLayoutRequests = false; + } + } + + abstract void layout(int delta, boolean animate); + + @Override + public View getSelectedView() { + if (mItemCount > 0 && mSelectedPosition >= 0) { + return getChildAt(mSelectedPosition - mFirstPosition); + } else { + return null; + } + } + + /** + * Override to prevent spamming ourselves with layout requests + * as we place views + * + * @see android.view.View#requestLayout() + */ + @Override + public void requestLayout() { + if (!mBlockLayoutRequests) { + super.requestLayout(); + } + } + + @Override + public SpinnerAdapter getAdapter() { + return mAdapter; + } + + @Override + public int getCount() { + return mItemCount; + } + + /** + * Maps a point to a position in the list. + * + * @param x X in local coordinate + * @param y Y in local coordinate + * @return The position of the item which contains the specified point, or + * {@link #INVALID_POSITION} if the point does not intersect an item. + */ + public int pointToPosition(int x, int y) { + Rect frame = mTouchFrame; + if (frame == null) { + mTouchFrame = new Rect(); + frame = mTouchFrame; + } + + final int count = getChildCount(); + for (int i = count - 1; i >= 0; i--) { + View child = getChildAt(i); + if (child.getVisibility() == View.VISIBLE) { + child.getHitRect(frame); + if (frame.contains(x, y)) { + return mFirstPosition + i; + } + } + } + return INVALID_POSITION; + } + + class RecycleBin { + private final SparseArray mScrapHeap = new SparseArray(); + + public void put(int position, View v) { + mScrapHeap.put(position, v); + } + + View get(int position) { + // System.out.print("Looking for " + position); + View result = mScrapHeap.get(position); + if (result != null) { + // System.out.println(" HIT"); + mScrapHeap.delete(position); + } else { + // System.out.println(" MISS"); + } + return result; + } + + void clear() { + final SparseArray scrapHeap = mScrapHeap; + final int count = scrapHeap.size(); + for (int i = 0; i < count; i++) { + final View view = scrapHeap.valueAt(i); + if (view != null) { + removeDetachedView(view, true); + } + } + scrapHeap.clear(); } } } \ No newline at end of file diff --git a/src/api-impl/android/widget/AdapterView.java b/src/api-impl/android/widget/AdapterView.java index 160f4ab0..b28b967f 100644 --- a/src/api-impl/android/widget/AdapterView.java +++ b/src/api-impl/android/widget/AdapterView.java @@ -1,71 +1,1094 @@ +/* + * 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 android.content.Context; +import android.database.DataSetObserver; +import android.os.Parcelable; +import android.os.SystemClock; import android.util.AttributeSet; import android.view.View; import android.view.ViewGroup; -public abstract class AdapterView extends ViewGroup { +/** + * An AdapterView is a view whose children are determined by an {@link Adapter}. + * + *

+ * See {@link ListView}, {@link GridView}, {@link Spinner} and + * {@link Gallery} for commonly used subclasses of AdapterView. + * + *

+ *

Developer Guides

+ *

For more information about using AdapterView, read the + * Binding to Data with AdapterView + * developer guide.

+ */ +public abstract class AdapterView extends ViewGroup { - private Adapter adapter; + /** + * The item view type returned by {@link Adapter#getItemViewType(int)} when + * the adapter does not want the item's view recycled. + */ + public static final int ITEM_VIEW_TYPE_IGNORE = -1; + + /** + * The item view type returned by {@link Adapter#getItemViewType(int)} when + * the item is a header or footer. + */ + public static final int ITEM_VIEW_TYPE_HEADER_OR_FOOTER = -2; + + /** + * The position of the first child displayed + */ + int mFirstPosition = 0; + + /** + * The offset in pixels from the top of the AdapterView to the top + * of the view to select during the next layout. + */ + int mSpecificTop; + + /** + * Position from which to start looking for mSyncRowId + */ + int mSyncPosition; + + /** + * Row id to look for when data has changed + */ + long mSyncRowId = INVALID_ROW_ID; + + /** + * Height of the view when mSyncPosition and mSyncRowId where set + */ + long mSyncHeight; + + /** + * True if we need to sync to mSyncRowId + */ + boolean mNeedSync = false; + + /** + * Indicates whether to sync based on the selection or position. Possible + * values are {@link #SYNC_SELECTED_POSITION} or + * {@link #SYNC_FIRST_POSITION}. + */ + int mSyncMode; + + /** + * Our height after the last layout + */ + private int mLayoutHeight; + + /** + * Sync based on the selected child + */ + static final int SYNC_SELECTED_POSITION = 0; + + /** + * Sync based on the first child displayed + */ + static final int SYNC_FIRST_POSITION = 1; + + /** + * Maximum amount of time to spend in {@link #findSyncPosition()} + */ + static final int SYNC_MAX_DURATION_MILLIS = 100; + + /** + * Indicates that this view is currently being laid out. + */ + boolean mInLayout = false; + + /** + * The listener that receives notifications when an item is selected. + */ + OnItemSelectedListener mOnItemSelectedListener; + + /** + * The listener that receives notifications when an item is clicked. + */ + OnItemClickListener mOnItemClickListener; + + /** + * The listener that receives notifications when an item is long clicked. + */ + OnItemLongClickListener mOnItemLongClickListener; + + /** + * True if the data has changed since the last layout + */ + boolean mDataChanged; + + /** + * The position within the adapter's data set of the item to select + * during the next layout. + */ + int mNextSelectedPosition = INVALID_POSITION; + + /** + * The item id of the item to select during the next layout. + */ + long mNextSelectedRowId = INVALID_ROW_ID; + + /** + * The position within the adapter's data set of the currently selected item. + */ + int mSelectedPosition = INVALID_POSITION; + + /** + * The item id of the currently selected item. + */ + long mSelectedRowId = INVALID_ROW_ID; + + /** + * View to show if there are no items to show. + */ + private View mEmptyView; + + /** + * The number of items in the current adapter. + */ + int mItemCount; + + /** + * The number of items in the adapter before a data changed event occurred. + */ + int mOldItemCount; + + /** + * Represents an invalid position. All valid positions are in the range 0 to 1 less than the + * number of items in the current adapter. + */ + public static final int INVALID_POSITION = -1; + + /** + * Represents an empty or invalid row id + */ + public static final long INVALID_ROW_ID = Long.MIN_VALUE; + + /** + * The last selected position we used when notifying + */ + int mOldSelectedPosition = INVALID_POSITION; + + /** + * The id of the last selected position we used when notifying + */ + long mOldSelectedRowId = INVALID_ROW_ID; + + /** + * Indicates what focusable state is requested when calling setFocusable(). + * In addition to this, this view has other criteria for actually + * determining the focusable state (such as whether its empty or the text + * filter is shown). + * + * @see #setFocusable(boolean) + * @see #checkFocus() + */ + private boolean mDesiredFocusableState; + private boolean mDesiredFocusableInTouchModeState; + + /** Lazily-constructed runnable for dispatching selection events. */ + private SelectionNotifier mSelectionNotifier; + + /** Selection notifier that's waiting for the next layout pass. */ + private SelectionNotifier mPendingSelectionNotifier; + + /** + * When set to true, calls to requestLayout() will not propagate up the parent hierarchy. + * This is used to layout the children during a layout pass. + */ + boolean mBlockLayoutRequests = false; public AdapterView(Context context) { - super(context); + this(context, null); } - public AdapterView(Context context, AttributeSet attributeSet) { - super(context, attributeSet); + public AdapterView(Context context, AttributeSet attrs) { + this(context, attrs, 0); } - public AdapterView(Context context, AttributeSet attributeSet, int defStyle) { - super(context, attributeSet, defStyle); + public AdapterView(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); } - public interface OnItemSelectedListener { + public AdapterView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); } + /** + * Interface definition for a callback to be invoked when an item in this + * AdapterView has been clicked. + */ public interface OnItemClickListener { - public void onItemClick(AdapterView parent, View view, int position, long id); + + /** + * Callback method to be invoked when an item in this AdapterView has + * been clicked. + *

+ * Implementers can call getItemAtPosition(position) if they need + * to access the data associated with the selected item. + * + * @param parent The AdapterView where the click happened. + * @param view The view within the AdapterView that was clicked (this + * will be a view provided by the adapter) + * @param position The position of the view in the adapter. + * @param id The row id of the item that was clicked. + */ + void onItemClick(AdapterView parent, View view, int position, long id); } + /** + * Register a callback to be invoked when an item in this AdapterView has + * been clicked. + * + * @param listener The callback that will be invoked. + */ + public void setOnItemClickListener(OnItemClickListener listener) { + mOnItemClickListener = listener; + } + + /** + * @return The callback to be invoked with an item in this AdapterView has + * been clicked, or null id no callback has been set. + */ + public final OnItemClickListener getOnItemClickListener() { + return mOnItemClickListener; + } + + /** + * Call the OnItemClickListener, if it is defined. Performs all normal + * actions associated with clicking: reporting accessibility event, playing + * a sound, etc. + * + * @param view The view within the AdapterView that was clicked. + * @param position The position of the view in the adapter. + * @param id The row id of the item that was clicked. + * @return True if there was an assigned OnItemClickListener that was + * called, false otherwise is returned. + */ + public boolean performItemClick(View view, int position, long id) { + final boolean result; + if (mOnItemClickListener != null) { + mOnItemClickListener.onItemClick(this, view, position, id); + result = true; + } else { + result = false; + } + return result; + } + + /** + * Interface definition for a callback to be invoked when an item in this + * view has been clicked and held. + */ public interface OnItemLongClickListener { + /** + * Callback method to be invoked when an item in this view has been + * clicked and held. + * + * Implementers can call getItemAtPosition(position) if they need to access + * the data associated with the selected item. + * + * @param parent The AbsListView where the click happened + * @param view The view within the AbsListView that was clicked + * @param position The position of the view in the list + * @param id The row id of the item that was clicked + * + * @return true if the callback consumed the long click, false otherwise + */ + boolean onItemLongClick(AdapterView parent, View view, int position, long id); } - public void setAdapter(Adapter adapter) { - this.adapter = adapter; + + /** + * Register a callback to be invoked when an item in this AdapterView has + * been clicked and held + * + * @param listener The callback that will run + */ + public void setOnItemLongClickListener(OnItemLongClickListener listener) { + if (!isLongClickable()) { + setLongClickable(true); + } + mOnItemLongClickListener = listener; } - public void setSelection(int i) { - setSelection(i, false); + /** + * @return The callback to be invoked with an item in this AdapterView has + * been clicked and held, or null id no callback as been set. + */ + public final OnItemLongClickListener getOnItemLongClickListener() { + return mOnItemLongClickListener; } - public void setOnItemSelectedListener(AdapterView.OnItemSelectedListener listener) {} + /** + * Interface definition for a callback to be invoked when + * an item in this view has been selected. + */ + public interface OnItemSelectedListener { + /** + *

Callback method to be invoked when an item in this view has been + * selected. This callback is invoked only when the newly selected + * position is different from the previously selected position or if + * there was no selected item.

+ * + * Impelmenters can call getItemAtPosition(position) if they need to access the + * data associated with the selected item. + * + * @param parent The AdapterView where the selection happened + * @param view The view within the AdapterView that was clicked + * @param position The position of the view in the adapter + * @param id The row id of the item that is selected + */ + void onItemSelected(AdapterView parent, View view, int position, long id); - public void setOnItemClickListener(AdapterView.OnItemClickListener listener) {} - - public void setOnItemLongClickListener(AdapterView.OnItemLongClickListener listener) {} - - public void setSelection(int position, boolean animate) {} - - public Adapter getAdapter() { - return adapter; + /** + * Callback method to be invoked when the selection disappears from this + * view. The selection can disappear for instance when touch is activated + * or when the adapter becomes empty. + * + * @param parent The AdapterView that now contains no selected item. + */ + void onNothingSelected(AdapterView parent); } - public Object getItemAtPosition(int position) { - return adapter.getItem(position); + + /** + * Register a callback to be invoked when an item in this AdapterView has + * been selected. + * + * @param listener The callback that will run + */ + public void setOnItemSelectedListener(OnItemSelectedListener listener) { + mOnItemSelectedListener = listener; } - public void setEmptyView(View emptyView) {} - - public int getFirstVisiblePosition() { - return 0; + public final OnItemSelectedListener getOnItemSelectedListener() { + return mOnItemSelectedListener; } - public int getLastVisiblePosition() { - return 0; + /** + * Returns the adapter currently associated with this widget. + * + * @return The adapter used to provide this view's content. + */ + public abstract T getAdapter(); + + /** + * Sets the adapter that provides the data and the views to represent the data + * in this widget. + * + * @param adapter The adapter to use to create this view's content. + */ + public abstract void setAdapter(T adapter); + + /** + * This method is not supported and throws an UnsupportedOperationException when called. + * + * @param child Ignored. + * + * @throws UnsupportedOperationException Every time this method is invoked. + */ + @Override + public void addView(View child) { + throw new UnsupportedOperationException("addView(View) is not supported in AdapterView"); } + /** + * This method is not supported and throws an UnsupportedOperationException when called. + * + * @param child Ignored. + * @param index Ignored. + * + * @throws UnsupportedOperationException Every time this method is invoked. + */ + @Override + public void addView(View child, int index) { + throw new UnsupportedOperationException("addView(View, int) is not supported in AdapterView"); + } + + /** + * This method is not supported and throws an UnsupportedOperationException when called. + * + * @param child Ignored. + * @param params Ignored. + * + * @throws UnsupportedOperationException Every time this method is invoked. + */ + @Override + public void addView(View child, LayoutParams params) { + throw new UnsupportedOperationException("addView(View, LayoutParams) " + + "is not supported in AdapterView"); + } + + /** + * This method is not supported and throws an UnsupportedOperationException when called. + * + * @param child Ignored. + * @param index Ignored. + * @param params Ignored. + * + * @throws UnsupportedOperationException Every time this method is invoked. + */ + @Override + public void addView(View child, int index, LayoutParams params) { + throw new UnsupportedOperationException("addView(View, int, LayoutParams) " + + "is not supported in AdapterView"); + } + + /** + * This method is not supported and throws an UnsupportedOperationException when called. + * + * @param child Ignored. + * + * @throws UnsupportedOperationException Every time this method is invoked. + */ + @Override + public void removeView(View child) { + throw new UnsupportedOperationException("removeView(View) is not supported in AdapterView"); + } + + /** + * This method is not supported and throws an UnsupportedOperationException when called. + * + * @param index Ignored. + * + * @throws UnsupportedOperationException Every time this method is invoked. + */ + @Override + public void removeViewAt(int index) { + throw new UnsupportedOperationException("removeViewAt(int) is not supported in AdapterView"); + } + + /** + * This method is not supported and throws an UnsupportedOperationException when called. + * + * @throws UnsupportedOperationException Every time this method is invoked. + */ + @Override + public void removeAllViews() { + throw new UnsupportedOperationException("removeAllViews() is not supported in AdapterView"); + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + mLayoutHeight = getHeight(); + } + + /** + * Return the position of the currently selected item within the adapter's data set + * + * @return int Position (starting at 0), or {@link #INVALID_POSITION} if there is nothing selected. + */ + public int getSelectedItemPosition() { + return mNextSelectedPosition; + } + + /** + * @return The id corresponding to the currently selected item, or {@link #INVALID_ROW_ID} + * if nothing is selected. + */ + public long getSelectedItemId() { + return mNextSelectedRowId; + } + + /** + * @return The view corresponding to the currently selected item, or null + * if nothing is selected + */ + public abstract View getSelectedView(); + + /** + * @return The data corresponding to the currently selected item, or + * null if there is nothing selected. + */ + public Object getSelectedItem() { + T adapter = getAdapter(); + int selection = getSelectedItemPosition(); + if (adapter != null && adapter.getCount() > 0 && selection >= 0) { + return adapter.getItem(selection); + } else { + return null; + } + } + + /** + * @return The number of items owned by the Adapter associated with this + * AdapterView. (This is the number of data items, which may be + * larger than the number of visible views.) + */ public int getCount() { - return adapter.getCount(); + return mItemCount; } -} + + /** + * Returns the position within the adapter's data set for the view, where + * view is a an adapter item or a descendant of an adapter item. + *

+ * Note: The result of this method only reflects the + * position of the data bound to view during the most recent + * layout pass. If the adapter's data set has changed without a subsequent + * layout pass, the position returned by this method may not match the + * current position of the data within the adapter. + * + * @param view an adapter item, or a descendant of an adapter item. This + * must be visible in this AdapterView at the time of the call. + * @return the position within the adapter's data set of the view, or + * {@link #INVALID_POSITION} if the view does not correspond to a + * list item (or it is not currently visible) + */ + public int getPositionForView(View view) { + View listItem = view; + try { + View v; + while ((v = (View) listItem.getParent()) != null && !v.equals(this)) { + listItem = v; + } + } catch (ClassCastException e) { + // We made it up to the window without find this list view + return INVALID_POSITION; + } + + if (listItem != null) { + // Search the children for the list item + final int childCount = getChildCount(); + for (int i = 0; i < childCount; i++) { + if (getChildAt(i).equals(listItem)) { + return mFirstPosition + i; + } + } + } + + // Child not found! + return INVALID_POSITION; + } + + /** + * Returns the position within the adapter's data set for the first item + * displayed on screen. + * + * @return The position within the adapter's data set + */ + public int getFirstVisiblePosition() { + return mFirstPosition; + } + + /** + * Returns the position within the adapter's data set for the last item + * displayed on screen. + * + * @return The position within the adapter's data set + */ + public int getLastVisiblePosition() { + return mFirstPosition + getChildCount() - 1; + } + + /** + * Sets the currently selected item. To support accessibility subclasses that + * override this method must invoke the overriden super method first. + * + * @param position Index (starting at 0) of the data item to be selected. + */ + public abstract void setSelection(int position); + + /** + * Sets the view to show if the adapter is empty + */ + public void setEmptyView(View emptyView) { + mEmptyView = emptyView; + + final T adapter = getAdapter(); + final boolean empty = ((adapter == null) || adapter.isEmpty()); + updateEmptyStatus(empty); + } + + /** + * When the current adapter is empty, the AdapterView can display a special view + * called the empty view. The empty view is used to provide feedback to the user + * that no data is available in this AdapterView. + * + * @return The view to show if the adapter is empty. + */ + public View getEmptyView() { + return mEmptyView; + } + + /** + * Indicates whether this view is in filter mode. Filter mode can for instance + * be enabled by a user when typing on the keyboard. + * + * @return True if the view is in filter mode, false otherwise. + */ + boolean isInFilterMode() { + return false; + } + + @Override + public void setFocusable(boolean focusable) { + final T adapter = getAdapter(); + final boolean empty = adapter == null || adapter.getCount() == 0; + + mDesiredFocusableState = focusable; + if (!focusable) { + mDesiredFocusableInTouchModeState = false; + } + + super.setFocusable(focusable && (!empty || isInFilterMode())); + } + + @Override + public void setFocusableInTouchMode(boolean focusable) { + final T adapter = getAdapter(); + final boolean empty = adapter == null || adapter.getCount() == 0; + + mDesiredFocusableInTouchModeState = focusable; + if (focusable) { + mDesiredFocusableState = true; + } + + super.setFocusableInTouchMode(focusable && (!empty || isInFilterMode())); + } + + void checkFocus() { + final T adapter = getAdapter(); + final boolean empty = adapter == null || adapter.getCount() == 0; + final boolean focusable = !empty || isInFilterMode(); + // The order in which we set focusable in touch mode/focusable may matter + // for the client, see View.setFocusableInTouchMode() comments for more + // details + super.setFocusableInTouchMode(focusable && mDesiredFocusableInTouchModeState); + super.setFocusable(focusable && mDesiredFocusableState); + if (mEmptyView != null) { + updateEmptyStatus((adapter == null) || adapter.isEmpty()); + } + } + + /** + * Update the status of the list based on the empty parameter. If empty is true and + * we have an empty view, display it. In all the other cases, make sure that the listview + * is VISIBLE and that the empty view is GONE (if it's not null). + */ + private void updateEmptyStatus(boolean empty) { + if (isInFilterMode()) { + empty = false; + } + + if (empty) { + if (mEmptyView != null) { + mEmptyView.setVisibility(View.VISIBLE); + setVisibility(View.GONE); + } else { + // If the caller just removed our empty view, make sure the list view is visible + setVisibility(View.VISIBLE); + } + + // We are now GONE, so pending layouts will not be dispatched. + // Force one here to make sure that the state of the list matches + // the state of the adapter. + if (mDataChanged) { + this.onLayout(false, getLeft(), getTop(), getRight(), getBottom()); + } + } else { + if (mEmptyView != null) mEmptyView.setVisibility(View.GONE); + setVisibility(View.VISIBLE); + } + } + + /** + * Gets the data associated with the specified position in the list. + * + * @param position Which data to get + * @return The data associated with the specified position in the list + */ + public Object getItemAtPosition(int position) { + T adapter = getAdapter(); + return (adapter == null || position < 0) ? null : adapter.getItem(position); + } + + public long getItemIdAtPosition(int position) { + T adapter = getAdapter(); + return (adapter == null || position < 0) ? INVALID_ROW_ID : adapter.getItemId(position); + } + + @Override + public void setOnClickListener(OnClickListener l) { + throw new RuntimeException("Don't call setOnClickListener for an AdapterView. " + + "You probably want setOnItemClickListener instead"); + } + + class AdapterDataSetObserver extends DataSetObserver { + + private Parcelable mInstanceState = null; + + @Override + public void onChanged() { + mDataChanged = true; + mOldItemCount = mItemCount; + mItemCount = getAdapter().getCount(); + + // Detect the case where a cursor that was previously invalidated has + // been repopulated with new data. + if (AdapterView.this.getAdapter().hasStableIds() && mInstanceState != null + && mOldItemCount == 0 && mItemCount > 0) { + // AdapterView.this.onRestoreInstanceState(mInstanceState); + mInstanceState = null; + } else { + rememberSyncState(); + } + checkFocus(); + requestLayout(); + } + + @Override + public void onInvalidated() { + mDataChanged = true; + + if (AdapterView.this.getAdapter().hasStableIds()) { + // Remember the current state for the case where our hosting activity is being + // stopped and later restarted + // mInstanceState = AdapterView.this.onSaveInstanceState(); + } + + // Data is invalid so we should reset our state + mOldItemCount = mItemCount; + mItemCount = 0; + mSelectedPosition = INVALID_POSITION; + mSelectedRowId = INVALID_ROW_ID; + mNextSelectedPosition = INVALID_POSITION; + mNextSelectedRowId = INVALID_ROW_ID; + mNeedSync = false; + + checkFocus(); + requestLayout(); + } + + public void clearSavedState() { + mInstanceState = null; + } + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + removeCallbacks(mSelectionNotifier); + } + + private class SelectionNotifier implements Runnable { + public void run() { + mPendingSelectionNotifier = null; + + if (mDataChanged && isLayoutRequested()) { + // Data has changed between when this SelectionNotifier was + // posted and now. Postpone the notification until the next + // layout is complete and we run checkSelectionChanged(). + if (getAdapter() != null) { + mPendingSelectionNotifier = this; + } + } else { + dispatchOnItemSelected(); + } + } + } + + void selectionChanged() { + // We're about to post or run the selection notifier, so we don't need + // a pending notifier. + mPendingSelectionNotifier = null; + + if (mOnItemSelectedListener != null) { + if (mInLayout || mBlockLayoutRequests) { + // If we are in a layout traversal, defer notification + // by posting. This ensures that the view tree is + // in a consistent state and is able to accommodate + // new layout or invalidate requests. + if (mSelectionNotifier == null) { + mSelectionNotifier = new SelectionNotifier(); + } else { + removeCallbacks(mSelectionNotifier); + } + post(mSelectionNotifier); + } else { + dispatchOnItemSelected(); + } + } + } + + private void dispatchOnItemSelected() { + fireOnSelected(); + } + + private void fireOnSelected() { + if (mOnItemSelectedListener == null) { + return; + } + final int selection = getSelectedItemPosition(); + if (selection >= 0) { + View v = getSelectedView(); + mOnItemSelectedListener.onItemSelected(this, v, selection, + getAdapter().getItemId(selection)); + } else { + mOnItemSelectedListener.onNothingSelected(this); + } + } + + // @Override + // protected boolean canAnimate() { + // return super.canAnimate() && mItemCount > 0; + // } + + void handleDataChanged() { + final int count = mItemCount; + boolean found = false; + + if (count > 0) { + + int newPos; + + // Find the row we are supposed to sync to + if (mNeedSync) { + // Update this first, since setNextSelectedPositionInt inspects + // it + mNeedSync = false; + + // See if we can find a position in the new data with the same + // id as the old selection + newPos = findSyncPosition(); + if (newPos >= 0) { + // Verify that new selection is selectable + int selectablePos = lookForSelectablePosition(newPos, true); + if (selectablePos == newPos) { + // Same row id is selected + setNextSelectedPositionInt(newPos); + found = true; + } + } + } + if (!found) { + // Try to use the same position if we can't find matching data + newPos = getSelectedItemPosition(); + + // Pin position to the available range + if (newPos >= count) { + newPos = count - 1; + } + if (newPos < 0) { + newPos = 0; + } + + // Make sure we select something selectable -- first look down + int selectablePos = lookForSelectablePosition(newPos, true); + if (selectablePos < 0) { + // Looking down didn't work -- try looking up + selectablePos = lookForSelectablePosition(newPos, false); + } + if (selectablePos >= 0) { + setNextSelectedPositionInt(selectablePos); + checkSelectionChanged(); + found = true; + } + } + } + if (!found) { + // Nothing is selected + mSelectedPosition = INVALID_POSITION; + mSelectedRowId = INVALID_ROW_ID; + mNextSelectedPosition = INVALID_POSITION; + mNextSelectedRowId = INVALID_ROW_ID; + mNeedSync = false; + checkSelectionChanged(); + } + } + + /** + * Called after layout to determine whether the selection position needs to + * be updated. Also used to fire any pending selection events. + */ + void checkSelectionChanged() { + if ((mSelectedPosition != mOldSelectedPosition) || (mSelectedRowId != mOldSelectedRowId)) { + selectionChanged(); + mOldSelectedPosition = mSelectedPosition; + mOldSelectedRowId = mSelectedRowId; + } + + // If we have a pending selection notification -- and we won't if we + // just fired one in selectionChanged() -- run it now. + if (mPendingSelectionNotifier != null) { + mPendingSelectionNotifier.run(); + } + } + + /** + * Searches the adapter for a position matching mSyncRowId. The search starts at mSyncPosition + * and then alternates between moving up and moving down until 1) we find the right position, or + * 2) we run out of time, or 3) we have looked at every position + * + * @return Position of the row that matches mSyncRowId, or {@link #INVALID_POSITION} if it can't + * be found + */ + int findSyncPosition() { + int count = mItemCount; + + if (count == 0) { + return INVALID_POSITION; + } + + long idToMatch = mSyncRowId; + int seed = mSyncPosition; + + // If there isn't a selection don't hunt for it + if (idToMatch == INVALID_ROW_ID) { + return INVALID_POSITION; + } + + // Pin seed to reasonable values + seed = Math.max(0, seed); + seed = Math.min(count - 1, seed); + + long endTime = SystemClock.uptimeMillis() + SYNC_MAX_DURATION_MILLIS; + + long rowId; + + // first position scanned so far + int first = seed; + + // last position scanned so far + int last = seed; + + // True if we should move down on the next iteration + boolean next = false; + + // True when we have looked at the first item in the data + boolean hitFirst; + + // True when we have looked at the last item in the data + boolean hitLast; + + // Get the item ID locally (instead of getItemIdAtPosition), so + // we need the adapter + T adapter = getAdapter(); + if (adapter == null) { + return INVALID_POSITION; + } + + while (SystemClock.uptimeMillis() <= endTime) { + rowId = adapter.getItemId(seed); + if (rowId == idToMatch) { + // Found it! + return seed; + } + + hitLast = last == count - 1; + hitFirst = first == 0; + + if (hitLast && hitFirst) { + // Looked at everything + break; + } + + if (hitFirst || (next && !hitLast)) { + // Either we hit the top, or we are trying to move down + last++; + seed = last; + // Try going up next time + next = false; + } else if (hitLast || (!next && !hitFirst)) { + // Either we hit the bottom, or we are trying to move up + first--; + seed = first; + // Try going down next time + next = true; + } + + } + + return INVALID_POSITION; + } + + /** + * Find a position that can be selected (i.e., is not a separator). + * + * @param position The starting position to look at. + * @param lookDown Whether to look down for other positions. + * @return The next selectable position starting at position and then searching either up or + * down. Returns {@link #INVALID_POSITION} if nothing can be found. + */ + int lookForSelectablePosition(int position, boolean lookDown) { + return position; + } + + /** + * Utility to keep mSelectedPosition and mSelectedRowId in sync + * @param position Our current position + */ + void setSelectedPositionInt(int position) { + mSelectedPosition = position; + mSelectedRowId = getItemIdAtPosition(position); + } + + /** + * Utility to keep mNextSelectedPosition and mNextSelectedRowId in sync + * @param position Intended value for mSelectedPosition the next time we go + * through layout + */ + void setNextSelectedPositionInt(int position) { + mNextSelectedPosition = position; + mNextSelectedRowId = getItemIdAtPosition(position); + // If we are trying to sync to the selection, update that too + if (mNeedSync && mSyncMode == SYNC_SELECTED_POSITION && position >= 0) { + mSyncPosition = position; + mSyncRowId = mNextSelectedRowId; + } + } + + /** + * Remember enough information to restore the screen state when the data has + * changed. + * + */ + void rememberSyncState() { + if (getChildCount() > 0) { + mNeedSync = true; + mSyncHeight = mLayoutHeight; + if (mSelectedPosition >= 0) { + // Sync the selection state + View v = getChildAt(mSelectedPosition - mFirstPosition); + mSyncRowId = mNextSelectedRowId; + mSyncPosition = mNextSelectedPosition; + if (v != null) { + mSpecificTop = v.getTop(); + } + mSyncMode = SYNC_SELECTED_POSITION; + } else { + // Sync the based on the offset of the first view + View v = getChildAt(0); + T adapter = getAdapter(); + if (mFirstPosition >= 0 && mFirstPosition < adapter.getCount()) { + mSyncRowId = adapter.getItemId(mFirstPosition); + } else { + mSyncRowId = NO_ID; + } + mSyncPosition = mFirstPosition; + if (v != null) { + mSpecificTop = v.getTop(); + } + mSyncMode = SYNC_FIRST_POSITION; + } + } + } +} \ No newline at end of file diff --git a/src/api-impl/android/widget/Gallery.java b/src/api-impl/android/widget/Gallery.java index d55f6604..51ec56c1 100644 --- a/src/api-impl/android/widget/Gallery.java +++ b/src/api-impl/android/widget/Gallery.java @@ -1,21 +1,1325 @@ +/* + * Copyright (C) 2007 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 android.content.Context; -import android.util.AttributeSet; +import com.android.internal.R; -public class Gallery extends AbsSpinner { +import android.annotation.Widget; +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Rect; +import android.util.AttributeSet; +import android.util.Log; +import android.view.GestureDetector; +import android.view.Gravity; +import android.view.KeyEvent; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; + +/** + * A view that shows items in a center-locked, horizontally scrolling list. + *

+ * The default values for the Gallery assume you will be using + * {@link android.R.styleable#Theme_galleryItemBackground} as the background for + * each View given to the Gallery from the Adapter. If you are not doing this, + * you may need to adjust some Gallery properties, such as the spacing. + *

+ * Views given to the Gallery should use {@link Gallery.LayoutParams} as their + * layout parameters type. + * + * @attr ref android.R.styleable#Gallery_animationDuration + * @attr ref android.R.styleable#Gallery_spacing + * @attr ref android.R.styleable#Gallery_gravity + * + * @deprecated This widget is no longer supported. Other horizontally scrolling + * widgets include {@link HorizontalScrollView} and {@link android.support.v4.view.ViewPager} + * from the support library. + */ +@Deprecated +@Widget +public class Gallery extends AbsSpinner implements GestureDetector.OnGestureListener { + + private static final String TAG = "Gallery"; + + private static final boolean localLOGV = false; + + /** + * Duration in milliseconds from the start of a scroll during which we're + * unsure whether the user is scrolling or flinging. + */ + private static final int SCROLL_TO_FLING_UNCERTAINTY_TIMEOUT = 250; + + /** + * Horizontal spacing between items. + */ + private int mSpacing = 0; + + /** + * How long the transition animation should run when a child view changes + * position, measured in milliseconds. + */ + private int mAnimationDuration = 400; + + /** + * Left most edge of a child seen so far during layout. + */ + private int mLeftMost; + + /** + * Right most edge of a child seen so far during layout. + */ + private int mRightMost; + + private int mGravity; + + /** + * Helper for detecting touch gestures. + */ + private GestureDetector mGestureDetector; + + /** + * The position of the item that received the user's down touch. + */ + private int mDownTouchPosition; + + /** + * The view of the item that received the user's down touch. + */ + private View mDownTouchView; + + /** + * Executes the delta scrolls from a fling or scroll movement. + */ + private FlingRunnable mFlingRunnable = new FlingRunnable(); + + /** + * Sets mSuppressSelectionChanged = false. This is used to set it to false + * in the future. It will also trigger a selection changed. + */ + private Runnable mDisableSuppressSelectionChangedRunnable = new Runnable() { + @Override + public void run() { + mSuppressSelectionChanged = false; + selectionChanged(); + } + }; + + /** + * When fling runnable runs, it resets this to false. Any method along the + * path until the end of its run() can set this to true to abort any + * remaining fling. For example, if we've reached either the leftmost or + * rightmost item, we will set this to true. + */ + private boolean mShouldStopFling; + + /** + * The currently selected item's child. + */ + private View mSelectedChild; + + /** + * Whether to continuously callback on the item selected listener during a + * fling. + */ + private boolean mShouldCallbackDuringFling = true; + + /** + * Whether to callback when an item that is not selected is clicked. + */ + private boolean mShouldCallbackOnUnselectedItemClick = true; + + /** + * If true, do not callback to item selected listener. + */ + private boolean mSuppressSelectionChanged; + + /** + * If true, this onScroll is the first for this user's drag (remember, a + * drag sends many onScrolls). + */ + private boolean mIsFirstScroll; + + /** + * If true, mFirstPosition is the position of the rightmost child, and + * the children are ordered right to left. + */ + private boolean mIsRtl = true; + + /** + * Offset between the center of the selected child view and the center of the Gallery. + * Used to reset position correctly during layout. + */ + private int mSelectedCenterOffset; public Gallery(Context context) { - super(context); + this(context, null); } - public Gallery(Context context, AttributeSet attributeSet) { - super(context, attributeSet); + public Gallery(Context context, AttributeSet attrs) { + this(context, attrs, R.attr.galleryStyle); } - public Gallery(Context context, AttributeSet attributeSet, int defStyleAttr) { - super(context, attributeSet, defStyleAttr); + public Gallery(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); } - public void setHorizontalFadingEdgeEnabled(boolean enabled) {} + public Gallery(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + + mGestureDetector = new GestureDetector(context, this); + mGestureDetector.setIsLongpressEnabled(true); + + final TypedArray a = context.obtainStyledAttributes( + attrs, com.android.internal.R.styleable.Gallery, defStyleAttr, defStyleRes); + + int index = a.getInt(com.android.internal.R.styleable.Gallery_gravity, -1); + if (index >= 0) { + setGravity(index); + } + + int animationDuration = + a.getInt(com.android.internal.R.styleable.Gallery_animationDuration, -1); + if (animationDuration > 0) { + setAnimationDuration(animationDuration); + } + + int spacing = + a.getDimensionPixelOffset(com.android.internal.R.styleable.Gallery_spacing, 0); + setSpacing(spacing); + + a.recycle(); + + // We draw the selected item last (because otherwise the item to the + // right overlaps it) + // mGroupFlags |= FLAG_USE_CHILD_DRAWING_ORDER; + + // mGroupFlags |= FLAG_SUPPORT_STATIC_TRANSFORMATIONS; + } + + /** + * Whether or not to callback on any {@link #getOnItemSelectedListener()} + * while the items are being flinged. If false, only the final selected item + * will cause the callback. If true, all items between the first and the + * final will cause callbacks. + * + * @param shouldCallback Whether or not to callback on the listener while + * the items are being flinged. + */ + public void setCallbackDuringFling(boolean shouldCallback) { + mShouldCallbackDuringFling = shouldCallback; + } + + /** + * Whether or not to callback when an item that is not selected is clicked. + * If false, the item will become selected (and re-centered). If true, the + * {@link #getOnItemClickListener()} will get the callback. + * + * @param shouldCallback Whether or not to callback on the listener when a + * item that is not selected is clicked. + * @hide + */ + public void setCallbackOnUnselectedItemClick(boolean shouldCallback) { + mShouldCallbackOnUnselectedItemClick = shouldCallback; + } + + /** + * Sets how long the transition animation should run when a child view + * changes position. Only relevant if animation is turned on. + * + * @param animationDurationMillis The duration of the transition, in + * milliseconds. + * + * @attr ref android.R.styleable#Gallery_animationDuration + */ + public void setAnimationDuration(int animationDurationMillis) { + mAnimationDuration = animationDurationMillis; + } + + /** + * Sets the spacing between items in a Gallery + * + * @param spacing The spacing in pixels between items in the Gallery + * + * @attr ref android.R.styleable#Gallery_spacing + */ + public void setSpacing(int spacing) { + mSpacing = spacing; + } + + @Override + protected int computeHorizontalScrollExtent() { + // Only 1 item is considered to be selected + return 1; + } + + protected int computeHorizontalScrollOffset() { + // Current scroll position is the same as the selected position + return mSelectedPosition; + } + + @Override + protected int computeHorizontalScrollRange() { + // Scroll range is the same as the item count + return mItemCount; + } + + @Override + protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { + return p instanceof LayoutParams; + } + + @Override + protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { + return new LayoutParams(p); + } + + @Override + public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) { + return new LayoutParams(getContext(), attrs); + } + + @Override + protected ViewGroup.LayoutParams generateDefaultLayoutParams() { + /* + * Gallery expects Gallery.LayoutParams. + */ + return new Gallery.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, + ViewGroup.LayoutParams.WRAP_CONTENT); + } + + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + super.onLayout(changed, l, t, r, b); + + /* + * Remember that we are in layout to prevent more layout request from + * being generated. + */ + mInLayout = true; + layout(0, false); + mInLayout = false; + } + + @Override + int getChildHeight(View child) { + return child.getMeasuredHeight(); + } + + /** + * Tracks a motion scroll. In reality, this is used to do just about any + * movement to items (touch scroll, arrow-key scroll, set an item as selected). + * + * @param deltaX Change in X from the previous event. + */ + void trackMotionScroll(int deltaX) { + + if (getChildCount() == 0) { + return; + } + + boolean toLeft = deltaX < 0; + + int limitedDeltaX = getLimitedMotionScrollAmount(toLeft, deltaX); + if (limitedDeltaX != deltaX) { + // The above call returned a limited amount, so stop any scrolls/flings + mFlingRunnable.endFling(false); + onFinishedMovement(); + } + + offsetChildrenLeftAndRight(limitedDeltaX); + + detachOffScreenChildren(toLeft); + + if (toLeft) { + // If moved left, there will be empty space on the right + fillToGalleryRight(); + } else { + // Similarly, empty space on the left + fillToGalleryLeft(); + } + + // Clear unused views + mRecycler.clear(); + + setSelectionToCenterChild(); + + final View selChild = mSelectedChild; + if (selChild != null) { + final int childLeft = selChild.getLeft(); + final int childCenter = selChild.getWidth() / 2; + final int galleryCenter = getWidth() / 2; + mSelectedCenterOffset = childLeft + childCenter - galleryCenter; + } + + onScrollChanged(0, 0, 0, 0); // dummy values, View's implementation does not use these. + + invalidate(); + } + + int getLimitedMotionScrollAmount(boolean motionToLeft, int deltaX) { + int extremeItemPosition = motionToLeft != mIsRtl ? mItemCount - 1 : 0; + View extremeChild = getChildAt(extremeItemPosition - mFirstPosition); + + if (extremeChild == null) { + return deltaX; + } + + int extremeChildCenter = getCenterOfView(extremeChild); + int galleryCenter = getCenterOfGallery(); + + if (motionToLeft) { + if (extremeChildCenter <= galleryCenter) { + + // The extreme child is past his boundary point! + return 0; + } + } else { + if (extremeChildCenter >= galleryCenter) { + + // The extreme child is past his boundary point! + return 0; + } + } + + int centerDifference = galleryCenter - extremeChildCenter; + + return motionToLeft + ? Math.max(centerDifference, deltaX) + : Math.min(centerDifference, deltaX); + } + + /** + * Offset the horizontal location of all children of this view by the + * specified number of pixels. + * + * @param offset the number of pixels to offset + */ + private void offsetChildrenLeftAndRight(int offset) { + for (int i = getChildCount() - 1; i >= 0; i--) { + getChildAt(i).offsetLeftAndRight(offset); + } + } + + /** + * @return The center of this Gallery. + */ + private int getCenterOfGallery() { + return (getWidth() - paddingLeft - paddingRight) / 2 + paddingLeft; + } + + /** + * @return The center of the given view. + */ + private static int getCenterOfView(View view) { + return view.getLeft() + view.getWidth() / 2; + } + + /** + * Detaches children that are off the screen (i.e.: Gallery bounds). + * + * @param toLeft Whether to detach children to the left of the Gallery, or + * to the right. + */ + private void detachOffScreenChildren(boolean toLeft) { + int numChildren = getChildCount(); + int firstPosition = mFirstPosition; + int start = 0; + int count = 0; + + if (toLeft) { + final int galleryLeft = paddingLeft; + for (int i = 0; i < numChildren; i++) { + int n = mIsRtl ? (numChildren - 1 - i) : i; + final View child = getChildAt(n); + if (child.getRight() >= galleryLeft) { + break; + } else { + start = n; + count++; + mRecycler.put(firstPosition + n, child); + } + } + if (!mIsRtl) { + start = 0; + } + } else { + final int galleryRight = getWidth() - paddingRight; + for (int i = numChildren - 1; i >= 0; i--) { + int n = mIsRtl ? numChildren - 1 - i : i; + final View child = getChildAt(n); + if (child.getLeft() <= galleryRight) { + break; + } else { + start = n; + count++; + mRecycler.put(firstPosition + n, child); + } + } + if (mIsRtl) { + start = 0; + } + } + + // detachViewsFromParent(start, count); + for (int i = start + count - 1; i >= start; i--) { + detachViewFromParent(i); + } + + if (toLeft != mIsRtl) { + mFirstPosition += count; + } + } + + /** + * Scrolls the items so that the selected item is in its 'slot' (its center + * is the gallery's center). + */ + private void scrollIntoSlots() { + + if (getChildCount() == 0 || mSelectedChild == null) return; + + int selectedCenter = getCenterOfView(mSelectedChild); + int targetCenter = getCenterOfGallery(); + + int scrollAmount = targetCenter - selectedCenter; + if (scrollAmount != 0) { + mFlingRunnable.startUsingDistance(scrollAmount); + } else { + onFinishedMovement(); + } + } + + private void onFinishedMovement() { + if (mSuppressSelectionChanged) { + mSuppressSelectionChanged = false; + + // We haven't been callbacking during the fling, so do it now + super.selectionChanged(); + } + mSelectedCenterOffset = 0; + invalidate(); + } + + @Override + void selectionChanged() { + if (!mSuppressSelectionChanged) { + super.selectionChanged(); + } + } + + /** + * Looks for the child that is closest to the center and sets it as the + * selected child. + */ + private void setSelectionToCenterChild() { + + View selView = mSelectedChild; + if (mSelectedChild == null) return; + + int galleryCenter = getCenterOfGallery(); + + // Common case where the current selected position is correct + if (selView.getLeft() <= galleryCenter && selView.getRight() >= galleryCenter) { + return; + } + + int closestEdgeDistance = Integer.MAX_VALUE; + int newSelectedChildIndex = 0; + for (int i = getChildCount() - 1; i >= 0; i--) { + + View child = getChildAt(i); + + if (child.getLeft() <= galleryCenter && child.getRight() >= galleryCenter) { + // This child is in the center + newSelectedChildIndex = i; + break; + } + + int childClosestEdgeDistance = Math.min(Math.abs(child.getLeft() - galleryCenter), + Math.abs(child.getRight() - galleryCenter)); + if (childClosestEdgeDistance < closestEdgeDistance) { + closestEdgeDistance = childClosestEdgeDistance; + newSelectedChildIndex = i; + } + } + + int newPos = mFirstPosition + newSelectedChildIndex; + + if (newPos != mSelectedPosition) { + setSelectedPositionInt(newPos); + setNextSelectedPositionInt(newPos); + checkSelectionChanged(); + } + } + + /** + * Creates and positions all views for this Gallery. + *

+ * We layout rarely, most of the time {@link #trackMotionScroll(int)} takes + * care of repositioning, adding, and removing children. + * + * @param delta Change in the selected position. +1 means the selection is + * moving to the right, so views are scrolling to the left. -1 + * means the selection is moving to the left. + */ + @Override + void layout(int delta, boolean animate) { + + mIsRtl = false; // isLayoutRtl(); + + int childrenLeft = mSpinnerPadding.left; + int childrenWidth = getRight() - getLeft() - mSpinnerPadding.left - mSpinnerPadding.right; + + if (mDataChanged) { + handleDataChanged(); + } + + // Handle an empty gallery by removing all views. + if (mItemCount == 0) { + resetList(); + return; + } + + // Update to the new selected position. + if (mNextSelectedPosition >= 0) { + setSelectedPositionInt(mNextSelectedPosition); + } + + // All views go in recycler while we are in layout + recycleAllViews(); + + // Clear out old views + // detachAllViewsFromParent(); + for (int i=getChildCount() - 1; i >= 0; i--) { + detachViewFromParent(i); + } + + /* + * These will be used to give initial positions to views entering the + * gallery as we scroll + */ + mRightMost = 0; + mLeftMost = 0; + + // Make selected view and center it + + /* + * mFirstPosition will be decreased as we add views to the left later + * on. The 0 for x will be offset in a couple lines down. + */ + mFirstPosition = mSelectedPosition; + View sel = makeAndAddView(mSelectedPosition, 0, 0, true); + + // Put the selected child in the center + int selectedOffset = childrenLeft + (childrenWidth / 2) - (sel.getWidth() / 2) + + mSelectedCenterOffset; + sel.offsetLeftAndRight(selectedOffset); + + fillToGalleryRight(); + fillToGalleryLeft(); + + // Flush any cached views that did not get reused above + mRecycler.clear(); + + invalidate(); + checkSelectionChanged(); + + mDataChanged = false; + mNeedSync = false; + setNextSelectedPositionInt(mSelectedPosition); + + updateSelectedItemMetadata(); + } + + private void fillToGalleryLeft() { + if (mIsRtl) { + fillToGalleryLeftRtl(); + } else { + fillToGalleryLeftLtr(); + } + } + + private void fillToGalleryLeftRtl() { + int itemSpacing = mSpacing; + int galleryLeft = paddingLeft; + int numChildren = getChildCount(); + + // Set state for initial iteration + View prevIterationView = getChildAt(numChildren - 1); + int curPosition; + int curRightEdge; + + if (prevIterationView != null) { + curPosition = mFirstPosition + numChildren; + curRightEdge = prevIterationView.getLeft() - itemSpacing; + } else { + // No children available! + mFirstPosition = curPosition = mItemCount - 1; + curRightEdge = getRight() - getLeft() - paddingRight; + mShouldStopFling = true; + } + + while (curRightEdge > galleryLeft && curPosition < mItemCount) { + prevIterationView = makeAndAddView(curPosition, curPosition - mSelectedPosition, + curRightEdge, false); + + // Set state for next iteration + curRightEdge = prevIterationView.getLeft() - itemSpacing; + curPosition++; + } + } + + private void fillToGalleryLeftLtr() { + int itemSpacing = mSpacing; + int galleryLeft = paddingLeft; + + // Set state for initial iteration + View prevIterationView = getChildAt(0); + int curPosition; + int curRightEdge; + + if (prevIterationView != null) { + curPosition = mFirstPosition - 1; + curRightEdge = prevIterationView.getLeft() - itemSpacing; + } else { + // No children available! + curPosition = 0; + curRightEdge = getRight() - getLeft() - paddingRight; + mShouldStopFling = true; + } + + while (curRightEdge > galleryLeft && curPosition >= 0) { + prevIterationView = makeAndAddView(curPosition, curPosition - mSelectedPosition, + curRightEdge, false); + + // Remember some state + mFirstPosition = curPosition; + + // Set state for next iteration + curRightEdge = prevIterationView.getLeft() - itemSpacing; + curPosition--; + } + } + + private void fillToGalleryRight() { + if (mIsRtl) { + fillToGalleryRightRtl(); + } else { + fillToGalleryRightLtr(); + } + } + + private void fillToGalleryRightRtl() { + int itemSpacing = mSpacing; + int galleryRight = getRight() - getLeft() - paddingRight; + + // Set state for initial iteration + View prevIterationView = getChildAt(0); + int curPosition; + int curLeftEdge; + + if (prevIterationView != null) { + curPosition = mFirstPosition -1; + curLeftEdge = prevIterationView.getRight() + itemSpacing; + } else { + curPosition = 0; + curLeftEdge = paddingLeft; + mShouldStopFling = true; + } + + while (curLeftEdge < galleryRight && curPosition >= 0) { + prevIterationView = makeAndAddView(curPosition, curPosition - mSelectedPosition, + curLeftEdge, true); + + // Remember some state + mFirstPosition = curPosition; + + // Set state for next iteration + curLeftEdge = prevIterationView.getRight() + itemSpacing; + curPosition--; + } + } + + private void fillToGalleryRightLtr() { + int itemSpacing = mSpacing; + int galleryRight = getRight() - getLeft() - paddingRight; + int numChildren = getChildCount(); + int numItems = mItemCount; + + // Set state for initial iteration + View prevIterationView = getChildAt(numChildren - 1); + int curPosition; + int curLeftEdge; + + if (prevIterationView != null) { + curPosition = mFirstPosition + numChildren; + curLeftEdge = prevIterationView.getRight() + itemSpacing; + } else { + mFirstPosition = curPosition = mItemCount - 1; + curLeftEdge = paddingLeft; + mShouldStopFling = true; + } + + while (curLeftEdge < galleryRight && curPosition < numItems) { + prevIterationView = makeAndAddView(curPosition, curPosition - mSelectedPosition, + curLeftEdge, true); + + // Set state for next iteration + curLeftEdge = prevIterationView.getRight() + itemSpacing; + curPosition++; + } + } + + /** + * Obtain a view, either by pulling an existing view from the recycler or by + * getting a new one from the adapter. If we are animating, make sure there + * is enough information in the view's layout parameters to animate from the + * old to new positions. + * + * @param position Position in the gallery for the view to obtain + * @param offset Offset from the selected position + * @param x X-coordinate indicating where this view should be placed. This + * will either be the left or right edge of the view, depending on + * the fromLeft parameter + * @param fromLeft Are we positioning views based on the left edge? (i.e., + * building from left to right)? + * @return A view that has been added to the gallery + */ + private View makeAndAddView(int position, int offset, int x, boolean fromLeft) { + + View child; + if (!mDataChanged) { + child = mRecycler.get(position); + if (child != null) { + // Can reuse an existing view + int childLeft = child.getLeft(); + + // Remember left and right edges of where views have been placed + mRightMost = Math.max(mRightMost, childLeft + + child.getMeasuredWidth()); + mLeftMost = Math.min(mLeftMost, childLeft); + + // Position the view + setUpChild(child, offset, x, fromLeft); + + return child; + } + } + + // Nothing found in the recycler -- ask the adapter for a view + child = mAdapter.getView(position, null, this); + + // Position the view + setUpChild(child, offset, x, fromLeft); + + return child; + } + + /** + * Helper for makeAndAddView to set the position of a view and fill out its + * layout parameters. + * + * @param child The view to position + * @param offset Offset from the selected position + * @param x X-coordinate indicating where this view should be placed. This + * will either be the left or right edge of the view, depending on + * the fromLeft parameter + * @param fromLeft Are we positioning views based on the left edge? (i.e., + * building from left to right)? + */ + private void setUpChild(View child, int offset, int x, boolean fromLeft) { + + // Respect layout params that are already in the view. Otherwise + // make some up... + Gallery.LayoutParams lp = (Gallery.LayoutParams) child.getLayoutParams(); + if (lp == null) { + lp = (Gallery.LayoutParams) generateDefaultLayoutParams(); + } + + addViewInLayout(child, fromLeft != mIsRtl ? -1 : 0, lp/*, true*/); + + child.setSelected(offset == 0); + + // Get measure specs + int childHeightSpec = ViewGroup.getChildMeasureSpec(mHeightMeasureSpec, + mSpinnerPadding.top + mSpinnerPadding.bottom, lp.height); + int childWidthSpec = ViewGroup.getChildMeasureSpec(mWidthMeasureSpec, + mSpinnerPadding.left + mSpinnerPadding.right, lp.width); + + // Measure child + child.measure(childWidthSpec, childHeightSpec); + + int childLeft; + int childRight; + + // Position vertically based on gravity setting + int childTop = calculateTop(child, true); + int childBottom = childTop + child.getMeasuredHeight(); + + int width = child.getMeasuredWidth(); + if (fromLeft) { + childLeft = x; + childRight = childLeft + width; + } else { + childLeft = x - width; + childRight = x; + } + + child.layout(childLeft, childTop, childRight, childBottom); + } + + /** + * Figure out vertical placement based on mGravity + * + * @param child Child to place + * @return Where the top of the child should be + */ + private int calculateTop(View child, boolean duringLayout) { + int myHeight = duringLayout ? getMeasuredHeight() : getHeight(); + int childHeight = duringLayout ? child.getMeasuredHeight() : child.getHeight(); + + int childTop = 0; + + switch (mGravity) { + case Gravity.TOP: + childTop = mSpinnerPadding.top; + break; + case Gravity.CENTER_VERTICAL: + int availableSpace = myHeight - mSpinnerPadding.bottom + - mSpinnerPadding.top - childHeight; + childTop = mSpinnerPadding.top + (availableSpace / 2); + break; + case Gravity.BOTTOM: + childTop = myHeight - mSpinnerPadding.bottom - childHeight; + break; + } + return childTop; + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + + // Give everything to the gesture detector + boolean retValue = mGestureDetector.onTouchEvent(event); + + int action = event.getAction(); + if (action == MotionEvent.ACTION_UP) { + // Helper method for lifted finger + onUp(); + } else if (action == MotionEvent.ACTION_CANCEL) { + onCancel(); + } + + return retValue; + } + + @Override + public boolean onSingleTapUp(MotionEvent e) { + + if (mDownTouchPosition >= 0) { + + // An item tap should make it selected, so scroll to this child. + scrollToChild(mDownTouchPosition - mFirstPosition); + + // Also pass the click so the client knows, if it wants to. + if (mShouldCallbackOnUnselectedItemClick || mDownTouchPosition == mSelectedPosition) { + performItemClick(mDownTouchView, mDownTouchPosition, mAdapter + .getItemId(mDownTouchPosition)); + } + + return true; + } + + return false; + } + + @Override + public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { + + if (!mShouldCallbackDuringFling) { + // We want to suppress selection changes + + // Remove any future code to set mSuppressSelectionChanged = false + removeCallbacks(mDisableSuppressSelectionChangedRunnable); + + // This will get reset once we scroll into slots + if (!mSuppressSelectionChanged) mSuppressSelectionChanged = true; + } + + // Fling the gallery! + mFlingRunnable.startUsingVelocity((int) -velocityX); + + return true; + } + + @Override + public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { + + if (localLOGV) Log.v(TAG, String.valueOf(e2.getX() - e1.getX())); + + /* + * Now's a good time to tell our parent to stop intercepting our events! + * The user has moved more than the slop amount, since GestureDetector + * ensures this before calling this method. Also, if a parent is more + * interested in this touch's events than we are, it would have + * intercepted them by now (for example, we can assume when a Gallery is + * in the ListView, a vertical scroll would not end up in this method + * since a ListView would have intercepted it by now). + */ + parent.requestDisallowInterceptTouchEvent(true); + + // As the user scrolls, we want to callback selection changes so related- + // info on the screen is up-to-date with the gallery's selection + if (!mShouldCallbackDuringFling) { + if (mIsFirstScroll) { + /* + * We're not notifying the client of selection changes during + * the fling, and this scroll could possibly be a fling. Don't + * do selection changes until we're sure it is not a fling. + */ + if (!mSuppressSelectionChanged) mSuppressSelectionChanged = true; + postDelayed(mDisableSuppressSelectionChangedRunnable, SCROLL_TO_FLING_UNCERTAINTY_TIMEOUT); + } + } else { + if (mSuppressSelectionChanged) mSuppressSelectionChanged = false; + } + + // Track the motion + trackMotionScroll(-1 * (int) distanceX); + + mIsFirstScroll = false; + return true; + } + + @Override + public boolean onDown(MotionEvent e) { + + // Kill any existing fling/scroll + mFlingRunnable.stop(false); + + // Get the item's view that was touched + mDownTouchPosition = pointToPosition((int) e.getX(), (int) e.getY()); + + if (mDownTouchPosition >= 0) { + mDownTouchView = getChildAt(mDownTouchPosition - mFirstPosition); + mDownTouchView.setPressed(true); + } + + // Reset the multiple-scroll tracking state + mIsFirstScroll = true; + + // Must return true to get matching events for this down event. + return true; + } + + /** + * Called when a touch event's action is MotionEvent.ACTION_UP. + */ + void onUp() { + + if (mFlingRunnable.mScroller.isFinished()) { + scrollIntoSlots(); + } + + dispatchUnpress(); + } + + /** + * Called when a touch event's action is MotionEvent.ACTION_CANCEL. + */ + void onCancel() { + onUp(); + } + + @Override + public void onLongPress(MotionEvent e) { + + if (mDownTouchPosition < 0) { + return; + } + + long id = getItemIdAtPosition(mDownTouchPosition); + dispatchLongPress(mDownTouchView, mDownTouchPosition, id); + } + + // Unused methods from GestureDetector.OnGestureListener below + + @Override + public void onShowPress(MotionEvent e) { + } + + private void dispatchUnpress() { + + for (int i = getChildCount() - 1; i >= 0; i--) { + getChildAt(i).setPressed(false); + } + + setPressed(false); + } + + @Override + protected void dispatchSetPressed(boolean pressed) { + + // Show the pressed state on the selected child + if (mSelectedChild != null) { + mSelectedChild.setPressed(pressed); + } + } + + private boolean dispatchLongPress(View view, int position, long id) { + boolean handled = false; + + if (mOnItemLongClickListener != null) { + handled = mOnItemLongClickListener.onItemLongClick(this, mDownTouchView, + mDownTouchPosition, id); + } + + return handled; + } + + public boolean dispatchKeyEvent(KeyEvent event) { + // Gallery steals all key events + // return event.dispatch(this, null, null); + return false; + } + + private boolean scrollToChild(int childPosition) { + View child = getChildAt(childPosition); + + if (child != null) { + int distance = getCenterOfGallery() - getCenterOfView(child); + mFlingRunnable.startUsingDistance(distance); + return true; + } + + return false; + } + + @Override + void setSelectedPositionInt(int position) { + super.setSelectedPositionInt(position); + + // Updates any metadata we keep about the selected item. + updateSelectedItemMetadata(); + } + + private void updateSelectedItemMetadata() { + + View oldSelectedChild = mSelectedChild; + + View child = mSelectedChild = getChildAt(mSelectedPosition - mFirstPosition); + if (child == null) { + return; + } + + child.setSelected(true); + child.setFocusable(true); + + if (hasFocus()) { + child.requestFocus(); + } + + // We unfocus the old child down here so the above hasFocus check + // returns true + if (oldSelectedChild != null && oldSelectedChild != child) { + + // Make sure its drawable state doesn't contain 'selected' + oldSelectedChild.setSelected(false); + + // Make sure it is not focusable anymore, since otherwise arrow keys + // can make this one be focused + oldSelectedChild.setFocusable(false); + } + + } + + /** + * Describes how the child views are aligned. + * @param gravity + * + * @attr ref android.R.styleable#Gallery_gravity + */ + public void setGravity(int gravity) + { + if (mGravity != gravity) { + mGravity = gravity; + requestLayout(); + } + } + + protected int getChildDrawingOrder(int childCount, int i) { + int selectedIndex = mSelectedPosition - mFirstPosition; + + // Just to be safe + if (selectedIndex < 0) return i; + + if (i == childCount - 1) { + // Draw the selected child last + return selectedIndex; + } else if (i >= selectedIndex) { + // Move the children after the selected child earlier one + return i + 1; + } else { + // Keep the children before the selected child the same + return i; + } + } + + protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) { + // super.onFocusChanged(gainFocus, direction, previouslyFocusedRect); + + /* + * The gallery shows focus by focusing the selected item. So, give + * focus to our selected item instead. We steal keys from our + * selected item elsewhere. + */ + if (gainFocus && mSelectedChild != null) { + mSelectedChild.requestFocus(direction); + mSelectedChild.setSelected(true); + } + + } + + /** + * Responsible for fling behavior. Use {@link #startUsingVelocity(int)} to + * initiate a fling. Each frame of the fling is handled in {@link #run()}. + * A FlingRunnable will keep re-posting itself until the fling is done. + */ + private class FlingRunnable implements Runnable { + /** + * Tracks the decay of a fling scroll + */ + private Scroller mScroller; + + /** + * X value reported by mScroller on the previous fling + */ + private int mLastFlingX; + + public FlingRunnable() { + mScroller = new Scroller(getContext()); + } + + private void startCommon() { + // Remove any pending flings + removeCallbacks(this); + } + + public void startUsingVelocity(int initialVelocity) { + if (initialVelocity == 0) return; + + startCommon(); + + int initialX = initialVelocity < 0 ? Integer.MAX_VALUE : 0; + mLastFlingX = initialX; + mScroller.fling(initialX, 0, initialVelocity, 0, + 0, Integer.MAX_VALUE, 0, Integer.MAX_VALUE); + post(this); + } + + public void startUsingDistance(int distance) { + if (distance == 0) return; + + startCommon(); + + mLastFlingX = 0; + mScroller.startScroll(0, 0, -distance, 0, mAnimationDuration); + post(this); + } + + public void stop(boolean scrollIntoSlots) { + removeCallbacks(this); + endFling(scrollIntoSlots); + } + + private void endFling(boolean scrollIntoSlots) { + /* + * Force the scroller's status to finished (without setting its + * position to the end) + */ + mScroller.forceFinished(true); + + if (scrollIntoSlots) scrollIntoSlots(); + } + + @Override + public void run() { + + if (mItemCount == 0) { + endFling(true); + return; + } + + mShouldStopFling = false; + + final Scroller scroller = mScroller; + boolean more = scroller.computeScrollOffset(); + final int x = scroller.getCurrX(); + + // Flip sign to convert finger direction to list items direction + // (e.g. finger moving down means list is moving towards the top) + int delta = mLastFlingX - x; + + // Pretend that each frame of a fling scroll is a touch scroll + if (delta > 0) { + // Moving towards the left. Use leftmost view as mDownTouchPosition + mDownTouchPosition = mIsRtl ? (mFirstPosition + getChildCount() - 1) : + mFirstPosition; + + // Don't fling more than 1 screen + delta = Math.min(getWidth() - paddingLeft - paddingRight - 1, delta); + } else { + // Moving towards the right. Use rightmost view as mDownTouchPosition + mDownTouchPosition = mIsRtl ? mFirstPosition : + (mFirstPosition + getChildCount() - 1); + + // Don't fling more than 1 screen + delta = Math.max(-(getWidth() - paddingRight - paddingLeft - 1), delta); + } + + trackMotionScroll(delta); + + if (more && !mShouldStopFling) { + mLastFlingX = x; + post(this); + } else { + endFling(true); + } + } + + } + + /** + * Gallery extends LayoutParams to provide a place to hold current + * Transformation information along with previous position/transformation + * info. + */ + public static class LayoutParams extends ViewGroup.LayoutParams { + public LayoutParams(Context c, AttributeSet attrs) { + super(c, attrs); + } + + public LayoutParams(int w, int h) { + super(w, h); + } + + public LayoutParams(ViewGroup.LayoutParams source) { + super(source.width, source.height, source.weight); + } + } } diff --git a/src/api-impl/android/widget/ListView.java b/src/api-impl/android/widget/ListView.java index f2348008..2ae17cf1 100644 --- a/src/api-impl/android/widget/ListView.java +++ b/src/api-impl/android/widget/ListView.java @@ -104,4 +104,15 @@ public class ListView extends AbsListView { public void setSelection(int position, boolean animate) { super.setSelection(position + getHeaderViewsCount(), animate); } + + @Override + public View getSelectedView() { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'getSelectedView'"); + } + + @Override + public void setSelection(int position) { + setSelection(position, false); + } } diff --git a/src/api-impl/android/widget/Spinner.java b/src/api-impl/android/widget/Spinner.java index 2069adaf..6489921f 100644 --- a/src/api-impl/android/widget/Spinner.java +++ b/src/api-impl/android/widget/Spinner.java @@ -1,16 +1,61 @@ package android.widget; import android.content.Context; +import android.database.DataSetObserver; import android.util.AttributeSet; public class Spinner extends AbsSpinner { + private Observer observer = new Observer(); public Spinner(Context context) { super(context); + haveCustomMeasure = false; } public Spinner(Context context, AttributeSet attributeSet) { super(context, attributeSet); + haveCustomMeasure = false; } + public Spinner(Context context, AttributeSet attributeSet, int defStyle) { + super(context, attributeSet, defStyle); + haveCustomMeasure = false; + } + + @Override + protected native long native_constructor(Context context, AttributeSet attrs); + protected native void native_setAdapter(long widget, SpinnerAdapter adapter); + + public void setAdapter(SpinnerAdapter adapter) { + SpinnerAdapter oldAdapter = getAdapter(); + if (oldAdapter != null) + oldAdapter.unregisterDataSetObserver(observer); + super.setAdapter(adapter); + if (adapter != null) + adapter.registerDataSetObserver(observer); + native_setAdapter(this.widget, adapter); + } + + public SpinnerAdapter getAdapter() { + return (SpinnerAdapter) super.getAdapter(); + } + + @Override + public native void setOnItemSelectedListener(AdapterView.OnItemSelectedListener listener); + + private class Observer extends DataSetObserver { + + @Override + public void onChanged() { + Spinner.this.native_setAdapter(widget, getAdapter()); + } + @Override + public void onInvalidated() { + Spinner.this.native_setAdapter(widget, getAdapter()); + } + } + + @Override + void layout(int delta, boolean animate) {} + } \ No newline at end of file