Pull to refresh

Соединение SlidingMenu с Support Library и обход возможных проблем

Development for Android *
Sandbox

Предисловие


Здравствуй, Хабр!

Сразу хочу сделать небольшое отступление от темы: я не профессиональный android-разработчик и только учусь. В этом посте, рассчитанном более на новичков, мне бы хотелось объединить решения тех проблем, с которыми столкнулся, при написании задуманного приложения, а в частности использовании ActionBar при помощи Support Library и присоединении к ним SlidingMenu.

Примерами качества для меня являются приложения VK, Forsquare и Instagram. Собственно из них сразу были взяты идеи использования ActionBar и SlidingMenu. Как и при верстке/разработке web-сайтов (моим основным делом) и их приличной работе в Internet Explorer, так и здесь, я первым делом задумался над совместимостью с устаревающими версиями Android, поскольку ActionBar поддерживается только с 3.0. Благодаря поиску быстро нашел решение — ActionBar для версий 2.1+.


Начнем


1. Создаем новый проект в Eclipse. Как это сделать, можно найти при помощи поиска хабрахабра. Минимальная поддерживаемая версия API 7. Желательно, без темы (Theme: None).
2. Подключаем Support Library, как это сделать, написано по ссылке, указанной выше. Вот еще раз, на всякий случай. Прописываем тему.
3. Вот и первая проблема, с которой мне пришлось столкнуться: все прекрасно работает на Android ниже 3.0. Оказывается в статье выше не уточнен один момент: тему предлагается наследовать в файле res/values/styles.xml, однако же это оказался не самый верный вариант. При создании проекта, Eclipse сразу создает несколько стилевых файлов, для разных версий API:

  • res/values/styles.xml — стандартный файл стилей;
  • res/values-v11/styles.xml — файл стилей для API 11+;
  • и res/values-v14/styles.xml — файл стилей для API 14+;

Мы же наследуем тему только в первом из приведенных файлов, поэтому работает только до версии Android 3.0 (API11).
Решается наследованием темы во всех файлах или непосредственно в манифесте проекта. Для этого переходим в AndroidManifest.xml и находим строки:
<application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >

где заменяем:
android:theme="@style/AppTheme"

на:
android:theme="@style/Theme.AppCompat.Light"


При отсутствии строки android:theme — добавляем ее.
В данном случае стоит помнить, что при наследовании темы непосредственно в манифесте, свою цветовую схему использовать не получится, поэтому рекомендую использовать первый вариант, с наследованием в каждом файле отдельно.

На эту проблему ушло 2 дня, решение которой обнаружилось при поиске информации, на совершенно иную тему.

Запускаем, проверяем. Все работает.


Подключаем SlidingMenu

В процессе поиска такого меню, сразу наткнулся на Drawer. Однако, мне нужно было не это, по некоторым причинам, которые здесь описывать не стану.
Выбор пал на SlidingMenu. Библиотека полностью бесплатна и доступна на GitHub.

1. Скачиваем все файлы и приступаем к подключению в Eclipse: для этого, идем File → New → Other → Android Project from Existing Code, в открывшемся окне указываем путь до папки library ранее скачанного SlidingMenu. После того, как файлы скопируются, нажимаем ОК. Библиотека подключена.
2. Подключаем ее к нашему проекту, созданному ранее. Повторяем процедуру подключения Support Lirary, но в этот раз выбираем «library».
3. А вот и вторая проблема — консоль сообщает о конфликте двух файлов android-support-v4.jar. Оказалось, что это файл мало, что содержится в самом проекте, так еще и подключается Support Library и SlidingMenu. Решение оказалось простым, нашлось оно на StackOverflow.com: удаляем данный файл из SlidingMenu library и из нашего проекта (у обоих он находится в папке «lib»).
Теперь возникла новая проблема — SlidingMenu library сообщает о множестве ошибок. Это связано с отсутствием удаленного нами файла. Обход этого также был найден на StackOverflow: отключаем библиотеку Support Lib от нашего проекта и подключаем ее-же, но уже к SlidingMenu library. В таком случае, нужный для всех троих файл будет подключен сначала к библиотеке SlidingMenu, а SlidingMenu, уже вместе со своим функционалом, подключит и Support Lib к нашему проекту.

