package com.rey.material.widget;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.ColorStateList;
import android.database.DataSetObserver;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
import android.graphics.Rect;
import android.graphics.Region;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.annotation.NonNull;
import android.support.v4.content.ContextCompat;
import android.support.v4.view.GravityCompat;
import android.support.v4.view.ViewCompat;
import android.support.v4.view.ViewGroupCompat;
import android.support.v7.internal.widget.TintManager;
import android.support.v7.internal.widget.TintTypedArray;
import android.support.v7.internal.widget.ViewUtils;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.SparseArray;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
import android.widget.AdapterView;
import android.widget.FrameLayout;
import android.widget.GridView;
import android.widget.ListAdapter;
import android.widget.SpinnerAdapter;
import com.rey.material.R;
import com.rey.material.drawable.ArrowDrawable;
import com.rey.material.drawable.DividerDrawable;
import com.rey.material.drawable.RippleDrawable;
import com.rey.material.util.ThemeUtil;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class Spinner extends FrameLayout {
private static final int MAX_ITEMS_MEASURED = 15;
private static final int INVALID_POSITION = -1;
/**
* Interface definition for a callback to be invoked when a item's view is clicked.
*/
public interface OnItemClickListener{
/**
* Called when a item's view is clicked.
* @param parent The Spinner view.
* @param view The item view.
* @param position The position of item.
* @param id The id of item.
* @return false will make the Spinner doesn't select this item.
*/
boolean onItemClick(Spinner parent, View view, int position, long id);
}
/**
* Interface definition for a callback to be invoked when an item is selected.
*/
public interface OnItemSelectedListener{
/**
* Called when an item is selected.
* @param parent The Spinner view.
* @param view The item view.
* @param position The position of item.
* @param id The id of item.
*/
void onItemSelected(Spinner parent, View view, int position, long id);
}
private boolean mLabelEnable;
private LabelView mLabelView;
private SpinnerAdapter mAdapter;
private OnItemClickListener mOnItemClickListener;
private OnItemSelectedListener mOnItemSelectedListener;
private int mMinWidth;
private int mMinHeight;
private DropdownPopup mPopup;
private int mDropDownWidth;
private ArrowDrawable mArrowDrawable;
private int mArrowSize;
private int mArrowPadding;
private boolean mArrowAnimSwitchMode;
private DividerDrawable mDividerDrawable;
private int mDividerHeight;
private int mDividerPadding;
private int mGravity;
private boolean mDisableChildrenWhenDisabled;
private int mSelectedPosition = INVALID_POSITION;
private RecycleBin mRecycler = new RecycleBin();
private Rect mTempRect = new Rect();
private DropDownAdapter mTempAdapter;
private SpinnerDataSetObserver mDataSetObserver = new SpinnerDataSetObserver();
private TintManager mTintManager;
private RippleManager mRippleManager;
private boolean mIsRtl = false;
public Spinner(Context context) {
super(context);
init(context, null, R.attr.listPopupWindowStyle, 0);
}
public Spinner(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs, R.attr.listPopupWindowStyle, 0);
}
public Spinner(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs, defStyleAttr, 0);
}
public Spinner(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr);
init(context, attrs, defStyleAttr, defStyleRes);
}
public void init(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
setWillNotDraw(false);
applyStyle(context, attrs, defStyleAttr, defStyleRes);
if(isInEditMode()){
TextView tv = new TextView(context, attrs, defStyleAttr);
tv.setText("Item 1");
super.addView(tv);
}
setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
showPopup();
}
});
}
public void applyStyle(int resId){
applyStyle(getContext(), null, 0, resId);
}
private void applyStyle(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes){
CharSequence memoLabel = mLabelView == null ? null : mLabelView.getText();
removeAllViews();
getRippleManager().onCreate(this, context, attrs, defStyleAttr, defStyleRes);
TintTypedArray a = TintTypedArray.obtainStyledAttributes(context, attrs, R.styleable.Spinner, defStyleAttr, defStyleRes);
mLabelEnable = a.getBoolean(R.styleable.Spinner_spn_labelEnable, false);
if(mLabelEnable){
mLabelView = new LabelView(context);
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1)
mLabelView.setTextDirection(mIsRtl ? TEXT_DIRECTION_RTL : TEXT_DIRECTION_LTR);
mLabelView.setSingleLine(true);
int labelPadding = a.getDimensionPixelOffset(R.styleable.Spinner_spn_labelPadding, 0);
int labelTextSize = a.getDimensionPixelSize(R.styleable.Spinner_spn_labelTextSize, 0);
ColorStateList labelTextColor = a.getColorStateList(R.styleable.Spinner_spn_labelTextColor);
int labelTextAppearance = a.getResourceId(R.styleable.Spinner_spn_labelTextAppearance, 0);
int labelEllipsize = a.getInteger(R.styleable.Spinner_spn_labelEllipsize, 0);
CharSequence label = ThemeUtil.getString(a, R.styleable.Spinner_spn_label, memoLabel);
mLabelView.setText(label);
mLabelView.setPadding(0, 0, 0, labelPadding);
if(labelTextAppearance > 0)
mLabelView.setTextAppearance(context, labelTextAppearance);
if(labelTextSize > 0)
mLabelView.setTextSize(TypedValue.COMPLEX_UNIT_PX, labelTextSize);
if(labelTextColor != null)
mLabelView.setTextColor(labelTextColor);
switch (labelEllipsize) {
case 1:
mLabelView.setEllipsize(TextUtils.TruncateAt.START);
break;
case 2:
mLabelView.setEllipsize(TextUtils.TruncateAt.MIDDLE);
break;
case 3:
mLabelView.setEllipsize(TextUtils.TruncateAt.END);
break;
case 4:
mLabelView.setEllipsize(TextUtils.TruncateAt.MARQUEE);
break;
default:
mLabelView.setEllipsize(TextUtils.TruncateAt.END);
break;
}
addView(mLabelView, 0, new ViewGroup.LayoutParams(LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
}
mGravity = a.getInt(R.styleable.Spinner_android_gravity, Gravity.CENTER);
setMinimumWidth(a.getDimensionPixelOffset(R.styleable.Spinner_android_minWidth, 0));
setMinimumHeight(a.getDimensionPixelOffset(R.styleable.Spinner_android_minHeight, 0));
mPopup = new DropdownPopup(context, attrs, defStyleAttr, defStyleRes);
mPopup.setModal(true);
mDropDownWidth = a.getLayoutDimension(R.styleable.Spinner_android_dropDownWidth, LayoutParams.WRAP_CONTENT);
mPopup.setBackgroundDrawable(a.getDrawable(R.styleable.Spinner_android_popupBackground));
mPopup.setPromptText(a.getString(R.styleable.Spinner_prompt));
mPopup.setItemAnimation(a.getResourceId(R.styleable.Spinner_spn_popupItemAnimation, 0));
mPopup.setItemAnimationOffset(a.getInteger(R.styleable.Spinner_spn_popupItemAnimOffset, 50));
mDisableChildrenWhenDisabled = a.getBoolean(R.styleable.Spinner_disableChildrenWhenDisabled, false);
mArrowAnimSwitchMode = a.getBoolean(R.styleable.Spinner_spn_arrowSwitchMode, false);
int arrowAnimDuration = a.getInteger(R.styleable.Spinner_spn_arrowAnimDuration, 0);
mArrowSize = a.getDimensionPixelSize(R.styleable.Spinner_spn_arrowSize, ThemeUtil.dpToPx(getContext(), 4));
mArrowPadding = a.getDimensionPixelSize(R.styleable.Spinner_spn_arrowPadding, ThemeUtil.dpToPx(getContext(), 4));
ColorStateList arrowColor = a.getColorStateList(R.styleable.Spinner_spn_arrowColor);
if(arrowColor == null)
arrowColor = ColorStateList.valueOf(ThemeUtil.colorControlNormal(context, 0xFF000000));
int resId = a.getResourceId(R.styleable.Spinner_spn_arrowInterpolator, 0);
Interpolator arrowInterpolator = resId != 0 ? AnimationUtils.loadInterpolator(context, resId) : null;
boolean arrowClockwise = a.getBoolean(R.styleable.Spinner_spn_arrowAnimClockwise, true);
if(mArrowSize > 0) {
mArrowDrawable = new ArrowDrawable(ArrowDrawable.MODE_DOWN, mArrowSize, arrowColor, arrowAnimDuration, arrowInterpolator, arrowClockwise);
mArrowDrawable.setCallback(this);
}
mDividerHeight = a.getDimensionPixelOffset(R.styleable.Spinner_spn_dividerHeight, 0);
mDividerPadding = a.getDimensionPixelOffset(R.styleable.Spinner_spn_dividerPadding, 0);
int dividerAnimDuration = a.getInteger(R.styleable.Spinner_spn_dividerAnimDuration, 0);
ColorStateList dividerColor = a.getColorStateList(R.styleable.Spinner_spn_dividerColor);
if(dividerColor == null){
int[][] states = new int[][]{
new int[]{-android.R.attr.state_pressed},
new int[]{android.R.attr.state_pressed, android.R.attr.state_enabled},
};
int[] colors = new int[]{
ThemeUtil.colorControlNormal(context, 0xFF000000),
ThemeUtil.colorControlActivated(context, 0xFF000000),
};
dividerColor = new ColorStateList(states, colors);
}
if(mDividerHeight > 0){
mDividerDrawable = new DividerDrawable(mDividerHeight, dividerColor, dividerAnimDuration);
mDividerDrawable.setCallback(this);
}
mTintManager = a.getTintManager();
a.recycle();
if (mTempAdapter != null) {
mPopup.setAdapter(mTempAdapter);
mTempAdapter = null;
}
if(mAdapter != null)
setAdapter(mAdapter);
}
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
@Override
public void onRtlPropertiesChanged(int layoutDirection) {
boolean rtl = layoutDirection == LAYOUT_DIRECTION_RTL;
if(mIsRtl != rtl) {
mIsRtl = rtl;
if(mLabelView != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1)
mLabelView.setTextDirection(mIsRtl ? TEXT_DIRECTION_RTL : TEXT_DIRECTION_LTR);
requestLayout();
}
}
/**
* @return The selected item's view.
*/
public View getSelectedView() {
View v = getChildAt(getChildCount() - 1);
return v == mLabelView ? null : v;
}
/**
* Set the selected position of this Spinner.
* @param position The selected position.
*/
public void setSelection(int position) {
if(mAdapter != null)
position = Math.min(position, mAdapter.getCount() - 1);
if(mSelectedPosition != position){
mSelectedPosition = position;
if(mOnItemSelectedListener != null)
mOnItemSelectedListener.onItemSelected(this, getSelectedView(), position, mAdapter == null ? -1 : mAdapter.getItemId(position));
onDataInvalidated();
}
}
/**
* @return The selected posiiton.
*/
public int getSelectedItemPosition(){
return mSelectedPosition;
}
/**
* @return The selected item.
*/
public Object getSelectedItem(){
return mAdapter == null ? null : mAdapter.getItem(mSelectedPosition);
}
/**
* @return The adapter back this Spinner.
*/
public SpinnerAdapter getAdapter() {
return mAdapter;
}
/**
* Set an adapter for this Spinner.
* @param adapter
*/
public void setAdapter(SpinnerAdapter adapter) {
if(mAdapter != null)
mAdapter.unregisterDataSetObserver(mDataSetObserver);
mRecycler.clear();
mAdapter = adapter;
mAdapter.registerDataSetObserver(mDataSetObserver);
onDataChanged();
if (mPopup != null)
mPopup.setAdapter(new DropDownAdapter(adapter));
else
mTempAdapter = new DropDownAdapter(adapter);
}
/**
* Set the background drawable for the spinner's popup window of choices.
*
* @param background Background drawable
*
* @attr ref android.R.styleable#Spinner_popupBackground
*/
public void setPopupBackgroundDrawable(Drawable background) {
mPopup.setBackgroundDrawable(background);
}
/**
* Set the background drawable for the spinner's popup window of choices.
*
* @param resId Resource ID of a background drawable
*
* @attr ref android.R.styleable#Spinner_popupBackground
*/
public void setPopupBackgroundResource(int resId) {
setPopupBackgroundDrawable(mTintManager.getDrawable(resId));
}
/**
* Get the background drawable for the spinner's popup window of choices.
*
* @return background Background drawable
*
* @attr ref android.R.styleable#Spinner_popupBackground
*/
public Drawable getPopupBackground() {
return mPopup.getBackground();
}
/**
* Set a vertical offset in pixels for the spinner's popup window of choices.
*
* @param pixels Vertical offset in pixels
*
* @attr ref android.R.styleable#ListPopupWindow_dropDownVerticalOffset
*/
public void setDropDownVerticalOffset(int pixels) {
mPopup.setVerticalOffset(pixels);
}
/**
* Get the configured vertical offset in pixels for the spinner's popup window of choices.
*
* @return Vertical offset in pixels
*
* @attr ref android.R.styleable#ListPopupWindow_dropDownVerticalOffset
*/
public int getDropDownVerticalOffset() {
return mPopup.getVerticalOffset();
}
/**
* Set a horizontal offset in pixels for the spinner's popup window of choices.
*
* @param pixels Horizontal offset in pixels
*
* @attr ref android.R.styleable#ListPopupWindow_dropDownHorizontalOffset
*/
public void setDropDownHorizontalOffset(int pixels) {
mPopup.setHorizontalOffset(pixels);
}
/**
* Get the configured horizontal offset in pixels for the spinner's popup window of choices.
*
* @return Horizontal offset in pixels
*
* @attr ref android.R.styleable#ListPopupWindow_dropDownHorizontalOffset
*/
public int getDropDownHorizontalOffset() {
return mPopup.getHorizontalOffset();
}
/**
* Set the width of the spinner's popup window of choices in pixels. This value
* may also be set to {@link android.view.ViewGroup.LayoutParams#MATCH_PARENT}
* to match the width of the Spinner itself, or
* {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT} to wrap to the measured size
* of contained dropdown list items.
*
* @param pixels Width in pixels, WRAP_CONTENT, or MATCH_PARENT
*
* @attr ref android.R.styleable#Spinner_dropDownWidth
*/
public void setDropDownWidth(int pixels) {
mDropDownWidth = pixels;
}
/**
* Get the configured width of the spinner's popup window of choices in pixels.
* The returned value may also be {@link android.view.ViewGroup.LayoutParams#MATCH_PARENT}
* meaning the popup window will match the width of the Spinner itself, or
* {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT} to wrap to the measured size
* of contained dropdown list items.
*
* @return Width in pixels, WRAP_CONTENT, or MATCH_PARENT
*
* @attr ref android.R.styleable#Spinner_dropDownWidth
*/
public int getDropDownWidth() {
return mDropDownWidth;
}
@Override
public void setEnabled(boolean enabled) {
super.setEnabled(enabled);
if (mDisableChildrenWhenDisabled) {
final int count = getChildCount();
for (int i = 0; i < count; i++)
getChildAt(i).setEnabled(enabled);
}
}
@Override
public void setMinimumHeight(int minHeight) {
mMinHeight = minHeight;
super.setMinimumHeight(minHeight);
}
@Override
public void setMinimumWidth(int minWidth) {
mMinWidth = minWidth;
super.setMinimumWidth(minWidth);
}
/**
* Describes how the selected item view is positioned.
*
* @param gravity See {@link android.view.Gravity}
*
* @attr ref android.R.styleable#Spinner_gravity
*/
public void setGravity(int gravity) {
if (mGravity != gravity) {
if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) == 0)
gravity |= Gravity.START;
mGravity = gravity;
requestLayout();
}
}
@Override
public int getBaseline() {
View child = getSelectedView();
if (child != null) {
final int childBaseline = child.getBaseline();
return childBaseline >= 0 ? child.getTop() + childBaseline : -1;
}
return -1;
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
if (mPopup != null && mPopup.isShowing())
mPopup.dismiss();
}
@Override
public void setBackgroundDrawable(Drawable drawable) {
Drawable background = getBackground();
if(background instanceof RippleDrawable && !(drawable instanceof RippleDrawable))
((RippleDrawable) background).setBackgroundDrawable(drawable);
else
super.setBackgroundDrawable(drawable);
}
protected RippleManager getRippleManager(){
if(mRippleManager == null){
synchronized (RippleManager.class){
if(mRippleManager == null)
mRippleManager = new RippleManager();
}
}
return mRippleManager;
}
@Override
public void setOnClickListener(OnClickListener l) {
RippleManager rippleManager = getRippleManager();
if (l == rippleManager)
super.setOnClickListener(l);
else {
rippleManager.setOnClickListener(l);
setOnClickListener(rippleManager);
}
}
@Override
public boolean onTouchEvent(@NonNull MotionEvent event) {
boolean result = super.onTouchEvent(event);
return getRippleManager().onTouchEvent(event) || result;
}
/**
* Set a listener that will be called when a item's view is clicked.
* @param l The {@link Spinner.OnItemClickListener} will be called.
*/
public void setOnItemClickListener(OnItemClickListener l) {
mOnItemClickListener = l;
}
/**
* Set a listener that will be called when an item is selected.
* @param l The {@link Spinner.OnItemSelectedListener} will be called.
*/
public void setOnItemSelectedListener(OnItemSelectedListener l) {
mOnItemSelectedListener = l;
}
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
return true;
}
@Override
protected boolean verifyDrawable(Drawable who) {
return super.verifyDrawable(who) || mArrowDrawable == who || mDividerDrawable == who;
}
private int getArrowDrawableWidth(){
return mArrowDrawable != null ? mArrowSize + mArrowPadding * 2 : 0;
}
private int getDividerDrawableHeight(){
return mDividerHeight > 0 ? mDividerHeight + mDividerPadding : 0;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int paddingHorizontal = getPaddingLeft() + getPaddingRight() + getArrowDrawableWidth();
int paddingVertical = getPaddingTop() + getPaddingBottom() + getDividerDrawableHeight();
int labelWidth = 0;
int labelHeight = 0;
if(mLabelView != null){
mLabelView.measure(MeasureSpec.makeMeasureSpec(widthSize - paddingHorizontal, widthMode), MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
labelWidth = mLabelView.getMeasuredWidth();
labelHeight = mLabelView.getMeasuredHeight();
}
int width = 0;
int height = 0;
View v = getSelectedView();
if(v != null){
int ws;
int hs;
ViewGroup.LayoutParams params = v.getLayoutParams();
switch (params.width){
case ViewGroup.LayoutParams.WRAP_CONTENT:
ws = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
break;
case ViewGroup.LayoutParams.MATCH_PARENT:
ws = MeasureSpec.makeMeasureSpec(widthSize - paddingHorizontal, widthMode);
break;
default:
ws = MeasureSpec.makeMeasureSpec(params.width, MeasureSpec.EXACTLY);
break;
}
switch (params.height){
case ViewGroup.LayoutParams.WRAP_CONTENT:
hs = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
break;
case ViewGroup.LayoutParams.MATCH_PARENT:
hs = MeasureSpec.makeMeasureSpec(heightSize - paddingVertical - labelHeight, heightMode);
break;
default:
hs = MeasureSpec.makeMeasureSpec(params.height, MeasureSpec.EXACTLY);
break;
}
v.measure(ws, hs);
width = v.getMeasuredWidth();
height = v.getMeasuredHeight();
}
width = Math.max(mMinWidth, Math.max(labelWidth, width) + paddingHorizontal);
height = Math.max(mMinHeight, height + labelHeight + paddingVertical);
switch (widthMode){
case MeasureSpec.AT_MOST:
width = Math.min(widthSize, width);
break;
case MeasureSpec.EXACTLY:
width = widthSize;
break;
}
switch (heightMode){
case MeasureSpec.AT_MOST:
height = Math.min(heightSize, height);
break;
case MeasureSpec.EXACTLY:
height = heightSize;
break;
}
setMeasuredDimension(width, height);
width -= paddingHorizontal;
height -= labelHeight + paddingVertical;
if(v != null && (v.getMeasuredWidth() != width || v.getMeasuredHeight() != height))
v.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int w = r - l;
int h = b - t;
int arrowWidth = getArrowDrawableWidth();
if(mArrowDrawable != null) {
int top = getPaddingTop() + (mLabelView == null ? 0 : mLabelView.getMeasuredHeight());
int bottom = h - getDividerDrawableHeight() - getPaddingBottom();
if(mIsRtl)
mArrowDrawable.setBounds(getPaddingLeft(), top, getPaddingLeft() + arrowWidth, bottom);
else
mArrowDrawable.setBounds(getWidth() - getPaddingRight() - arrowWidth, top, getWidth() - getPaddingRight(), bottom);
}
if(mDividerDrawable != null)
mDividerDrawable.setBounds(getPaddingLeft(), h - mDividerHeight - getPaddingBottom(), w - getPaddingRight(), h - getPaddingBottom());
int childLeft = mIsRtl ? (getPaddingLeft() + arrowWidth) : getPaddingLeft();
int childRight = mIsRtl ? (w - getPaddingRight()) : (w - getPaddingRight() - arrowWidth);
int childTop = getPaddingTop();
int childBottom = h - getPaddingBottom();
if(mLabelView != null){
if(mIsRtl)
mLabelView.layout(childRight - mLabelView.getMeasuredWidth(), childTop, childRight, childTop + mLabelView.getMeasuredHeight());
else
mLabelView.layout(childLeft, childTop, childLeft + mLabelView.getMeasuredWidth(), childTop + mLabelView.getMeasuredHeight());
childTop += mLabelView.getMeasuredHeight();
}
View v = getSelectedView();
if(v != null){
int x, y;
int horizontalGravity = mGravity & Gravity.HORIZONTAL_GRAVITY_MASK;
if(horizontalGravity == Gravity.START)
horizontalGravity = mIsRtl ? Gravity.RIGHT : Gravity.LEFT;
else if(horizontalGravity == Gravity.END)
horizontalGravity = mIsRtl ? Gravity.LEFT : Gravity.RIGHT;
switch (horizontalGravity) {
case Gravity.LEFT:
x = childLeft;
break;
case Gravity.CENTER_HORIZONTAL:
x = (childRight - childLeft - v.getMeasuredWidth()) / 2 + childLeft;
break;
case Gravity.RIGHT:
x = childRight - v.getMeasuredWidth();
break;
default:
x = (childRight - childLeft - v.getMeasuredWidth()) / 2 + childLeft;
break;
}
int verticalGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
switch (verticalGravity) {
case Gravity.TOP:
y = childTop;
break;
case Gravity.CENTER_VERTICAL:
y = (childBottom - childTop - v.getMeasuredHeight()) / 2 + childTop;
break;
case Gravity.BOTTOM:
y = childBottom - v.getMeasuredHeight();
break;
default:
y = (childBottom - childTop - v.getMeasuredHeight()) / 2 + childTop;
break;
}
v.layout(x, y, x + v.getMeasuredWidth(), y + v.getMeasuredHeight());
}
}
@Override
public void draw(@NonNull Canvas canvas) {
super.draw(canvas);
if(mDividerDrawable != null)
mDividerDrawable.draw(canvas);
if(mArrowDrawable != null)
mArrowDrawable.draw(canvas);
}
@Override
protected void drawableStateChanged() {
super.drawableStateChanged();
if(mArrowDrawable != null)
mArrowDrawable.setState(getDrawableState());
if(mDividerDrawable != null)
mDividerDrawable.setState(getDrawableState());
}
public boolean performItemClick(View view, int position, long id) {
if (mOnItemClickListener != null) {
// playSoundEffect(SoundEffectConstants.CLICK);
// if (view != null)
// view.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
if(mOnItemClickListener.onItemClick(this, view, position, id))
setSelection(position);
return true;
}
else
setSelection(position);
return false;
}
private void onDataChanged(){
if(mSelectedPosition == INVALID_POSITION)
setSelection(0);
else if(mSelectedPosition < mAdapter.getCount())
onDataInvalidated();
else
setSelection(mAdapter.getCount() - 1);
}
private void onDataInvalidated(){
if(mAdapter == null)
return;
if(mLabelView == null)
removeAllViews();
else
for(int i = getChildCount() - 1; i > 0; i--)
removeViewAt(i);
int type = mAdapter.getItemViewType(mSelectedPosition);
View v = mAdapter.getView(mSelectedPosition, mRecycler.get(type), this);
v.setFocusable(false);
v.setClickable(false);
super.addView(v);
mRecycler.put(type, v);
}
private void showPopup(){
if (!mPopup.isShowing()){
mPopup.show();
final ListView lv = mPopup.getListView();
if(lv != null){
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB)
lv.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
lv.setSelection(getSelectedItemPosition());
if(mArrowDrawable != null && mArrowAnimSwitchMode)
lv.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
lv.getViewTreeObserver().removeOnPreDrawListener(this);
mArrowDrawable.setMode(ArrowDrawable.MODE_UP, true);
return true;
}
});
}
}
}
private void onPopupDismissed(){
if(mArrowDrawable != null)
mArrowDrawable.setMode(ArrowDrawable.MODE_DOWN, true);
}
private int measureContentWidth(SpinnerAdapter adapter, Drawable background) {
if (adapter == null)
return 0;
int width = 0;
View itemView = null;
int itemType = 0;
final int widthMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
final int heightMeasureSpec = MeasureSpec.makeMeasureSpec(0, 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.
int start = Math.max(0, getSelectedItemPosition());
final int end = Math.min(adapter.getCount(), start + MAX_ITEMS_MEASURED);
final int count = end - start;
start = Math.max(0, start - (MAX_ITEMS_MEASURED - count));
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, null);
if (itemView.getLayoutParams() == null)
itemView.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
itemView.measure(widthMeasureSpec, heightMeasureSpec);
width = Math.max(width, itemView.getMeasuredWidth());
}
// Add background padding to measured width
if (background != null) {
background.getPadding(mTempRect);
width += mTempRect.left + mTempRect.right;
}
return width;
}
static class SavedState extends BaseSavedState {
int position;
boolean showDropdown;
SavedState(Parcelable superState) {
super(superState);
}
/**
* Constructor called from {@link #CREATOR}
*/
SavedState(Parcel in) {
super(in);
position = in.readInt();
showDropdown = in.readByte() != 0;
}
@Override
public void writeToParcel(@NonNull Parcel out, int flags) {
super.writeToParcel(out, flags);
out.writeInt(position);
out.writeByte((byte) (showDropdown ? 1 : 0));
}
@Override
public String toString() {
return "AbsSpinner.SavedState{"
+ Integer.toHexString(System.identityHashCode(this))
+ " position=" + position
+ " showDropdown=" + showDropdown + "}";
}
public static final Creator<SavedState> CREATOR
= new Creator<SavedState>() {
public SavedState createFromParcel(Parcel in) {
return new SavedState(in);
}
public SavedState[] newArray(int size) {
return new SavedState[size];
}
};
}
@Override
public Parcelable onSaveInstanceState() {
Parcelable superState = super.onSaveInstanceState();
SavedState ss = new SavedState(superState);
ss.position = getSelectedItemPosition();
ss.showDropdown = mPopup != null && mPopup.isShowing();
return ss;
}
@Override
public void onRestoreInstanceState(Parcelable state) {
SavedState ss = (SavedState) state;
super.onRestoreInstanceState(ss.getSuperState());
setSelection(ss.position);
if (ss.showDropdown) {
ViewTreeObserver vto = getViewTreeObserver();
if (vto != null) {
final ViewTreeObserver.OnGlobalLayoutListener listener = new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
showPopup();
final ViewTreeObserver vto = getViewTreeObserver();
if (vto != null)
vto.removeGlobalOnLayoutListener(this);
}
};
vto.addOnGlobalLayoutListener(listener);
}
}
}
private class SpinnerDataSetObserver extends DataSetObserver{
@Override
public void onChanged() {
onDataChanged();
}
@Override
public void onInvalidated() {
onDataInvalidated();
}
}
private class RecycleBin {
private final SparseArray<View> mScrapHeap = new SparseArray<>();
public void put(int type, View v) {
mScrapHeap.put(type, v);
}
View get(int type) {
View result = mScrapHeap.get(type);
if (result != null)
mScrapHeap.delete(type);
return result;
}
void clear() {
final SparseArray<View> scrapHeap = mScrapHeap;
scrapHeap.clear();
}
}
private static class DropDownAdapter implements ListAdapter, SpinnerAdapter, OnClickListener {
private SpinnerAdapter mAdapter;
private ListAdapter mListAdapter;
private AdapterView.OnItemClickListener mOnItemClickListener;
/**
* <p>Creates a new ListAdapter wrapper for the specified adapter.</p>
*
* @param adapter the Adapter to transform into a ListAdapter
*/
public DropDownAdapter(SpinnerAdapter adapter) {
this.mAdapter = adapter;
if (adapter instanceof ListAdapter)
this.mListAdapter = (ListAdapter) adapter;
}
public void setOnItemClickListener(AdapterView.OnItemClickListener listener){
mOnItemClickListener = listener;
}
@Override
public void onClick(View v) {
int position = (Integer) v.getTag();
if(mOnItemClickListener != null)
mOnItemClickListener.onItemClick(null, v, position, 0);
}
public int getCount() {
return mAdapter == null ? 0 : mAdapter.getCount();
}
public Object getItem(int position) {
return mAdapter == null ? null : mAdapter.getItem(position);
}
public long getItemId(int position) {
return mAdapter == null ? -1 : mAdapter.getItemId(position);
}
public View getView(int position, View convertView, ViewGroup parent) {
View v = getDropDownView(position, convertView, parent);
v.setOnClickListener(this);
v.setTag(position);
return v;
}
public View getDropDownView(int position, View convertView, ViewGroup parent) {
return (mAdapter == null) ? null : mAdapter.getDropDownView(position, convertView, parent);
}
public boolean hasStableIds() {
return mAdapter != null && mAdapter.hasStableIds();
}
/**
* If the wrapped SpinnerAdapter is also a ListAdapter, delegate this call. Otherwise,
* return true.
*/
public boolean areAllItemsEnabled() {
final ListAdapter adapter = mListAdapter;
return adapter == null || adapter.areAllItemsEnabled();
}
/**
* If the wrapped SpinnerAdapter is also a ListAdapter, delegate this call. Otherwise,
* return true.
*/
public boolean isEnabled(int position) {
final ListAdapter adapter = mListAdapter;
return adapter == null || adapter.isEnabled(position);
}
public int getItemViewType(int position) {
final ListAdapter adapter = mListAdapter;
if (adapter != null)
return adapter.getItemViewType(position);
else
return 0;
}
public int getViewTypeCount() {
final ListAdapter adapter = mListAdapter;
if (adapter != null)
return adapter.getViewTypeCount();
else
return 1;
}
public boolean isEmpty() {
return getCount() == 0;
}
@Override
public void registerDataSetObserver(DataSetObserver observer) {
if (mAdapter != null)
mAdapter.registerDataSetObserver(observer);
}
@Override
public void unregisterDataSetObserver(DataSetObserver observer) {
if (mAdapter != null)
mAdapter.unregisterDataSetObserver(observer);
}
}
private class LabelView extends android.widget.TextView{
public LabelView(Context context) {
super(context);
}
@Override
protected int[] onCreateDrawableState(int extraSpace) {
return Spinner.this.getDrawableState();
}
}
private class DropdownPopup extends ListPopupWindow {
private CharSequence mHintText;
private DropDownAdapter mAdapter;
private ViewTreeObserver.OnGlobalLayoutListener layoutListener = new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
computeContentWidth();
// Use super.show here to update; we don't want to move the selected
// position or adjust other things that would be reset otherwise.
DropdownPopup.super.show();
}
};
public DropdownPopup(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
setAnchorView(Spinner.this);
setModal(true);
setPromptPosition(POSITION_PROMPT_ABOVE);
setOnDismissListener(new PopupWindow.OnDismissListener() {
@SuppressWarnings("deprecation")
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
@Override
public void onDismiss() {
final ViewTreeObserver vto = getViewTreeObserver();
if (vto != null) {
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN)
vto.removeOnGlobalLayoutListener(layoutListener);
else
vto.removeGlobalOnLayoutListener(layoutListener);
}
onPopupDismissed();
}
});
}
@Override
public void setAdapter(ListAdapter adapter) {
super.setAdapter(adapter);
mAdapter = (DropDownAdapter)adapter;
mAdapter.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View v, int position, long id) {
Spinner.this.performItemClick(v, position, mAdapter.getItemId(position));
dismiss();
}
});
}
public CharSequence getHintText() {
return mHintText;
}
public void setPromptText(CharSequence hintText) {
mHintText = hintText;
}
void computeContentWidth() {
final Drawable background = getBackground();
int hOffset = 0;
if (background != null) {
background.getPadding(mTempRect);
hOffset = ViewUtils.isLayoutRtl(Spinner.this) ? mTempRect.right : -mTempRect.left;
} else
mTempRect.left = mTempRect.right = 0;
final int spinnerPaddingLeft = Spinner.this.getPaddingLeft();
final int spinnerPaddingRight = Spinner.this.getPaddingRight();
final int spinnerWidth = Spinner.this.getWidth();
if (mDropDownWidth == WRAP_CONTENT) {
int contentWidth = measureContentWidth((SpinnerAdapter) mAdapter, getBackground());
final int contentWidthLimit = getContext().getResources().getDisplayMetrics().widthPixels - mTempRect.left - mTempRect.right;
if (contentWidth > contentWidthLimit)
contentWidth = contentWidthLimit;
setContentWidth(Math.max(contentWidth, spinnerWidth - spinnerPaddingLeft - spinnerPaddingRight));
} else if (mDropDownWidth == MATCH_PARENT)
setContentWidth(spinnerWidth - spinnerPaddingLeft - spinnerPaddingRight);
else
setContentWidth(mDropDownWidth);
if (ViewUtils.isLayoutRtl(Spinner.this))
hOffset += spinnerWidth - spinnerPaddingRight - getWidth();
else
hOffset += spinnerPaddingLeft;
setHorizontalOffset(hOffset);
}
public void show() {
final boolean wasShowing = isShowing();
computeContentWidth();
setInputMethodMode(ListPopupWindow.INPUT_METHOD_NOT_NEEDED);
super.show();
if (wasShowing) {
// Skip setting up the layout/dismiss listener below. If we were previously
// showing it will still stick around.
return;
}
// Make sure we hide if our anchor goes away.
// TODO: This might be appropriate to push all the way down to PopupWindow,
// but it may have other side effects to investigate first. (Text editing handles, etc.)
final ViewTreeObserver vto = getViewTreeObserver();
if (vto != null)
vto.addOnGlobalLayoutListener(layoutListener);
}
}
}