...

понедельник, 9 июня 2014 г.

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

Для приложения под 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().

Наглядная демонстрация работы:



Ссылка на полный проект:

http://ift.tt/1oBJCSO

This entry passed through the Full-Text RSS service — if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.


Комментариев нет:

Отправить комментарий