package com.quran.labs.androidquran.widgets; import android.content.Context; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.support.v7.widget.AppCompatSpinner; import android.util.AttributeSet; import android.view.View; import android.widget.SpinnerAdapter; /** * An {@link AppCompatSpinner} that uses the last items in an adapter and a multiplier to * determine the width of the Spinner and its dropdown. * * AppCompatSpinner uses the measurement of the first 15 items to determine the width. */ public class QuranSpinner extends AppCompatSpinner { private static final int MAX_ITEMS_MEASURED = 15; private static final float WIDTH_MULTIPLIER = 1.1f; private static final Rect PADDING_RECT = new Rect(); private SpinnerAdapter adapter; public QuranSpinner(Context context) { super(context); } public QuranSpinner(Context context, AttributeSet attrs) { super(context, attrs); } public QuranSpinner(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @Override public void setAdapter(SpinnerAdapter adapter) { super.setAdapter(adapter); this.adapter = adapter; } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); if (MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.AT_MOST) { int calculatedWidth = calculateWidth(); int measuredWidth = getMeasuredWidth(); if (calculatedWidth > measuredWidth) { int width = Math.min(calculatedWidth, MeasureSpec.getSize(widthMeasureSpec)); setMeasuredDimension(width, getMeasuredHeight()); setDropDownWidth(calculatedWidth); // hack to fix an odd bug with Farsi - see quran/quran_android#849 // because we get incorrect width for Farsi, set the actual spinner width to the overall // desired width. this causes all subsequent children added to use the width of the // spinner to measure themselves instead of "wrap_content". Leaving this hack here for // non-Farsi languages as well, since it has a nice sub-benefit of causing the Spinner // to not change width when the selected item is changed to a longer/shorter item. getLayoutParams().width = width; } else { setDropDownWidth(measuredWidth); } } } private int calculateWidth() { if (adapter == null) { return 0; } int width = 0; View itemView = null; int itemType = 0; final int widthMeasureSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.UNSPECIFIED); final int heightMeasureSpec = MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.UNSPECIFIED); // Make sure the number of items we'll measure is capped. If it's a huge data set // with wildly varying sizes, oh well. final int end = adapter.getCount(); int start = Math.max(end - MAX_ITEMS_MEASURED, 0); for (int i = start; i < end; i++) { final int positionType = adapter.getItemViewType(i); if (positionType != itemType) { itemType = positionType; itemView = null; } itemView = adapter.getView(i, itemView, this); if (itemView.getLayoutParams() == null) { itemView.setLayoutParams(new LayoutParams( LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)); } itemView.measure(widthMeasureSpec, heightMeasureSpec); width = Math.max(width, itemView.getMeasuredWidth()); } // make sure to take the background padding into account Drawable drawable = getBackground(); if (drawable != null) { drawable.getPadding(PADDING_RECT); width += PADDING_RECT.left + PADDING_RECT.right; } width *= WIDTH_MULTIPLIER; // add some extra spacing return width; } }