1/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.widget;
18
19import android.content.Context;
20import android.content.res.Resources;
21import android.icu.util.Calendar;
22import android.util.AttributeSet;
23import android.view.LayoutInflater;
24import android.view.View;
25import android.view.ViewGroup;
26import android.view.accessibility.AccessibilityEvent;
27
28import com.android.internal.R;
29
30/**
31 * Displays a selectable list of years.
32 */
33class YearPickerView extends ListView {
34    private final YearAdapter mAdapter;
35    private final int mViewSize;
36    private final int mChildSize;
37
38    private OnYearSelectedListener mOnYearSelectedListener;
39
40    public YearPickerView(Context context, AttributeSet attrs) {
41        this(context, attrs, R.attr.listViewStyle);
42    }
43
44    public YearPickerView(Context context, AttributeSet attrs, int defStyleAttr) {
45        this(context, attrs, defStyleAttr, 0);
46    }
47
48    public YearPickerView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
49        super(context, attrs, defStyleAttr, defStyleRes);
50
51        final LayoutParams frame = new LayoutParams(
52                LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
53        setLayoutParams(frame);
54
55        final Resources res = context.getResources();
56        mViewSize = res.getDimensionPixelOffset(R.dimen.datepicker_view_animator_height);
57        mChildSize = res.getDimensionPixelOffset(R.dimen.datepicker_year_label_height);
58
59        setOnItemClickListener(new OnItemClickListener() {
60            @Override
61            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
62                final int year = mAdapter.getYearForPosition(position);
63                mAdapter.setSelection(year);
64
65                if (mOnYearSelectedListener != null) {
66                    mOnYearSelectedListener.onYearChanged(YearPickerView.this, year);
67                }
68            }
69        });
70
71        mAdapter = new YearAdapter(getContext());
72        setAdapter(mAdapter);
73    }
74
75    public void setOnYearSelectedListener(OnYearSelectedListener listener) {
76        mOnYearSelectedListener = listener;
77    }
78
79    /**
80     * Sets the currently selected year. Jumps immediately to the new year.
81     *
82     * @param year the target year
83     */
84    public void setYear(final int year) {
85        mAdapter.setSelection(year);
86
87        post(new Runnable() {
88            @Override
89            public void run() {
90                final int position = mAdapter.getPositionForYear(year);
91                if (position >= 0 && position < getCount()) {
92                    setSelectionCentered(position);
93                }
94            }
95        });
96    }
97
98    public void setSelectionCentered(int position) {
99        final int offset = mViewSize / 2 - mChildSize / 2;
100        setSelectionFromTop(position, offset);
101    }
102
103    public void setRange(Calendar min, Calendar max) {
104        mAdapter.setRange(min, max);
105    }
106
107    private static class YearAdapter extends BaseAdapter {
108        private static final int ITEM_LAYOUT = R.layout.year_label_text_view;
109        private static final int ITEM_TEXT_APPEARANCE =
110                R.style.TextAppearance_Material_DatePicker_List_YearLabel;
111        private static final int ITEM_TEXT_ACTIVATED_APPEARANCE =
112                R.style.TextAppearance_Material_DatePicker_List_YearLabel_Activated;
113
114        private final LayoutInflater mInflater;
115
116        private int mActivatedYear;
117        private int mMinYear;
118        private int mCount;
119
120        public YearAdapter(Context context) {
121            mInflater = LayoutInflater.from(context);
122        }
123
124        public void setRange(Calendar minDate, Calendar maxDate) {
125            final int minYear = minDate.get(Calendar.YEAR);
126            final int count = maxDate.get(Calendar.YEAR) - minYear + 1;
127
128            if (mMinYear != minYear || mCount != count) {
129                mMinYear = minYear;
130                mCount = count;
131                notifyDataSetInvalidated();
132            }
133        }
134
135        public boolean setSelection(int year) {
136            if (mActivatedYear != year) {
137                mActivatedYear = year;
138                notifyDataSetChanged();
139                return true;
140            }
141            return false;
142        }
143
144        @Override
145        public int getCount() {
146            return mCount;
147        }
148
149        @Override
150        public Integer getItem(int position) {
151            return getYearForPosition(position);
152        }
153
154        @Override
155        public long getItemId(int position) {
156            return getYearForPosition(position);
157        }
158
159        public int getPositionForYear(int year) {
160            return year - mMinYear;
161        }
162
163        public int getYearForPosition(int position) {
164            return mMinYear + position;
165        }
166
167        @Override
168        public boolean hasStableIds() {
169            return true;
170        }
171
172        @Override
173        public View getView(int position, View convertView, ViewGroup parent) {
174            final TextView v;
175            final boolean hasNewView = convertView == null;
176            if (hasNewView) {
177                v = (TextView) mInflater.inflate(ITEM_LAYOUT, parent, false);
178            } else {
179                v = (TextView) convertView;
180            }
181
182            final int year = getYearForPosition(position);
183            final boolean activated = mActivatedYear == year;
184
185            if (hasNewView || v.isActivated() != activated) {
186                final int textAppearanceResId;
187                if (activated && ITEM_TEXT_ACTIVATED_APPEARANCE != 0) {
188                    textAppearanceResId = ITEM_TEXT_ACTIVATED_APPEARANCE;
189                } else {
190                    textAppearanceResId = ITEM_TEXT_APPEARANCE;
191                }
192                v.setTextAppearance(textAppearanceResId);
193                v.setActivated(activated);
194            }
195
196            v.setText(Integer.toString(year));
197            return v;
198        }
199
200        @Override
201        public int getItemViewType(int position) {
202            return 0;
203        }
204
205        @Override
206        public int getViewTypeCount() {
207            return 1;
208        }
209
210        @Override
211        public boolean isEmpty() {
212            return false;
213        }
214
215        @Override
216        public boolean areAllItemsEnabled() {
217            return true;
218        }
219
220        @Override
221        public boolean isEnabled(int position) {
222            return true;
223        }
224    }
225
226    public int getFirstPositionOffset() {
227        final View firstChild = getChildAt(0);
228        if (firstChild == null) {
229            return 0;
230        }
231        return firstChild.getTop();
232    }
233
234    /** @hide */
235    @Override
236    public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
237        super.onInitializeAccessibilityEventInternal(event);
238
239        // There are a bunch of years, so don't bother.
240        if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_SCROLLED) {
241            event.setFromIndex(0);
242            event.setToIndex(0);
243        }
244    }
245
246    /**
247     * The callback used to indicate the user changed the year.
248     */
249    public interface OnYearSelectedListener {
250        /**
251         * Called upon a year change.
252         *
253         * @param view The view associated with this listener.
254         * @param year The year that was set.
255         */
256        void onYearChanged(YearPickerView view, int year);
257    }
258}