В комментариях проблему описали более техническим языком и предложили простое решение: «У вас проблемы были в том, что в SlidingMenu лежала более старая версия support библиотеки и в консоль вам падало сообщение о разных хеш-суммах, достаточно было из основного проекта скопировать либу и закинуть в SlidingMenu.»

На решение этой проблемы ушло 3 дня (да, я люблю портить себе жизнь), просто потому, что не обращал внимания на ошибки в консоли.
Сложно, но если разобраться в таких нюансах, все становится вполне понятно и логично.
Чтобы красные надписи не смущали, очищаем консоль.

После всего вышеописанного, переходим в MainActivity.java и перед методом onCreate() объявляем переменную:
private SlidingMenu menu;

Далее, непосредственно в onCreate добавляем инициализатор меню:
  
menu = new SlidingMenu(this);
menu.setMode(SlidingMenu.LEFT);
menu.setTouchModeBehind(SlidingMenu.TOUCHMODE_FULLSCREEN);
menu.setShadowDrawable(R.drawable.slidemenu_shadowgradient);
menu.setShadowWidth(15);
menu.setFadeDegree(0.0f);
menu.attachToActivity(this, SlidingMenu.SLIDING_WINDOW);
menu.setBehindWidth(200);
menu.setMenu(R.layout.menu_frame);

Как видно в коде, указаны пути до файла с тенью (menu.setShadowDrawable(R.drawable.slidemenu_shadowgradient)) и, собственно, само меню (menu.setMenu(R.layout.menu_frame)). Эти файлы необходимо создать. Примеры всех исходников под спойлером ниже.

Исходники
MainActivity.java

import com.jeremyfeinstein.slidingmenu.lib.SlidingMenu;
import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
import android.view.Menu;
import android.view.MenuItem;

public class MainActivity extends ActionBarActivity {
	
	private SlidingMenu menu;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		
		menu = new SlidingMenu(this);
		menu.setMode(SlidingMenu.LEFT);
		menu.setTouchModeBehind(SlidingMenu.TOUCHMODE_FULLSCREEN);
		menu.setShadowDrawable(R.drawable.aslidingmenu_shadowgradient);
		menu.setShadowWidth(15);
		menu.setFadeDegree(0.0f);
		menu.attachToActivity(this, SlidingMenu.SLIDING_WINDOW);
		menu.setBehindWidth(200);
		menu.setMenu(R.layout.menu_frame);
	}
}



res/drawable/slidingmenu_shadowgradient.xml

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item>
    <shape>
	    <gradient
	        android:endColor="@color/purple_dark"
	       android:startColor="@color/back" />  
    </shape>
</item>
</selector>



res/layout/menu_frame.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" 
    android:background="@color/back">
 
    <TextView
        android:id="@+id/textView1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/menu_1"
        android:textAppearance="?android:attr/textAppearanceLarge" />
 
    <TextView
        android:id="@+id/textView2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/menu_2"
        android:textAppearance="?android:attr/textAppearanceLarge" />
 
    <TextView
        android:id="@+id/textView3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/menu_3"
        android:textAppearance="?android:attr/textAppearanceLarge" />
 
    <TextView
        android:id="@+id/textView4"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/menu_4"
        android:textAppearance="?android:attr/textAppearanceLarge" />
 
</LinearLayout>



res/values/strings.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <string name="app_name">SlideMenu Demo</string>
    <string name="action_settings">Settings</string>
    <string name="hello_world">Hello world!</string>

    <color name="back">#3d4140</color>
    <color name="purple_light">#ffffff</color> 
    <color name="purple_dark">#353838</color> 
    <string name="menu_1">Menu 1</string> 
    <string name="menu_2">Menu 2</string> 
    <string name="menu_3">Menu 3</string> 
    <string name="menu_4">Menu 4</string>
    
</resources>




Добавляем функционала

Все, что описано выше, конечно хорошо, но не хватает некоторых мелочей. Например, пользуясь приложением VK, часто приходится прибегать к боковому меню (SlidingMenu). Выдвигаю я его движением пальца по экрану, однако есть несколько разных способов его открытия.

