2013-03-06 4 views
1

Я искал всюду и не могу найти никакого способа сделать это вообще:Как определить наличие ландшафтной версии макета во время выполнения?

В моем проекте у меня есть две папки ресурсов для макетов: один называется «макет», а другой называется «макет» -земельные участки". Это знакомый способ иметь два отдельных макета для каждой активности, которые будут использоваться в зависимости от текущей ориентации. Проблема в том, что у меня нет отдельных макетов макета для каждой деятельности, и не все действия даже требуют другой макет для ландшафтного режима.

Что я сделал, это переопределение по умолчанию onConfigurationChanged, чтобы предотвратить изменения ориентации (с соответствующим configChanges="orientation" в манифесте). Это избавило меня от многих головных болей, набивающих вещи в Bundles каждый раз, когда пользователь наклонил их экран. Однако, в некоторых случаях, я на самом деле хочу изменений ориентации. Всякий раз, когда в папке «макет-земля» есть соответствующий макет, я хочу, чтобы ориентация менялась, как обычно. Всякий раз, когда этого не происходит, я хочу, чтобы onConfigurationChanged подавлял событие изменения ориентации.

Я сделал родительский BaseActivity класс, который расширяет Activity и является базовым классом для всех моих мероприятий, и отменяет onConfigurationChanged:

