[Из песочницы] Создание нестандартного компонента на основе ListView

Для приложения под Android мне понадобился элемент интерфейса, отдаленно напоминающий DatePicker. Он должен уметь: прокручивать список от начала и до конца (но не по кругу), так чтобы выделять центральный элемент. по мере удаления элемента от центра компонента изменять шрифт и прозрачность цифр «доводить» список до нужного элемента отображать заданное количество элементов на экране определять направление скроллинга (вверх или вниз) рисовать тень для содержимого текстовых окон Должен получиться компонент подобного вида: 05ce586721dafadb41a1106c5eb04333.pngУнаследуем наш компонент RollView от LinearLayout с дочерним элементом ListView. Внутри компонента реализуем интерфейс OnScrollListener для определения поведения ListView при скроллинге.

public class RollView extends LinearLayout implements OnScrollListener{ private final ListView innerListView; } В конструкторе инициализируем ListView через xml файл и присваиваем слушателя.Для представления данных создадим внутренний адаптер с переопределенным методом getView ():

private class RollAdapter extends ArrayAdapter {

private final LayoutInflater mInflater; @Override public View getView (int position, View convertView, ViewGroup parent) { if (convertView == null){ convertView = mInflater.inflate (R.layout.roll_view_adapter, null); convertView.setLayoutParams (mParams); } TextView tv = (TextView) convertView.findViewById (R.id.text); tv.setTag (position); // записываем позицию элемента tv.setText (getItem (position)); convertView.setTag (tv); //записываем ссылку на TextView в тег if (! listViews.contains (convertView)) listViews.add (convertView); // в список для последующего обновления размера текста return convertView; } } Все View из метода getView будем записывать в ArrayList, чтобы изменять их параметры. Метод refreshLayoutParams () задает размеры для элементов списка в зависимости от количества видимых элементов. Больше в классе адаптера ничего делать не будем.

Для того, чтобы можно было сдвинуть первый элемент списка в середину добавим в начало и конец массива пустые строки.Теперь нужно обработать скроллинг в методах onScroll и onScrollStateChanged:

private int lastFirstVisibleElement; // индекс предыдущего «первого видимого элемента» для определения направления скроллинга private int centralIndex; //индекс элемента находящегося в центре @Override public void onScroll (AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { refreshTextViews (); //обновление размера текста и прозрачности //Для определения направления скроллинга if (lastFirstVisibleElement > firstVisibleItem){ Log.i («RollView», «Scroll up»); } else if (lastFirstVisibleElement < firstVisibleItem){ Log.i("RollView", "Scroll down"); } lastFirstVisibleElement = firstVisibleItem; }

@Override public void onScrollStateChanged (AbsListView view, int scrollState) { //После отпускания пальца if (scrollState == SCROLL_STATE_IDLE){ //Плавная доводка smoothScrollToPositionFromTop (centralIndex — totalElementVisible / 2, 0, 1); } Метод refreshTextViews () отвечает за изменение размера текста и прозрачности в зависимости от положения элемента:

public void refreshTextViews (){ float maxTextSize = 0; for (View v: listViews){ int centerOfViewY = v.getBottom () — (mAdapter.mParams.height / 2); ShadowTextView tv = (ShadowTextView) v.getTag (); float coefficient = (Math.abs (centerOfViewY — mAdapter.centerLineY)) / (float)mAdapter.centerLineY; float scale = 0; //Если коэффициент больше 1 — значит элемент за пределами видимости if (coefficient < 1) scale = Math.abs(coefficient - 1); tv.setAlpha(scale); //Определяем элемент с наибольшим размером текста для доводки к нему float textSize = CENTRAL_TEXT_SIZE * scale; if (textSize > maxTextSize){ maxTextSize = textSize; centralIndex = (Integer) tv.getTag (); } tv.setTextSize (textSize); } }

Осталось добавить тени для текста. Для этого создадим унаследованный от TextView компонент ShadowTextView. Для рисования текста с тенями нужно создать кисть (Paint) и задать ей параметры:

Параметры кисти private final Paint mPaint = new Paint (); // Параметры кисти для рисования теней private void initPaint (){ mPaint.setAntiAlias (true); mPaint.setTextSize (getTextSize ()); mPaint.setColor (Color.WHITE); mPaint.setStrokeWidth (2.0f); mPaint.setStyle (Paint.Style.FILL); mPaint.setTextAlign (Paint.Align.CENTER); mPaint.setShadowLayer (10.0f, 0.0f, 0.0f, Color.BLACK); } и в методе onDraw () перерисовать компонент: private final Rect mBounds = new Rect (); // границы текста @Override protected void onDraw (Canvas canvas){ canvas.drawColor (Color.TRANSPARENT); int x = getWidth () / 2; int y = (getHeight () + mBounds.height ()) / 2; canvas.drawText (getText ().toString (), x, y, mPaint); } } Для перерисовки теней из RollView добавим метод redraw (): public void redraw (){ text = getText ().toString (); mPaint.setTextSize (getTextSize ()); mPaint.getTextBounds (text, 0, getText ().toString ().length () , mBounds); invalidate (); } Осталось только заменить TextView в на ShadowTextView и вызвать в методе refreshTextViews метод tv.redraw (); Теперь для получения выбранного пользователем значения осталось только добавить методы getCurrentItemValue () и getCurrentItemIndex ().Наглядная демонстрация работы:[embedded content]Ссылка на полный проект: https://bitbucket.org/msinchevskaya/rollview

© Habrahabr.ru