В этом уроке делается простое приложение:
(картинку я взял из этого урока, описание как на своем смартфоне сделать такую же лежит в нем же).
Двигаем ползунок - ширина кнопок меняется. Очень просто.
Вот такой код предлагает автор:
public class MainActivity extends Activity implements OnSeekBarChangeListener {
SeekBar sbWeight;
Button btn1;
Button btn2;
LinearLayout.LayoutParams lParams1;
LinearLayout.LayoutParams lParams2;
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
sbWeight = (SeekBar) findViewById(R.id.sbWeight);
sbWeight.setOnSeekBarChangeListener(this);
btn1 = (Button) findViewById(R.id.btn1);
btn2 = (Button) findViewById(R.id.btn2);
lParams1 = (LinearLayout.LayoutParams) btn1.getLayoutParams();
lParams2 = (LinearLayout.LayoutParams) btn2.getLayoutParams();
}
@Override
public void onProgressChanged(SeekBar seekBar, int progress,
boolean fromUser) {
int leftValue = progress;
int rightValue = seekBar.getMax() - progress;
// настраиваем вес
lParams1.weight = leftValue;
lParams2.weight = rightValue;
// в текст кнопок пишем значения переменных
btn1.setText(String.valueOf(leftValue));
btn2.setText(String.valueOf(rightValue));
}
Это фрагмент, с полным кодом можно знакомиться тут: Урок 18. Меняем layoutParams в рабочем приложении
<dis>Тут мне следует сделать небольшое отступление от темы и сразу договориться с читателем: программировать под Android учит Дмитрий. У него отличная серия уроков. В этом посте вы не научитесь тому чему учит Дмитрий. Мое изучение урока 18 мне же самому напомнило анекдот Эволюция пpогpаммиста и мне захотелось об этом написать.
</dis>
В своей работе я не допускаю появление такого кода. Здесь явно смешаны объекты представляющие контролы, дополнительный код который управляет этими контролами, и данные которые показывают эти контролы. В примере заложена такая архитектура, которая ведет к проблемам в перспективе примерно больше чем полгода, если вероятны изменения в этом коде.
Вот что меня в нем не устроило:
1. sbWeight.setOnSeekBarChangeListener(this);
Здесь происходит смешивание функционала на уровне метода. Очень вероятное расширение поведения - это добавление второго SeekBar. Понятно что для него должна быть другая обработка. При использовании одного метода нужно делать дополнительное перенаправление логики в методе onProgressChanged (который один на всех) на специфичную для контрола логику (которая у каждого своя). Метод распухнет, появится неинтересный для чтения код. Поэтому мне сразу хочется убрать this и декларировать отдельный самостоятельный объект.
2. lParams1.weight = leftValue;
Здесь тоже происходит смешивание функционала. тут вероятны два развития приложения: новый контрол который тоже регулирует баланс веса (например Stars) и новый контрол который тоже отображает веса. В обоих случаях будет происходить добавление кода в этот метод и он станет решать сразу несколько задач. Метод тоже распухнет с неинтересным кодом.
3. public void onProgressChanged(
Здесь написана вся логика по реагированию на действия пользователя над контролами (действия когда от контрола приходит нотификация) и по управлению весами кнопок. Очень вероятное изменение: реализация этого или очень похожего функционала в новой форме (Copy&Paste?).
3. (да-да, самому смешно, но это именно "дырка", или "отсутствие кода")
Ненадежное хранение значения: в приведенной архитектуре значение веса хранится в контроле SeekBar и копируется кодом в два других контрола (кнопки). Если добавится новый контрол для управления весом, то возникнет неоднозначность: у которого из них брать значение? Ведь понятно что логически значение единственно и даже вопроса такого возникать не должно. Здесь не хватает отдельного (единственного) свойства для хранения этого значения (а контролы могут к нему обращаться что бы скопировать из него значение для собственного использования и логически это будет копия).
Я попробовал сделать аналогичное приложение и добавил новые контролы: кнопки и progress динамически показывают значение весов, Seek и Rating позволяют изменить вес и оба этих контрола тоже динамически показывают изменения (поменял вес в одном - другой отреагировал). Вот так это выглядит:
Вот такой получился код инициализации у формы:
WeightBalance
weightBalance;
@Override
public
void
onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
weightBalance
= new
WeightBalance();
weightBalance.setWeightBalance(50);
weightBalance.setWeightBalanceMax(100);
new
Buttons_WieghtVisualizer(weightBalance,
(Button)this.findViewById(R.id.btnLeft),
(Button)this.findViewById(R.id.btnRight),
this.findViewById(R.id.buttonsParent));
new
ProgressBar_WeightBalanceVisualizer((IWeightBalance)weightBalance,
(ProgressBar)this.findViewById(R.id.progressBar1));
new
SeekBar_WeightBalanceController(weightBalance,
(SeekBar)this.findViewById(R.id.seekBar1));
new
RatingBar_WeightBalanceController(weightBalance,
(RatingBar)this.findViewById(R.id.ratingBar1));
}
Источник данных (а это два значения: WeightBalance и WeightBalanceMax) реализован как отдельный объект (класс WeightBalance) с двумя свойствами . В эти свойства можно записать значение, а с помощью эвентов можно динамично реагировать на изменения значений в этих свойствах.
Вот такая реализация источника данных:
public
class
WeightBalance implements
IWeightBalance, IEditableWeightBalance {
private
int
weightBalance;
private
int
weightBalanceMax;
private
List<IOnWeightBalanceChangeListener>
onWeightBalanceChangeListeners;
public
WeightBalance() {
onWeightBalanceChangeListeners
= new
ArrayList<IOnWeightBalanceChangeListener>();
}
@Override
public
int
getWeightBalance() {
return
weightBalance;
}
@Override
public
int
getWeightBalanceMax() {
return
weightBalanceMax;
}
public
void
setWeightBalanceMax(int
max) {
this.weightBalanceMax
= max;
}
@Override
public
void
setWeightBalance(int
newValue) {
if(newValue
> weightBalanceMax)
{
//TODO: throw
new Exception("WeightBalance cannot be more than
WeightBalanceMax");
}
this.weightBalance
= newValue;
for(IOnWeightBalanceChangeListener
listener : onWeightBalanceChangeListeners)
{
listener.OnWeightBalanceChanged(weightBalance);
}
}
public
void
addOnWeightBalanceChangeListener(IOnWeightBalanceChangeListener
listener) {
if(!onWeightBalanceChangeListeners.contains(listener))
{
onWeightBalanceChangeListeners.add(listener);
}
}
public
void
removeOnWeightBalanceChangeListener(IOnWeightBalanceChangeListener
listener) {
onWeightBalanceChangeListeners.remove(listener);
}
Вот класс привязки значения свойства WeightBalance к двум кнопкам:
public
class
Buttons_WieghtVisualizer implements
IOnWeightBalanceChangeListener {
Button
leftButton;
Button
rightButton;
View
refreshView;
IWeightBalance
weightBalance;
public
Buttons_WieghtVisualizer(IWeightBalance weightBalance, Button
leftButton, Button rightButton, View refreshView) {
this.weightBalance
= weightBalance;
this.weightBalance.addOnWeightBalanceChangeListener(this);
this.leftButton
= leftButton;
this.rightButton
= rightButton;
this.refreshView
= refreshView;
OnWeightBalanceChanged(this.weightBalance.getWeightBalance());
}
@Override
public
void
OnWeightBalanceChanged(int
newValue) {
LinearLayout.LayoutParams
leftLP =
(LinearLayout.LayoutParams)this.leftButton.getLayoutParams();
leftLP.weight
= newValue;
LinearLayout.LayoutParams
rightLP =
(LinearLayout.LayoutParams)this.rightButton.getLayoutParams();
rightLP.weight
= weightBalance.getWeightBalanceMax()
- newValue;
this.refreshView.requestLayout();
}
}
Этому классу требуется знать значение и получать нотификации об изменении этого значения что бы динамически применять его на свойства кнопок. Этот класс реализует передачу WeightBalance в одном направлении: из источника данных в контрол.
Вот такой один класс привязки значения свойства WeightBalance к двум кнопкам:
public
class
SeekBar_WeightBalanceController implements
OnSeekBarChangeListener, IOnWeightBalanceChangeListener {
private
IEditableWeightBalance weightBalance;
private
SeekBar seekBar;
public
SeekBar_WeightBalanceController(IEditableWeightBalance weightBalance,
SeekBar seekBar) {
this.weightBalance
= weightBalance;
this.weightBalance.addOnWeightBalanceChangeListener(this);
this.seekBar
= seekBar;
this.seekBar.setMax(weightBalance.getWeightBalanceMax());
this.seekBar.setProgress(weightBalance.getWeightBalance());
this.seekBar.setOnSeekBarChangeListener(this);
}
@Override
public
void
onProgressChanged(SeekBar seekBar, int
progress, boolean
fromUser) {
this.weightBalance.removeOnWeightBalanceChangeListener(this);
weightBalance.setWeightBalance(progress);
this.weightBalance.addOnWeightBalanceChangeListener(this);
}
@Override
public
void
OnWeightBalanceChanged(int
newValue) {
this.seekBar.setOnSeekBarChangeListener(null);
this.seekBar.setProgress(newValue);
this.seekBar.setOnSeekBarChangeListener(this);
}
}
В этом классе реализована уже двухсторонняя передача значения: из свойств контрола в свойство WeightBalance и из свойства WeightBalance в свойства контрола.
Остальные классы и интерфейсы можно посмотреть в исходниках: Example_SeekBar.Sources
Вот собственно и все. Получился банальный двунаправленный (а местами и вовсе в одну сторону) биндинг, уже давно реализованный в WPF. Вот уж где мне надо было бы просто указать имена свойств что бы стандартный класс биндинга понял из какого свойства в объекте брать значение и в какое свойство в контроле его присваивать (+еще эвент от меня нужен в моем источнике данных что бы динамически сонхронизировать их).
Еще раз окинув взглядом мою реализацию, которая конечно же очень правильная (кто бы сомневался :-D ) я считаю материал самого урока вполне подходящим: "просто и доступно про Android".
И все эти мои выкрутасы с перестановкой кода ориентированы на другую цель ("продолжает работать, работать и работать") и основаны на других задачах (ресурсов на поддержку нет, а отвечать на вопросы и исправлять ошибки надо). Мой код меняется, поэтому он должен быть очень устойчив к вносимым изменениям. Мой код читается гораздо больше раз чем пишется, поэтому он должен быть понятен в крупных и мелких деталях. Архитектура моего приложения известна только автору (которого обычно уже нет под рукой), поэтому код должен четко и кратко заявлять как он устроен, что делает и где в нем место новому коду даже человеку который в первый раз его видит (обычное дело в нашей работе).
Исходники тут: Example_SeekBar.Sources
Готовый apk тут: Example_SeekBar.apk
Комментариев нет:
Отправить комментарий