Освой Android играючи
/* Моя кошка замечательно разбирается в программировании. Стоит мне объяснить проблему ей - и все становится ясно. */
John Robbins, Debugging Applications, Microsoft Press, 2000
Настраиваем текст
Настраиваем цвет
Вычисляем высоту заголовка и строки состояния
FEATURE_LEFT_ICON и FEATURE_RIGHT_ICON
FEATURE_PROGRESS - индикатор прогресса в заголовке
FEATURE_INDETERMINATE_PROGRESS - анимированный индикатор прогресса в заголовке
FEATURE_CUSTOM_TITLE - Индикатор прогресса и выравнивание текста по краям
Анимация в заголовке
FEATURE_NO_TITLE - Прячем заголовок
Начнём с простого. Текст заголовка при создании проекта устанавливается в манифесте у тега activity (свойство android:label="@string/app_name"). Таким образом, вам нужно зайти в строковые ресурсы и поменять на нужный текст в заголовке на этапе проектирования.
Иногда нужно поменять заголовок программно, например, вывести число сообщений или количество пойманных мышек. Вам придёт на помощь метод setTitle():
setTitle("Поймал мышек: " + mouseCounter.toString());
Соответственно, чтобы получить текст из заголовка, нужно применить обратный парный метод getTitle().
Если вам хочется программно поменять цвет текста в заголовке, то используйте метод setTitleColor() и парный ему метод getTitleColor() для считывания цвета.
setTitleColor(Color.GREEN);
В новых проектах код может не сработать, об этом ниже.
Если вы хотите поменять не только цвет текста, но и цвет заголовка, то вам вам ещё код:
// устанавливаем цвет полоски у заголовка
View title = getWindow().findViewById(android.R.id.title);
View titleBar = (View) title.getParent();
titleBar.setBackgroundColor(Color.CYAN);
В старые добрые времена, когда у всех были телефоны на Android 2.x, код исправно работал. Теперь же по умолчанию вместо заголовка выводится ActionBar. И приложения со старым кодом стали закрываться с ошибкой. Для совместимости нужно добавить проверку на null:
TextView title = (TextView) getWindow()
.findViewById(android.R.id.title);
if (title != null) {
// цвет в заголовке
title.setTextColor(Color.YELLOW);
// find parent view
ViewParent parent = title.getParent();
if (parent != null && (parent instanceof View)) {
View parentView = (View) parent;
parentView.setBackgroundColor(Color.CYAN);
}
}
Заодно вы видите, как ещё можно поменять цвет текста у заголовка. На самом деле код ещё может пригодиться. Ведь вы можете установить другую тему для активности. Откройте файл res/values-14/styles.xml и вместо существующей темы пропишите <style name="AppBaseTheme" parent="android:Theme">. Запустив пример, вы увидите изменения в заголовке.
О том, как поменять цвет у ActionBar, можно почитать в статье ActionBar: Drawable
Поэтому, если в этой статье ещё встретится неработающий код, то вы знаете, где собака порылась.
Также можно поменять цвета через ресурсы, используя стили и темы. Создайте файл custom_styles.xml в папке res/values:
<?xml version="1.0" encoding="utf-8"?>
<!-- Sets the text styles -->
<resources>
<style name="CustomWindowTitleText" parent="android:TextAppearance.WindowTitle">
<item name="android:textSize">20dip</item>
<item name="android:textColor">#5599FF</item>
<item name="android:textStyle">bold|italic</item>
</style>
<!-- Changes the background color of the title bar -->
<style name="CustomWindowTitleBackground">
<item name="android:background">#222222</item>
</style>
<!-- Set the theme for the window title -->
<!-- NOTE: setting android:textAppearence to style defined above -->
<style name="CustomWindowTitle" parent="android:WindowTitle">
<item name="android:textAppearance">@style/CustomWindowTitleText</item>
<item name="android:shadowDx">0</item>
<item name="android:shadowDy">0</item>
<item name="android:shadowRadius">5</item>
<item name="android:shadowColor">#1155CC</item>
</style>
<!-- Override properties in the default theme -->
<!-- NOTE: you must explicitly the windowTitleSize property, the title bar will not re-size automatically, text will be clipped -->
<style name="CustomTheme" parent="android:Theme">
<item name="android:windowTitleSize">40dip</item>
<item name="android:windowTitleStyle">@style/CustomWindowTitle</item>
<item name="android:windowTitleBackgroundStyle">@style/CustomWindowTitleBackground</item>
</style>
</resources>
Укажите в манифесте созданную тему:
<application
android:icon="@drawable/icon"
android:label="@string/app_name"
android:theme="@style/CustomTheme">
Если вам интересно узнать высоту заголовка, то придётся сначала вычислить высоту общую высоту, которая отводится под строку состояния и заголовок, затем нужно отдельно вычислить высоту строки состояния, а дальше, используя элементарную арифметику, можно получить высоту заголовка.
Сразу получить значения высоты не получится во время старта приложения, так как приложение в методе onCreate() отрисовывает все необходимые элементы и вычисления происходят до этой отрисовки. Поэтому можно применить следующий трюк - добавим обработчик к компоненту TextView и запустим вычисления через две секунды после загрузки этого элемента. Тогда вычисления будут корректными. Также вы можете повесить данные вычисления на щелчок кнопки.
final TextView tvContent = findViewById(R.id.textView);
tvContent.postDelayed(new Runnable() {
@Override
public void run() {
String display = String.format(
"Высота строки состояния = %d\\nВысота заголовка = %d",
getStatusBarHeight(), getTitleBarHeight());
tvContent.setText(display);
}
}, 2000);
// вычисляем высоту строки состояния
public int getStatusBarHeight() {
Rect r = new Rect();
Window w = getWindow();
w.getDecorView().getWindowVisibleDisplayFrame(r);
return r.top;
}
// вычисляем высоту заголовка
public int getTitleBarHeight() {
int viewTop = getWindow().findViewById(Window.ID_ANDROID_CONTENT)
.getTop();
return (viewTop - getStatusBarHeight());
}
Ещё лучше вычислять в методе onWindowFocusChanged().
У активности есть особый метод requestWindowFeature(), который следует вызывать до метода setContentView(). Если данный метод вызвать после, то он будет проигнорирован системой.
У метода используются следующие параметры:
Последние два варианта здесь не рассматриваются.
Выводим сразу два значка справа и слева в заголовке.
requestWindowFeature(Window.FEATURE_LEFT_ICON);
requestWindowFeature(Window.FEATURE_RIGHT_ICON);
setContentView(R.layout.activity_test);
setFeatureDrawableResource(Window.FEATURE_LEFT_ICON, R.drawable.ic_action_star_10);
setFeatureDrawableResource(Window.FEATURE_RIGHT_ICON, R.drawable.ic_cat);
Индикатор прогресса выводится в виде тонкой линии в верхней части заголовка. Можно устанавливать значения от 0 до 10000 (100%).
requestWindowFeature(Window.FEATURE_PROGRESS);
setContentView(R.layout.activity_main);
// Делаем индикатор видимым
setProgressBarVisibility(true);
setProgress(5);
// если значение станет 100%, то индикатор исчезнет
setProgress(10000);
Если значение индикатора станет равным 10000, то индикатор растворится (fade) и исчезнет.
Можно разместить индикатор прогресса в заголовке при выполнении длительных операций. В этом случае экономится место на экране. Добавить ProgressBar очень просто. Перед вызовом метода setContentView(R.layout.main) нужно добавить строчку кода:
requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
Далее остаётся только показать (или скрыть) ProgressBar с помощью метода setProgressBarIndeterminateVisibility():
public class TitleBarActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
setContentView(R.layout.main);
setProgressBarIndeterminateVisibility(true);
}
// Нажатие на кнопку
public void onClick(View v){
// Прячем ProgressBar в заголовке
setProgressBarIndeterminateVisibility(false);
}
}
Если вам надоел стандартный заголовок у активности и хочется чего-то новенького и необычного, то следующий пример для вас.
Создадим новый макет customtitlebar.xml, состоящий из пары элементов TextView и одного ProgressBar:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/RelativeLayout01"
android:layout_width="fill_parent"
android:layout_height="fill_parent" >
<TextView
android:id="@+id/titleTvLeft"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="left" >
</TextView>
<TextView
android:id="@+id/titleTvRight"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:text="right" >
</TextView>
<ProgressBar
android:id="@+id/leadProgressBar"
style="?android:attr/progressBarStyleSmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_toRightOf="@+id/titleTvLeft"
android:paddingLeft="3dip"
progressBarStyleSmall="" >
</ProgressBar>
</RelativeLayout>
Элементы TextView нужны для вывода текста с левой и правой стороны заголовка. Например, с левой части можно вывести название приложения, а справа какой-нибудь дополнительный текст. Постарайтесь избежать длинной строки справа (не более 20 символов), иначе он будет обрезаться.
Теперь переходим непосредственно к коду:
boolean customTitleSupported;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Проверяем, поддерживается свой заголовок перед выводом экрана!
customTitleSupported = requestWindowFeature(Window.FEATURE_CUSTOM_TITLE);
setContentView(R.layout.activity_main);
// Устанавливаем свой текст для левой и правой части заголовка
customTitleBar("Кот", "Кошка");
}
public void customTitleBar(String left, String right) {
if (right.length() > 20)
right = right.substring(0, 20);
// Устанавливаем свой заголовок
if (customTitleSupported) {
getWindow().setFeatureInt(Window.FEATURE_CUSTOM_TITLE,
R.layout.customtitlebar);
TextView leftTitleTextView = (TextView) findViewById(R.id.textViewLeft);
TextView rightTitleTextView = (TextView) findViewById(R.id.textViewRight);
leftTitleTextView.setText(left);
rightTitleTextView.setText(right);
ProgressBar titleProgressBar;
titleProgressBar = (ProgressBar) findViewById(R.id.progressBar);
// Если нужно скрыть индикатор прогресса, расскоментируйте эту строчку
// titleProgressBar.setVisibility(ProgressBar.GONE);
}
}
Запускаем проект и видим интересный заголовок, у которого текст расположен с двух сторон, а между ними крутится индикатор прогресса.
Натолкнувшись на статью на Хабре, я решил переделать предыдущий пример и использовать вместо индикатора прогресса анимацию из серии картинок.
Переделаем разметку customtitlebar.xml:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/RelativeLayout01"
android:layout_width="fill_parent"
android:layout_height="fill_parent" >
<TextView
android:id="@+id/titleTvLeft"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="left" >
</TextView>
<ImageView
android:id="@+id/progressBar"
android:layout_width="36dp"
android:layout_height="36dp"
android:layout_alignParentRight="true"
android:background="@drawable/loader"
android:maxHeight="36dp"
android:maxWidth="36dp"
android:scaleType="fitCenter" />
</RelativeLayout>
Скопируем в папку res/drawable серию картинок, которые создадут эффект анимации: snowman_01.png, snowman_02.png и т.д. (картинки можно взять из исходников к статье). В той же папке drawable создаём новый файл loader.xml:
<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
android:oneshot="false">
<item android:drawable="@drawable/snowman_01" android:duration="60"/>
<item android:drawable="@drawable/snowman_02" android:duration="60"/>
<item android:drawable="@drawable/snowman_03" android:duration="60"/>
<item android:drawable="@drawable/snowman_04" android:duration="60"/>
<item android:drawable="@drawable/snowman_05" android:duration="60"/>
<item android:drawable="@drawable/snowman_06" android:duration="60"/>
<item android:drawable="@drawable/snowman_07" android:duration="60"/>
<item android:drawable="@drawable/snowman_08" android:duration="60"/>
<item android:drawable="@drawable/snowman_09" android:duration="60"/>
<item android:drawable="@drawable/snowman_10" android:duration="60"/>
<item android:drawable="@drawable/snowman_11" android:duration="60"/>
<item android:drawable="@drawable/snowman_12" android:duration="60"/>
</animation-list>
Разместим на экране активности две кнопки для запуска и остановки анимации и переделаем метод customTitleBar() из старого примера под наши нужды:
public class AnimTitleActivity extends Activity {
private ImageView progressBar;
boolean customTitleSupported;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Проверяем, поддерживается свой заголовок перед выводом экрана!
customTitleSupported = requestWindowFeature(Window.FEATURE_CUSTOM_TITLE);
setContentView(R.layout.activity_test);
customTitleBar("Снеговик");
progressBar = (ImageView) findViewById(R.id.progressBar);
progressBar.setVisibility(View.GONE);
}
public void onClick(View view) {
switch (view.getId()) {
case R.id.button1:
progressBar.setVisibility(View.VISIBLE);
AnimationDrawable frameAnimation = (AnimationDrawable) progressBar
.getBackground();
frameAnimation.start();
break;
case R.id.button2:
progressBar.setVisibility(View.GONE);
progressBar.clearAnimation();
break;
default:
break;
}
}
public void customTitleBar(String left) {
// Устанавливаем свой заголовок
if (customTitleSupported) {
getWindow().setFeatureInt(Window.FEATURE_CUSTOM_TITLE,
R.layout.customtitlebar);
TextView titleTvLeft = (TextView) findViewById(R.id.titleTvLeft);
titleTvLeft.setText(left);
ImageView titleProgressBar;
titleProgressBar = (ImageView) findViewById(R.id.progressBar);
// Если нужно скрыть индикатор прогресса, расскоментируйте эту
// строчку
// titleProgressBar.setVisibility(ProgressBar.GONE);
}
}
}
Получим интересное дополнение к приложению, например, новогоднюю версию, когда в углу будет крутиться снеговик (или шарик, ёлочка и т.п.):
Напоминаю, что можно также скрыть заголовок, если использовать одну из системных тем (здесь только часть), которую можно прописать в манифесте:
Также можно скрыть заголовок и строку состояния программно. Данный код нужно разместить перед вызовом метода setContentView:
// Прячем заголовок
requestWindowFeature(Window.FEATURE_NO_TITLE);
// Прячем строку состояния и заголовок
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
Если вы используете старую тему, в которой нет ActionBar, то можете её включить программно (доступно с версии API 11):
requestWindowFeature(Window.FEATURE_ACTION_BAR);
setContentView(R.layout.main);
// Получаем доступ к ActionBar для модификаций
ActionBar actionBar = getActionBar();
В Android 5.0 (API 21) появился новый компонент Toolbar, в котором можно реализовать все описанные вещи гораздо проще без излишнего кода.