Иконка в ActionBar

Для добавления функционала кнопке-иконке в экшенбаре, используем следующий код, который размещается в любое место в MainActivity.java:

@Override
	public boolean onOptionsItemSelected(MenuItem item) {
	    switch (item.getItemId()) {  // узнаем ID нажатой кнопки
	    case android.R.id.home: // если это кнопка-иконка ActionBar,
	    	menu.toggle(true);        // открываем меню (или закрываем)
	        return true;
	    }
	    return super.onOptionsItemSelected(item);
	}

В конец onCreate() добавляем:
getSupportActionBar().setDisplayShowCustomEnabled(true);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);

Помимо прочего, последний код добавляет красивую стрелочку к иконке.

Закрытие меню, по нажатию на кнопку «Назад»

Тут все просто, меню открыто, но при нажатии «Назад» закрывается приложение, а не меню. Исправляем следующим кодом:

public boolean onKeyDown(int keyCode, KeyEvent event) {
		if (keyCode == KeyEvent.KEYCODE_BACK) { // если нажата кнопка "Назад"
			if(menu.isMenuShowing()){ // и если SlidingMenu открыто
        		menu.toggle(true); // закрываем его
                        return false;
        	        }
                 }
	         return super.onKeyDown(keyCode, event);
}

В комментариях также предложили использовать onBackPressed()

Полный код MainActivity.java

import com.jeremyfeinstein.slidingmenu.lib.SlidingMenu;

import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;

public class MainActivity extends ActionBarActivity {
	
	private SlidingMenu menu;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		
		menu = new SlidingMenu(this);
		menu.setMode(SlidingMenu.LEFT);
		menu.setTouchModeBehind(SlidingMenu.TOUCHMODE_FULLSCREEN);
		menu.setShadowDrawable(R.drawable.actionbar_gradient);
		menu.setShadowWidth(15);
		menu.setFadeDegree(0.0f);
		menu.attachToActivity(this, SlidingMenu.SLIDING_WINDOW);
		menu.setBehindWidth(200);
		menu.setMenu(R.layout.menu_frame);
		
		getSupportActionBar().setDisplayShowCustomEnabled(true);
        getSupportActionBar().setDisplayHomeAsUpEnabled(true);
	}

	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		// Inflate the menu; this adds items to the action bar if it is present.
		getMenuInflater().inflate(R.menu.main, menu);
		return true;
	}
	
	@Override
	public boolean onOptionsItemSelected(MenuItem item) {
	    switch (item.getItemId()) {
	    case android.R.id.home:
	    	menu.toggle(true);
	        return true;
	    }
	    return super.onOptionsItemSelected(item);
	}
	
	public boolean onKeyDown(int keyCode, KeyEvent event) {
		if (keyCode == KeyEvent.KEYCODE_BACK) {
			if(menu.isMenuShowing()){
        		menu.toggle(true);
                        return false;
        	        }
                }
	        return super.onKeyDown(keyCode, event);
	}

}



Результат:

(к сожалению, этот скрин не грузился полностью на HabraStorage, пришлось воспользоваться сторонним хостингом изображений).

В качестве заключения


Отвечу логичный на вопрос, который может возникнуть у читателя: «Почему не ActionBar Sherlock» (если вы конечно знаете, что это)?
Ответ прост: я не знал о его существовании в начале работы. Когда занялся SlidingMenu, узнал и о рекомендованном шерлоке, но менять Support Lib и половину кода с ним — уже не было желания.

Как выяснилось, начать программировать для Android не так то просто, учитывая, что половина проблем связана вовсе не с данной платформой, а с инструментами для нее.
Но, как я говорил выше, нужно только немного разобраться и дальше уже все становится куда понятнее.

Спасибо, если вы дочитали до конца.

P.S. Если у вас имеются замечания по качеству кода или качеству текста — высказывайте, но не забывайте, пожалуйста, что все рассчитано для новичков от такого-же новичка.

UPD: Добавил замечания из комментариев.
Tags:
Hubs:
Total votes 12: ↑10 and ↓2 +8
Views 15K
Comments Comments 17