package com.rey.material.widget; import android.content.Context; import android.content.res.ColorStateList; import android.content.res.TypedArray; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Typeface; import android.os.Build; import android.os.Parcel; import android.os.Parcelable; import android.support.annotation.NonNull; import android.util.AttributeSet; import android.util.TypedValue; import android.view.Gravity; import android.view.View; import android.view.ViewGroup; import android.view.animation.AnimationUtils; import android.view.animation.DecelerateInterpolator; import android.view.animation.Interpolator; import android.widget.BaseAdapter; import com.rey.material.R; import com.rey.material.drawable.BlankDrawable; import com.rey.material.util.ThemeUtil; import com.rey.material.util.TypefaceUtil; import java.util.Calendar; /** * Created by Rey on 12/26/2014. */ public class YearPicker extends ListView{ private YearAdapter mAdapter; private int mTextSize; private int mItemHeight; private int mSelectionColor; private int mAnimDuration; private Interpolator mInInterpolator; private Interpolator mOutInterpolator; private Typeface mTypeface; private int mItemRealHeight = -1; private int mPadding; private int mPositionShift; private int mDistanceShift; private Paint mPaint; /** * Interface definition for a callback to be invoked when the selected year is changed. */ public interface OnYearChangedListener{ /** * Called then the selected year is changed. * @param oldValue The old year value. * @param newValue The new year value. */ public void onYearChanged(int oldValue, int newValue); } private OnYearChangedListener mOnYearChangedListener; private static final int[][] STATES = new int[][]{ new int[]{-android.R.attr.state_checked}, new int[]{android.R.attr.state_checked}, }; private int[] mTextColors = new int[2]; private static final String YEAR_FORMAT = "%4d"; public YearPicker(Context context) { super(context); init(context, null, 0, 0); } public YearPicker(Context context, AttributeSet attrs) { super(context, attrs); init(context, attrs, 0, 0); } public YearPicker(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(context, attrs, defStyleAttr, 0); } public YearPicker(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); init(context, attrs, defStyleAttr, defStyleRes); } private void init(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes){ setWillNotDraw(false); mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mPaint.setStyle(Paint.Style.FILL); mAdapter = new YearAdapter(); setAdapter(mAdapter); setScrollBarStyle(SCROLLBARS_OUTSIDE_OVERLAY); setSelector(BlankDrawable.getInstance()); setDividerHeight(0); setCacheColorHint(Color.TRANSPARENT); setClipToPadding(false); mPadding = ThemeUtil.dpToPx(context, 4); applyStyle(context, attrs, defStyleAttr, defStyleRes); } public void applyStyle(int resId){ applyStyle(getContext(), null, 0, resId); } private void applyStyle(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes){ TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.YearPicker, defStyleAttr, defStyleRes); mTextSize = a.getDimensionPixelSize(R.styleable.YearPicker_dp_yearTextSize, context.getResources().getDimensionPixelOffset(R.dimen.abc_text_size_title_material)); int year = a.getInteger(R.styleable.YearPicker_dp_year, mAdapter.getYear()); int yearMin = a.getInteger(R.styleable.YearPicker_dp_yearMin, mAdapter.getMinYear()); int yearMax = a.getInteger(R.styleable.YearPicker_dp_yearMax, mAdapter.getMaxYear()); mItemHeight = a.getDimensionPixelSize(R.styleable.YearPicker_dp_yearItemHeight, ThemeUtil.dpToPx(context, 48)); mTextColors[0] = a.getColor(R.styleable.YearPicker_dp_textColor, 0xFF000000); mTextColors[1] = a.getColor(R.styleable.YearPicker_dp_textHighlightColor, 0xFFFFFFFF); mSelectionColor = a.getColor(R.styleable.YearPicker_dp_selectionColor, ThemeUtil.colorPrimary(context, 0xFF000000)); mAnimDuration = a.getInteger(R.styleable.YearPicker_dp_animDuration, context.getResources().getInteger(android.R.integer.config_mediumAnimTime)); int resId = a.getResourceId(R.styleable.YearPicker_dp_inInterpolator, 0); if(resId != 0) mInInterpolator = AnimationUtils.loadInterpolator(context, resId); else mInInterpolator = new DecelerateInterpolator(); resId = a.getResourceId(R.styleable.YearPicker_dp_outInterpolator, 0); if(resId != 0) mOutInterpolator = AnimationUtils.loadInterpolator(context, resId); else mOutInterpolator = new DecelerateInterpolator(); String familyName = a.getString(R.styleable.YearPicker_dp_fontFamily); int style = a.getInteger(R.styleable.YearPicker_dp_textStyle, Typeface.NORMAL); mTypeface = TypefaceUtil.load(context, familyName, style); a.recycle(); if(yearMax < yearMin) yearMax = Integer.MAX_VALUE; if(year < 0){ Calendar cal = Calendar.getInstance(); year = cal.get(Calendar.YEAR); } year = Math.max(yearMin, Math.min(yearMax, year)); setYearRange(yearMin, yearMax); setYear(year); mAdapter.notifyDataSetChanged(); } /** * Set the range of selectable year value. * @param min The minimum selectable year value. * @param max The maximum selectable year value. */ public void setYearRange(int min, int max){ mAdapter.setYearRange(min, max); } /** * Jump to a specific year. * @param year */ public void goTo(int year){ int position = mAdapter.positionOfYear(year) - mPositionShift; int offset = mDistanceShift; if(position < 0){ position = 0; offset = 0; } postSetSelectionFromTop(position, offset); } public void postSetSelectionFromTop(final int position, final int offset) { post(new Runnable() { @Override public void run() { setSelectionFromTop(position, offset); requestLayout(); } }); } /** * Set the selected year. * @param year The selected year value. */ public void setYear(int year){ if(mAdapter.getYear() == year) return; mAdapter.setYear(year); goTo(year); } /** * @return The selected year value. */ public int getYear(){ return mAdapter.getYear(); } /** * Set a listener will be called when the selected year value is changed. * @param listener The {@link YearPicker.OnYearChangedListener} will be called. */ public void setOnYearChangedListener(OnYearChangedListener listener){ mOnYearChangedListener = listener; } private void measureItemHeight(){ if(mItemRealHeight > 0) return; mPaint.setTextSize(mTextSize); mItemRealHeight = Math.max(Math.round(mPaint.measureText("9999", 0, 4)) + mPadding * 2, mItemHeight); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int heightMode = MeasureSpec.getMode(heightMeasureSpec); int heightSize = MeasureSpec.getSize(heightMeasureSpec); measureItemHeight(); if(heightMode != MeasureSpec.EXACTLY){ if(heightMode == MeasureSpec.AT_MOST){ int num = Math.min(mAdapter.getCount(), heightSize / mItemRealHeight); if(num >= 3) heightSize = mItemRealHeight * (num % 2 == 0 ? num - 1 : num); } else heightSize = mItemRealHeight * mAdapter.getCount(); heightMeasureSpec = MeasureSpec.makeMeasureSpec(heightSize + getPaddingTop() + getPaddingBottom(), MeasureSpec.EXACTLY); } super.onMeasure(widthMeasureSpec, heightMeasureSpec); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { float shift = (h / (float)mItemRealHeight - 1) / 2; mPositionShift = (int)Math.floor(shift); mPositionShift = shift > mPositionShift ? mPositionShift + 1 : mPositionShift; mDistanceShift = (int)((shift - mPositionShift) * mItemRealHeight) - getPaddingTop(); goTo(mAdapter.getYear()); } private class YearAdapter extends BaseAdapter implements OnClickListener{ private int mMinYear = 1990; private int mMaxYear = Integer.MAX_VALUE - 1; private int mCurYear = -1; public YearAdapter(){} public int getMinYear(){ return mMinYear; } public int getMaxYear(){ return mMaxYear; } public void setYearRange(int min, int max){ if(mMinYear != min || mMaxYear != max){ mMinYear = min; mMaxYear = max; notifyDataSetChanged(); } } public int positionOfYear(int year){ return year - mMinYear; } @Override public int getCount(){ return mMaxYear - mMinYear + 1; } @Override public Object getItem(int position){ return mMinYear + position; } @Override public long getItemId(int position) { return 0; } public void setYear(int year){ if(mCurYear != year){ int old = mCurYear; mCurYear = year; CircleCheckedTextView child = (CircleCheckedTextView) YearPicker.this.getChildAt(positionOfYear(old) - YearPicker.this.getFirstVisiblePosition()); if(child != null) child.setChecked(false); child = (CircleCheckedTextView) YearPicker.this.getChildAt(positionOfYear(mCurYear) - YearPicker.this.getFirstVisiblePosition()); if(child != null) child.setChecked(true); if(mOnYearChangedListener != null) mOnYearChangedListener.onYearChanged(old, mCurYear); } } public int getYear(){ return mCurYear; } @Override public void onClick(View v) { setYear((Integer)v.getTag()); } @Override public View getView(int position, View convertView, ViewGroup parent) { CircleCheckedTextView v = (CircleCheckedTextView)convertView; if(v == null){ v = new CircleCheckedTextView(getContext()); v.setGravity(Gravity.CENTER); if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) v.setTextAlignment(TEXT_ALIGNMENT_CENTER); v.setMinHeight(mItemRealHeight); v.setMaxHeight(mItemRealHeight); v.setAnimDuration(mAnimDuration); v.setInterpolator(mInInterpolator, mOutInterpolator); v.setBackgroundColor(mSelectionColor); v.setTypeface(mTypeface); v.setTextSize(TypedValue.COMPLEX_UNIT_PX, mTextSize); v.setTextColor(new ColorStateList(STATES, mTextColors)); v.setOnClickListener(this); } int year = (Integer)getItem(position); v.setTag(year); v.setText(String.format(YEAR_FORMAT, year)); v.setCheckedImmediately(year == mCurYear); return v; } } @Override public Parcelable onSaveInstanceState() { Parcelable superState = super.onSaveInstanceState(); SavedState ss = new SavedState(superState); ss.yearMin = mAdapter.getMinYear(); ss.yearMax = mAdapter.getMaxYear(); ss.year = mAdapter.getYear(); return ss; } @Override public void onRestoreInstanceState(Parcelable state) { SavedState ss = (SavedState) state; super.onRestoreInstanceState(ss.getSuperState()); setYearRange(ss.yearMin, ss.yearMax); setYear(ss.year); } static class SavedState extends BaseSavedState { int yearMin; int yearMax; int year; /** * Constructor called from {@link com.rey.material.widget.Switch#onSaveInstanceState()} */ SavedState(Parcelable superState) { super(superState); } /** * Constructor called from {@link #CREATOR} */ private SavedState(Parcel in) { super(in); yearMin = in.readInt(); yearMax = in.readInt(); year = in.readInt(); } @Override public void writeToParcel(@NonNull Parcel out, int flags) { super.writeToParcel(out, flags); out.writeValue(yearMin); out.writeValue(yearMax); out.writeValue(year); } @Override public String toString() { return "YearPicker.SavedState{" + Integer.toHexString(System.identityHashCode(this)) + " yearMin=" + yearMin + " yearMax=" + yearMax + " year=" + year + "}"; } 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]; } }; } }