Освой 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()

У активности есть особый метод requestWindowFeature(), который следует вызывать до метода setContentView(). Если данный метод вызвать после, то он будет проигнорирован системой.

У метода используются следующие параметры:

  • FEATURE_LEFT_ICON: значок выводится слева
  • FEATURE_RIGHT_ICON: значок выводится справа
  • FEATURE_PROGRESS: выводится индикатор прогресса (0—100%)
  • FEATURE_INDETERMINATE_PROGRESS: выводится анимированный круговой индикатор прогресса
  • FEATURE_CUSTOM_TITLE: используется собственная разметка для заголовка. При использовании данного параметра в методе setFeatureInt() нельзя комбинировать Window.FEATURE_CUSTOM_TITLE с другими флагами, так как он полностью заменяет заголовок на ваш
  • FEATURE_NO_TITLE: заголовок убирается
  • FEATURE_ACTION_BAR: доступно с API 11. Включает панель действий Action Bar. Можно использовать в старых темах, где панель действий не предусмотрена
  • FEATURE_ACTION_BAR_OVERLAY: доступно с API 11. Накладывается на экран, а не на заголовок

Последние два варианта здесь не рассматриваются.

FEATURE_LEFT_ICON и FEATURE_RIGHT_ICON

Выводим сразу два значка справа и слева в заголовке.


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); 

Значки в заголовке

FEATURE_PROGRESS - индикатор прогресса в заголовке

Индикатор прогресса выводится в виде тонкой линии в верхней части заголовка. Можно устанавливать значения от 0 до 10000 (100%).


requestWindowFeature(Window.FEATURE_PROGRESS);

setContentView(R.layout.activity_main);

// Делаем индикатор видимым
setProgressBarVisibility(true); 
setProgress(5); 
// если значение станет 100%, то индикатор исчезнет
setProgress(10000); 

Если значение индикатора станет равным 10000, то индикатор растворится (fade) и исчезнет.

FEATURE_INDETERMINATE_PROGRESS - анимированный индикатор прогресса в заголовке

Можно разместить индикатор прогресса в заголовке при выполнении длительных операций. В этом случае экономится место на экране. Добавить 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);
	}
}

ProgressBar в заголовке

FEATURE_CUSTOM_TITLE - Индикатор прогресса и выравнивание текста по краям

Если вам надоел стандартный заголовок у активности и хочется чего-то новенького и необычного, то следующий пример для вас.

Создадим новый макет 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);
		}
	}
}	

Получим интересное дополнение к приложению, например, новогоднюю версию, когда в углу будет крутиться снеговик (или шарик, ёлочка и т.п.):

Снеговик

FEATURE_NO_TITLE - Прячем заголовок

Напоминаю, что можно также скрыть заголовок, если использовать одну из системных тем (здесь только часть), которую можно прописать в манифесте:

  • Theme.NoTitleBar
  • Theme.Black.NoTitleBar
  • Theme.Light.NoTitleBar
  • Theme.Translucent.NoTitleBar
  • Theme.NoTitleBar.FullScreen
  • Theme.Black.NoTitleBar.FullScreen
  • Theme.Light.NoTitleBar.FullScreen
  • Theme.Translucent.NoTitleBar.FullScreen

Также можно скрыть заголовок и строку состояния программно. Данный код нужно разместить перед вызовом метода setContentView:


// Прячем заголовок
requestWindowFeature(Window.FEATURE_NO_TITLE);

// Прячем строку состояния и заголовок
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
        WindowManager.LayoutParams.FLAG_FULLSCREEN);

ActionBar

Если вы используете старую тему, в которой нет ActionBar, то можете её включить программно (доступно с версии API 11):


requestWindowFeature(Window.FEATURE_ACTION_BAR);
setContentView(R.layout.main);

// Получаем доступ к ActionBar для модификаций
ActionBar actionBar = getActionBar();

Toolbar

В Android 5.0 (API 21) появился новый компонент Toolbar, в котором можно реализовать все описанные вещи гораздо проще без излишнего кода.

Реклама