/app-theme-engine

An easy to use app-level theme engine for Android developers.

Primary LanguageJavaApache License 2.0Apache-2.0

App Theme Engine (BETA)

App Theme Engine is a library that makes it easy for developers to implement a theme system in their apps, similar to what's seen in Cabinet and Impression.

Download the latest sample APK to check it out!


Table of Contents

  1. Gradle Dependency
    1. Repository
    2. Dependency
  2. Applying
    1. ATEActivity
    2. Custom Activities and Fragments
    3. Task Description (Recents)
    4. Overflow Menu Widgets
    5. Lists
    6. Navigation Drawers
  3. Config
    1. Modifiers
    2. Default Configuration
    3. Value Retrieval
    4. ATEStatusBarCustomizer
  4. Tags
    1. Background Colors
    2. Text Colors
    3. Text Link Colors
    4. Tint Colors
  5. Pre-made Views

Gradle Dependency

JitPack Build Status License

Repository

Add this in your root build.gradle file (not your module build.gradle file):

allprojects {
	repositories {
		...
		maven { url "https://jitpack.io" }
	}
}

Dependency

Add this to your module's build.gradle file:

dependencies {
	...
	compile('com.github.afollestad:app-theme-engine:0.2.1') {
		transitive = true
	}
}

Applying

Before we go into details of how you can configure theme colors, you need to know how the theme engine is applied.

ATEActivity

As seen in the sample project, you can have all Activities in your app extends ATEActivity. This will do all the heavy lifting for you, all that you have to worry about is theme configuration.

public class MyActivity extends ATEActivity {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        
        // setContentView() triggers the theme engine
        setContentView(R.layout.my_layout);
    }
}

If you were to change theme colors from a visible ATEActivity, the changes are reflected automatically if you use the apply() methods discussed in the next two sections.

Custom Activities and Fragments

If you don't use ATEActivity, there's a few things you have to do:

public class MyActivity extends AppCompatActivity {

    private long updateTime = -1;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        ATE.preApply(this); // apply primary color to status bar, nav bar, and task description (recents)
        super.onCreate(savedInstanceState);
        setContentView(R.layout.my_layout); // call BEFORE apply()
        updateTime = System.currentTimeMillis();
        ATE.apply(this);    // apply colors to other views in the Activity
    }
    
    @Override
    protected void onResume() {
        super.onResume();
        // If values were applied/committed (from Config) since the Activity was created, recreate it now
        if (ATE.didValuesChange(this, updateTime))
            recreate();
    }
}

You can also apply theming to views in a Fragment:

public class MyFragment extends Fragment {

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        ATE.apply(this);
    }
}

Overflow Menu Widgets

If you have checkboxes or radio buttons in your Toolbar's overflow menu, you can tint them to your accent color:

public class MyActivity extends ATEActivity {

    private Toolbar mToolbar;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.my_activity);
        
        mToolbar = (Toolbar) findViewById(R.id.my_toolbar);
        setSupportActionbar(mToolbar); // ATE does not support toolbars that aren't set as action bars right now
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.main, menu);
        return super.onCreateOptionsMenu(menu);
    }

    @Override
    public boolean onMenuOpened(int featureId, Menu menu) {
        // When the overflow menu opens, a tint is applied to the widget views inside
        ATE.applyMenu(mToolbar);
        return super.onMenuOpened(featureId, menu);
    }
}

You could override onMenuOpened(int, Menu) from any other type of Activity too, not just ATEActivity.

Lists

When working with lists, you have to apply the theme engine to individual views through your adapter.

For RecyclerViews:

public static class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyViewHolder> {

    public MyAdapter() {
    }

    @Override
    public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View list = LayoutInflater.from(parent.getContext())
                .inflate(R.layout.list_item, parent, false);
        return new MyViewHolder(list);
    }

    @Override
    public void onBindViewHolder(MyViewHolder holder, int position) {
        // Setup views
    }

    @Override
    public int getItemCount() {
        return 20;
    }

    public static class MyViewHolder extends RecyclerView.ViewHolder {

        public MyViewHolder(View itemView) {
            super(itemView);
            // It's recommended you only apply the theme the first time the holder is created
            ATE.apply(itemView.getContext(), itemView);
        }
    }
}

For ListViews:

public static class MyAdapter extends BaseAdapter {

    @Override
    public int getCount() {
        return 20;
    }

    @Override
    public Object getItem(int position) {
        return null;
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        if (convertView == null) {
            convertView = LayoutInflater.from(parent.getContext())
                    .inflate(R.layout.list_item, parent, false);
            // Only apply the first time the view is created
            ATE.apply(convertView.getContext(), convertView);
        }
        return convertView;
    }
}

Navigation Drawers

