Как стать автором
Поиск
Написать публикацию
Обновить

Создание нестандартного компонента на основе ListView

Время на прочтение4 мин
Количество просмотров8.6K
Для приложения под Android мне понадобился элемент интерфейса, отдаленно напоминающий DatePicker. Он должен уметь:
  • прокручивать список от начала и до конца (но не по кругу), так чтобы выделять центральный элемент.
  • по мере удаления элемента от центра компонента изменять шрифт и прозрачность цифр
  • “доводить“ список до нужного элемента
  • отображать заданное количество элементов на экране
  • определять направление скроллинга (вверх или вниз)
  • рисовать тень для содержимого текстовых окон

Должен получиться компонент подобного вида:


Унаследуем наш компонент RollView от LinearLayout с дочерним элементом ListView. Внутри компонента реализуем интерфейс OnScrollListener для определения поведения ListView при скроллинге.
public class RollView extends LinearLayout implements OnScrollListener{
private final ListView innerListView;
}


В конструкторе инициализируем ListView через xml файл и присваиваем слушателя.
Для представления данных создадим внутренний адаптер с переопределенным методом getView():
private class RollAdapter extends ArrayAdapter<String> {

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().
Наглядная демонстрация работы:

Ссылка на полный проект:
https://bitbucket.org/msinchevskaya/rollview
Теги:
Хабы:
Всего голосов 10: ↑5 и ↓50
Комментарии8

Публикации

Ближайшие события