Spinner方向未更改:API17/18设置RTL无效,求技术指导
Hey there! I totally get the frustration—RTL support on older Android versions can be really spotty, even with AppCompat. Let's break down why your current setup isn't working and how to fix it.
Why the Problem Happens
On API 17-18, while android:layoutDirection="rtl" is technically supported at the View level, the AppCompatSpinner implementation doesn't fully handle this attribute. It won't automatically flip the dropdown arrow position or adjust text alignment like it does in newer APIs (27+). Using ViewCompat directly also doesn't trigger the internal layout adjustments needed for Spinner specifically.
Solution 1: Custom RTL Spinner Subclass
The most reliable fix is to create a custom AppCompatSpinner that manually handles RTL layout for older versions. This way, you can control text alignment and dropdown arrow position directly:
public class RTLCompatSpinner extends AppCompatSpinner { public RTLCompatSpinner(Context context) { super(context); initRTLSupport(); } public RTLCompatSpinner(Context context, AttributeSet attrs) { super(context, attrs); initRTLSupport(); } public RTLCompatSpinner(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); initRTLSupport(); } private void initRTLSupport() { // Target only API 17-18 (Jelly Bean MR1 to MR2) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1 && Build.VERSION.SDK_INT <= Build.VERSION_CODES.JELLY_BEAN_MR2) { // Align text to the right (RTL style) setGravity(Gravity.END | Gravity.CENTER_VERTICAL); // Adjust dropdown arrow position using reflection try { Field popupField = AppCompatSpinner.class.getDeclaredField("mPopup"); popupField.setAccessible(true); ListPopupWindow listPopup = (ListPopupWindow) popupField.get(this); // Offset the popup to align with the right side of the spinner int arrowOffset = getResources().getDimensionPixelSize(R.dimen.spinner_arrow_offset); listPopup.setHorizontalOffset(-arrowOffset); } catch (NoSuchFieldException | IllegalAccessException e) { e.printStackTrace(); } } } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { super.onLayout(changed, l, t, r, b); // Re-apply gravity in case layout changes if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1 && Build.VERSION.SDK_INT <= Build.VERSION_CODES.JELLY_BEAN_MR2) { setGravity(Gravity.END | Gravity.CENTER_VERTICAL); } } }
Then replace your XML AppCompatSpinner with this custom class:
<com.your.app.package.RTLCompatSpinner android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginStart="8dp" android:layout_marginEnd="8dp" android:layout_weight="1" android:layoutDirection="rtl" />
(Don't forget to replace com.your.app.package with your actual package name)
Solution 2: Manual RTL Adjustment in Code
If you don't want to create a custom class, you can adjust the Spinner directly in your Activity/Fragment:
Spinner yourSpinner = findViewById(R.id.your_spinner_id); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1 && Build.VERSION.SDK_INT <= Build.VERSION_CODES.JELLY_BEAN_MR2) { // Align text to the right yourSpinner.setGravity(Gravity.END | Gravity.CENTER_VERTICAL); // Fix dropdown popup position if (yourSpinner instanceof AppCompatSpinner) { try { Field popupField = AppCompatSpinner.class.getDeclaredField("mPopup"); popupField.setAccessible(true); ListPopupWindow popup = (ListPopupWindow) popupField.get(yourSpinner); // Shift the popup to appear on the right side of the spinner popup.setHorizontalOffset(-yourSpinner.getWidth()); } catch (Exception e) { e.printStackTrace(); } } }
Notes
- The reflection part is necessary because
AppCompatSpinnerdoesn't expose the popup window publicly. It's a bit hacky but works reliably for these older versions. - For the
spinner_arrow_offsetdimension, you can use a value like16dpto adjust the arrow's position to look natural.
内容的提问来源于stack exchange,提问作者Abolfazl Haghighi