ATE will automatically adapt when your Activity has a DrawerLayout at its root. When coloredStatusBar() is set to true, the primary dark theme color will be applied to the DrawerLayout rather than directly to the Window status bar. Thus, the status bar will be transparent when the drawer is open, and your theme color when it's closed. You don't have to manually do anything.

If you use NavigationView from the design support library, ATE will by default theme it. There are navigation view theming configuration methods discussed in the next section. If your drawer uses a Fragment or plain ListView/RecyclerView, you have to do what's discussed in the previous section.


Config

By default, Android app themes are static. They cannot be changed dynamically after an APK is built. This library allows you to dynamically change theme colors at runtime.

All configuration options are persisted using SharedPreferences, meaning once you set them, you don't have to set them again unless you want the value to be changed from what it was previously.

Modifiers

Here are a few configuration methods that can be used:

ATE.config(this) // context
    .primaryColor(color)
    .primaryColorDark(color)
    .accentColor(color)
    .statusBarColor(color) // by default, is equal to whatever primaryColorDark is set to
    .textColorPrimary(color)
    .textColorSecondary(color)
    .coloredStatusBar(true)
    .coloredActionBar(true)
    .coloredNavigationBar(false)
    .autoGeneratePrimaryDark(true)
    .navigationViewThemed(true)
    .navigationViewSelectedIcon(color)
    .navigationViewSelectedText(color)
    .navigationViewNormalIcon(color)
    .navigationViewNormalText(color)
    .apply(this); // activity, fragment, or view

There's also color resource and color attribute variations of the color modifiers. For an example: rather than using primaryColor(int), you could use primaryColorRes(int) or primaryColorAttr(int) in order to pass a value in the format R.color.resourceValue or R.attr.attributeValue.

Default Configuration

If you want to setup a default configuration the first time your app is run, you can use code like this:

if (!ATE.config(this).isConfigured()) {
    // Setup default options
}

Value Retrieval

Using the Config class, you can retrieve your theme values (if you need to for any reason). For an example:

int primaryColor = Config.primaryColor(this);

ATEStatusBarCustomizer

If you want individual Activities to have different status bar colors, e.g. in an app that extracts colors from an image using Palette to get theme colors, you can implement ATEStatusBarCustomizer in the Activities which require it.

public class MyActivity extends AppCompatActivity implements ATEStatusBarCustomizer {
    
    @ColorInt
    @Override
    public int getStatusBarColor() {
        return Color.RED; // return whatever you want here
    }
}

Tags

If you haven't used tags before, they can be applied to views directly from your XML layouts:

<View
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:tag="tag-value-here"
    />

The theme engine allows you to apply theme colors to any view using tags. You can even use multiple tags, separated by commas:

<View
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:tag="tag-one,tag-two,tag-three"
    />

Here's a list of available tag values:

Background Colors

You can change the background of any type of view.

  1. bg_primary_color - sets the background to the primary color.
  2. bg_primary_color_dark - sets the background to the primary dark color.
  3. bg_accent_color - sets the background to the accent color.
  4. bg_text_primary - sets the background to the primary text color.
  5. bg_text_secondary - sets the background to the secondary text color.

Text Colors

You can only change the text color of a view that extends TextView, which includes Button's.

  1. text_primary_color - sets the text color to the primary color.
  2. text_primary_color_dark - sets the text color to the primary dark color.
  3. text_accent_color - sets the text color to the accent color.
  4. text_primary - sets the text color to the primary text color.
  5. text_secondary - sets the text color to the secondary text color.

Text Link Colors

This should only really be needed on TextView's, it changes the color of links when TextViews are linkable.

  1. text_link_primary_color - sets the link text color to the primary color.
  2. text_link_primary_color_dark - sets the link text color to the primary dark color.
  3. text_link_accent_color - sets the link text color to the accent color.
  4. text_link_primary - sets the link text color to the primary text color.
  5. text_link_secondary - sets the link text color to the secondary text color.

Tint Colors

You can tint CheckBox's, RadioButton's, ProgressBar's, EditText's, SeekBar's, and ImageView's.

  1. tint_primary_color - tints the view with the primary color.
  2. tint_primary_color_dark - tints the view with the primary dark color.
  3. tint_accent_color - tints the view with the accent color.
  4. tint_text_primary - tints the view with the primary text color.
  5. tint_text_secondary - tints the view with the secondary text color.

Pre-made Views

Seven views come stock with this library:

  1. ATECheckBox - tints itself to the accent color.
  2. ATERadioButton - tints itself to the accent color.
  3. ATEEditText - tints itself to the accent color
  4. ATEProgressBar - tints itself to the accent color.
  5. ATESeekBar - tints itself to the accent color.
  6. ATEPrimaryTextView - sets its text color to the primary text color.
  7. ATESecondaryTextView - sets its text color to the secondary text color.

All that they really do is set their own tag to one of the tag values in the previous section, and then apply theming to themselves using the individual view apply() method.