@Override 
public void onConfigurationChanged(android.content.res.Configuration newConfig) 
{ 
    super.onConfigurationChanged(newConfig); 

    if(...there is a layout-land version of the current activity's layout...) 
    { 
     setRequestedOrientation(newConfig.orientation); 
    } 
    else 
    { 
     // Do nothing, since raising the orientation change event 
     // to change from a portrait layout to the same portrait layout is silly. 
    } 
} 

Единственная загвоздка в том, что я не могу найти какой-либо способ определения, является ли есть версия макета для моего макета. Я уже знаю идентификатор ресурса текущего макета (полученный в моем классе BaseActivity путем переопределения setContentView), но и альбомная версия макета, и портретная версия имеют один и тот же идентификатор, и я не вижу легкого способа для объекта Resources скажите мне, какая версия макета мне дает, когда я спрашиваю об этом конкретный ресурс по id. Должен быть способ сделать это. Что мне не хватает?

+0

Переопределение 'onConfigurationChanged()' просто для того, чтобы избежать сохранения состояния - это просто плохая практика, и я вернусь, чтобы укусить вас позже. – kcoppock

+0

Существование 'onSaveInstanceState' - это плохая практика. Идея о том, что вы должны быть готовы к тому, чтобы ваше приложение было полностью утилизировано в любой момент времени, просто потому, что пользователь повернул телефон плохой дизайн. Кроме того, _how_ избежит сохранения состояния, чтобы вернуться, чтобы укусить меня? Помните, что единственная причина, по которой я это делаю, - избегать ситуаций, когда диалог может появляться в то время, когда пользователь наклоняет свой экран. Пользовательские данные сохраняются в моем приложении всякий раз, когда он изменяется (в том числе в 'onSaveInstanceState', если необходимо.) – Alex

+0

Допустим, ваше приложение запущено, и пользователь решает открыть уведомление. Теперь ваше приложение находится в фоновом режиме. В зависимости от использования памяти ваше приложение может быть остановлено или уничтожено (по крайней мере, оно будет приостановлено) до того, как пользователь будет выполнен. Когда пользователь нажимает «назад», чтобы вернуться к вашей активности, активность воссоздается, и все ваши данные, содержащиеся в этом экземпляре активности, исчезли. – kcoppock

ответ

1

Хорошо, поэтому я нашел решение, хотя и жалкое, взломанное решение. Я не рекомендую этот хак, потому что он сделан из сатаны:

Объект Resources, который вы можете получить из метода Activity getResources(), сам по себе не скажет вам, есть ли версия ресурса «макет-земля». Однако вы можете получить объект AssetManager, который объект Resources использует внутренне, вызывая его метод getAssets().

Объект AssetManager имеет метод под названием openNonAssetFd, который попытается захватить файловый дескриптор для определенного файла в вашей папке res. К сожалению, большинство (если не все) вещей, которые вы пытаетесь открыть с помощью этого метода, будут вызывать FileNotFoundException, даже если файл существует!

Так, например, если у вас есть файл с именем res/layout-land/settings_menu.xml, он будет вызывать FileNotFoundException с сообщением: «Этот файл не может быть открыт как дескриптор файла, он, вероятно, сжат». Это плохой дизайн со стороны людей Android, так как технически этот файл есть, а исключение должно быть, по крайней мере, другого типа.

Что еще более важно, однако, с целью обнаружения простого существования макета layout-land, мы можем положиться на разницу в возвращаемых сообщениях при генерированном исключении. В этом заключается взлом:

Если файл существует, сообщение об исключении будет «Этот файл не может быть открыт как файловый дескриптор, он, вероятно, сжат».

Если файл не существует, сообщение об исключении будет идентично параметру, который вы передали методу openNonAssetFd (т. Е. Будет именем файла несуществующего ресурса).

Теперь с этим, мы можем сделать метод, как так:

protected boolean landscapeLayoutExists(int layoutId) 
{ 
// Since we're always passing in an id of a layout, this will always correspond 
// to a layout file in any of the layout folders (either layout, or both layout and layout-land, in my case). 
String rawName = this.getResources().getResourceEntryName(layoutId); 
String testPath = "res/layout-land/" + rawName + ".xml"; 

try 
{ 
    this.getResources().getAssets().openNonAssetFd(testPath); 
} 
catch(Exception ex) 
{ 
    if(ex.getMessage().equals(testPath)) 
     return false; 
    else 
     return true; 
} 

// Somehow, we managed to grab the file descriptor successfully. 
// In my experience, this cannot happen with layouts, but if it ever does, 
// then we definitely know the file exists. 
return true; 
} 

Используя этот метод, я могу определить, есть ли вариант расположения текущей деятельности в папке «раскладка-Лэнд» , и действовать соответственно.

Однако я не удовлетворен этим подходом, хотя он работает (на данный момент, по крайней мере, на Galaxy S3 я тестировал это). Если кто-то лучше, пожалуйста, дайте мне знать.

1

В вашем макете в папке layout_land добавьте id = layout_name_of_this_file для вашего самого удаленного FrameLayout. Например

<?xml version="1.0" encoding="utf-8"?> 
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 
    android:id="@+id/layout_name_of_this_file" 
    ..... 

и в коде

getResources().getIdentifier("layout_name_of_this_file", "id", "com.example....")) = resource id of the button (a pretty long number) 

и

getResources().getIdentifier("layout_name_of_the_layout_file_exist_in_layout_but_not_in_layout_land", "id", "com.example....")) = 0 

, потому что нет FrameLayout с этим идентификатором.

+0

Ну ... во-первых, это не сработает, так как 'getIdentifier' просто возвращает целое число id, которое скомпилируется в объект' R'. Если вы создали идентификатор 'button_fake_activity_name', то он будет существовать независимо от того, в настоящее время вы находитесь в ландшафтном макете (ресурсы существуют для каждого приложения, а не для активности). Однако я мог бы так же просто переопределить 'onConfigurationChanged' в действиях, где я не хотел менять ориентацию. Проблема в том, что я хотел простое решение, которое я мог бы поместить в класс «BaseActivity» вместо каждого действия. (У меня есть 50+ Activities ...) – Alex

+0

Нет, что я говорю, это если нет layout_land для активности имени, тогда нет имени button_fake_activity_name, и приведенный выше код вернет 0. Я тестировал в своем тестовом приложении. –

+0

Я имею в виду, что вы только помещаете поддельную кнопку в свой макет, а не в обычную раскладку. Таким образом, нет кнопки layout_land no. –

Смежные вопросы