воскресенье, февраля 17, 2013

Определить размер элементов в runtime

Иногда необходимо узнать размеры элементов - ширину, высоту - во время выполнения. Понадобиться это может чтобы изменить их взаимное расположение - например, чтобы отцентровать по высоте.

Проблема заключается в том, что во время выполнения onResume, а тем более - onCreate, размеры элементов ещё не известны. При этом какого-либо специального метода жизненного цикла Activity, вызываемого после окончания построения layout, нет.

Есть несколько способов решить такую проблему:
1. упаковать нужный набор компонентов в какой-нибудь потомок View, переопределить для полученного потомка метод onLayout, в котором вначале вызвать super-метод, а потом уже взять готовые размеры элементов и сделать то, что требуется.
Проблема - ну не хочется на каждый набор элементов создавать отдельный класс, так и утонуть в них недолго.

2. есть решение, использующее ViewTreeObserver - создать слушатель на OnGlobalLayoutListener(), в котором реализовать метод onGlobalLayout. Такое решение можно посмотреть в этом топике. Это уже лучше, но работать с этим не очень удобно, так как нужно поразбираться в isAlive(), не забыть вызвать removeGlobalOnLayoutListener, то есть работать нужно аккуратно.

Мне больше понравилось следующее решение, которое сейчас представляется наиболее и универсальным, и наиболее простым:

1. Создаём интерфейс

public interface OnLayoutCompletedListener {
void layoutCompleted(ViewGroup layout);
}


2. Делаем производный класс от одного из "базовых" layout - например, MyLinearLayout, в котором переопределяем метод onLayout - и в переопределённом методе вызываем layoutCompleted.


public class MyLinearLayout extends LinearLayout {
private OnLayoutCompletedListener layoutCompletedListener;

@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
if (layoutCompletedListener != null) {
layoutCompletedListener.layoutCompleted(this);
}
}

public void setOnLayoutCompletedListener(OnLayoutCompletedListener layoutCompletedListener) {
this.layoutCompletedListener = layoutCompletedListener;
}
}

3. В раскладке Activity (пусть activity_main.xml) заменяем "корневой" LineаrLayout на свежесозданный MyLinearLayout, при этом не забыть установить для него идентификатор:


<my.components.MyLinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    ... тут обычный набор ...

</my.components.MyLinearLayout>


4. Допиливаем Activity (у меня используются AndroidAnnotations):

@EActivity(R.layout.activity_main)
public class MyActivity extends Activity implements OnLayoutCompletedListener {

@ViewById
MyLinearLayout layout;

@AfterViews
protected void initViews() {
layout.setOnLayoutCompletedListener(this);
...
}

@Override
public void layoutCompleted(ViewGroup layout) {
updateArrowsPosition();
}
}

Вот и всё. Мне нравится то, что получаемое решение интуитивно понятно и набор дополнительных действий - минимален. Всё что нужно сделать (после того, как уже созданы нужные потомки "базовый" раскладок):


1. изменить раскладку для этой активити:
1.1. изменить класс для root-layout
1.2. добавить идентификатор
2. реализовать в активити интерфейс OnLayoutCompletedListener

2.1. добавить интерфейс в implements
2.2. получить экземпляр раскладки - в общем случае через findViewById
2.3. установить для экземпляра раскладки слушатель события
2.4. реализовать обработку события, генерируемого после завершения раскладки.

Вроде вполне несложно.






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