Pull to refresh

Как отделить тему от приложения

Reading time5 min
Views5.6K
Проблема настройки (кастомизации) внешнего вида Android-приложений часто возникает перед разработчиками. Причиной может быть необходимость следования интерфейса корпоративному стилю или требование заказчика, желающего, чтобы его приложение выглядело по-особенному, а не просто как набор стандартных элементов.

Существуют встроенные в платформу средства для этих целей (темы, стили), однако они не предоставляют внятного механизма изменения интерфейса приложения без изменения кода самого приложения.

Мною предлагается технология, позволяющая динамически менять внешний вид Android приложения путем установки новых «тем», которые могут быть скачаны отдельно от приложения. Описанная в статье разработка выполнялась в качестве пилотного проекта в департаменте мобильных приложений компании «Мера-НН» (www.meranetworks.com), где и работает автор статьи.

Библиотека


Рассматриваемый механизм предусматривает распространение тем в виде отдельных .apk-файлов, которые могут быть скачаны с сайта разработчика и установлены на устройство как обычное Android-приложение. Возможна смена тем как для базовых элементов платформы так и для вновь созданных (custom) UI элементов.

Идея базируется на том, что Android API позволяет из одного приложения получить доступ к ресурсам другого приложения.

Пример кода для доступа к ресурам другого приложения:

PackageManager pm = context.getPackageManager();
Resources res = pm.getResourcesForApplication ("package name");
Resources.Theme rstheme = res.newTheme();

здесь package name – произвольный пакет, установленный на устройстве.

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

Исходный код класса BaseActivity приведен ниже.

package com.mera.detachedthemeslib;

import android.app.Activity;
import android.content.res.Resources;
import android.os.Bundle;

public abstract class BaseActivity extends Activity {

	@Override
	public void onCreate(Bundle savedInstanceState) {
		ActivityManager.setThemeForActivity(this);
		super.onCreate(savedInstanceState);
	}

	@Override
	public Resources getResources() {
		return ActivityManager.getResourcesForActivity(this, super.getResources());
	}

	@Override
	public Resources.Theme getTheme() {
		return ActivityManager.getThemeForActivity(this, super.getTheme());
	}
}

Для того что бы управлять текущей конфигурацией приложения был создан класс ThemeManager.
Он реализует следующую функциональность:
  • позволяет получать список доступных тем;

static List<Theme> getThemes(Context ctx, ThemesConfiguration cfg)

  • позволяет производить переключение между темами.

static void setTheme(Context ctx, Theme theme)

Пример использоваия ThemeManager будет приведен ниже в примере кода для приложения используещего отделенные темы.

Данные классы (BaseActivity и ThemeManager), а также набор вспомогательных классов, были объединены в библиотеку, которая доступна на github.

Разработка приложений с использованием библиотеки:


Для обеспечения возможности динамического переключения тем в приложении должны быть выполнены некоторые условия.

Во-первых, для того, чтобы разрабатываемое приложение позволяло производить динамическую смену тем, необходимо чтобы все Activity данного приложения были унаследованы от BaseActivity (BaseListActivity, BasePreferenceActivity).

Во-вторых, приложение должно содержать тему с именем «MainTheme».
Пример из разработанного приложения:

<style name="MainTheme" parent="android:Theme">
        <item name="custButtonStyle">@android:style/Widget.Button</item>
</style>

При наличии других стандартных тем, встроенных в приложение, и при необходимости cделать их доступными для пользователей, их следует указать при обращении к ThemeManager. Пример того, как это делается приведен ниже, при добавлении темы «Green».

Имеются также ограничения для дополнительного приложения предоставляющего собой отдельную тему:
  • Имя пакета для него должно начинаться также как и имя для основного приложения.
  • Приложение-тема должно содержать тему с именем “DetachedTheme”.

Ниже приведен пример кода для приложения используещего отделенные темы:

Главный экран приложения:

package com.mera.detachedthemesapp;

import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.ArrayAdapter;
import android.widget.Spinner;

import com.mera.detachedthemeslib.BaseActivity;

public class MainActivity extends BaseActivity {
	    
	@Override
	public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        String[] items = new String[] {"One", "Two", "Three"};
        Spinner spinner = (Spinner) findViewById(R.id.spinner1);
        ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
                    android.R.layout.simple_spinner_item, items);
        adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
        spinner.setAdapter(adapter);

        this.findViewById(R.id.switch_button).setOnClickListener(new OnClickListener() {

			@Override
			public void onClick(View arg0) {
				startActivity(new Intent(MainActivity.this, SwitchThemeActivity.class));
			}
		});
	}
}

Экран со списком тем для переключение между ними:

package com.mera.detachedthemesapp;

import java.util.List;

import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ArrayAdapter;
import android.widget.TextView;

import com.mera.detachedthemeslib.BaseListActivity;
import com.mera.detachedthemeslib.ThemesConfiguration;
import com.mera.detachedthemeslib.ThemeManager;

public class SwitchThemeActivity extends BaseListActivity {

    private LayoutInflater mInflater;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mInflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);

        List<ThemeManager.Theme> themes = ThemeManager.getThemes(this,
                new ThemesConfiguration()
                        .addInnerTheme(R.style.CustomTheme2, "Green"));

        ArrayAdapter<ThemeManager.Theme> adapter = new ArrayAdapter<ThemeManager.Theme>(
                this, android.R.layout.simple_list_item_1, themes) {

            @Override
            public View getView(int position, View convertView, ViewGroup parent) {
                View row;

                if (null == convertView) {
                    row = mInflater.inflate(
                            android.R.layout.simple_list_item_1, null);
                } else {
                    row = convertView;
                }

                TextView tv = (TextView) row.findViewById(android.R.id.text1);
                tv.setText(((ThemeManager.Theme) getItem(position)).mTitle);
                return row;
            }

        };

        getListView().setAdapter(adapter);
        getListView().setOnItemClickListener(new OnItemClickListener() {

            @Override
            public void onItemClick(AdapterView<?> parent, View view,
                    int position, long id) {
                ThemeManager.Theme theme = (ThemeManager.Theme) getListView()
                        .getAdapter().getItem(position);

                ThemeManager.setTheme(SwitchThemeActivity.this, theme);
                Intent intent = new Intent();
                intent.setClass(SwitchThemeActivity.this, MainActivity.class);
                intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
                SwitchThemeActivity.this.startActivity(intent);
            }
        });

    }
}

Как это выглядит...


Разработанное приложение позволяет производить смену внешнего вида приложения используя две встроенные темы.

Так же приложение позволяет установить и использовать новую тему, которая была скачана и установлена отдельно как обычное Android-приложение.

Код библиотеки и примера приложения с отделенной темой доступен на github.
DetachedThemesLib — библиотека
DetachedThemesApp — приложение
DetachedThemesTheme — отделенная тема
Tags:
Hubs:
+19
Comments20

Articles