Seize the day

POST : Android Dev Study

앱 테마 만들기 #2

ThemeManager#applyTheme 메쏘드에서 테마의 적용이 시작됩니다. ThemeItemKey는 단순히 나중에 참조하게될 리소스 아이디만을 들고 있는 데이타 클래스입니다.


ThemeItemKey

public class ThemeItemKey {
    public final int keyId;
    public final int keyNamesId;
    public final int viewIdsId;
    public final int defaultMappingId;

    public ThemeItemKey(int keyId, int keyNamesId, int viewIdsId, int defaultMappingId) {
        this.keyId = keyId;
        this.keyNamesId = keyNamesId;
        this.viewIdsId = viewIdsId;
        this.defaultMappingId = defaultMappingId;
    }
}


ThemeItemKey로 적용할 때 ThemeItem을 로드하고 메모리 캐시(SparseArray)에 저장합니다. 

private final SparseArray<ThemeItem> themeItems = new SparseArray<>();
private final ThemeDrawableCache cache = new ThemeDrawableCache();
private final ThemeJsonParser jsonParser = new ThemeJsonParser();

public void applyTheme(@NonNull View viewGroup, ThemeItemKey themeItemKey) {
        ThemeItem themeItem = getThemeItem(themeItemKey);
        if (themeItem == ERROR_ITEM || !themeItem.hasData()) {
            return;
        }
        themeItem.applyTheme(context, viewGroup, cache);
    }

    @NonNull
    private ThemeItem getThemeItem(ThemeItemKey themeItemKey) {
        ThemeItem themeItem = themeItems.get(themeItemKey.keyId);
        if (themeItem == null) {
            try {
                String key = context.getString(themeItemKey.keyId);
                String[] names = context.getResources().getStringArray(themeItemKey.keyNamesId);
                TypedArray ar = context.getResources().obtainTypedArray(themeItemKey.viewIdsId);
                int[] ids = new int[ar.length()];
                for (int i = 0; i < ar.length(); i++) {
                    ids[i] = ar.getResourceId(i, 0);
                }
                ar.recycle();
                themeItem = new ThemeItem(key, names, ids);
                initThemeData(themeItemKey.keyId, themeItem);
                themeItems.put(themeItemKey.keyId, themeItem);
            } catch (Exception ex) {
                DebugUtils.notReached(ex);
                themeItems.put(themeItemKey.keyId, ERROR_ITEM);
                return ERROR_ITEM;
            }
        }
        return themeItem;
    }
	
	private void initThemeData(int themeItemId, ThemeItem themeItem) {
        boolean hasThemeData = false;
        if (hasFiles) {
            hasThemeData |= jsonParser.parseThemeItem(themeItem);
        }

        themeItem.setApplyDefault(!hasThemeData);
    }	


ThemItem은 json keys, view ids 그리고 해당 view에 적용할 ThemeData를 가지고 있습니다.

public class ThemeItem {
    public final String key;
    public final String[] names;
    public final int[] viewIds;
    private final ThemeData[] themeDatas;
    private final SparseIntArray viewIdToIndex = new SparseIntArray();
    private boolean applyDefault;
    private boolean hasData;

    @NonNull
    public ThemeData getThemeData(int index) {
        if (themeDatas[index] == null) {
            themeDatas[index] = new ThemeData();
            hasData = true;
        }
        return themeDatas[index];
    }

    public void applyTheme(Context context, View viewGroup, ThemeDrawableCache cache) {
        for (int i = 0; i < viewIds.length; i++) {
            if (themeDatas[i] != null) {
                View view = viewGroup.findViewById(viewIds[i]);
                if (view != null) {
                    themeDatas[i].applyTheme(context, view, cache, applyDefault);
                }
            }
        }
    }


ThemeData는 view 하나에 적용해야 할 Color, ColorList, Image(Drawable)를 가지고 있습니다. 컬러 리스트와 이미지 데이타는 ResourceData 인터페이스를 implement한 ColorListResourceData와 ImageResourceData의 인스턴스를 들고 있습니다.

public class ThemeData {
    private final SparseArray<ResourceData> resourceDatas = new SparseArray<>();

    public void addResourceData(int resourceType, ResourceData resourceData) {
        resourceDatas.put(resourceType, resourceData);
    }

    boolean applyTheme(Context context, View view, ThemeDrawableCache cache, boolean applyDefault) {
        boolean result = false;
        try {
			for (int i = 0; i < resourceDatas.size(); i++) {
				result |= resourceDatas.valueAt(i).applyTheme(context, view, cache);
			}
        } catch (Exception ex) {
            TraceUtils.e("applyTheme failed");
        }
        return result;
    }


ResourceData 

public interface  ResourceData {

    boolean applyTheme(Context context, View view, ThemeDrawableCache cache);
}


따라서 applyTheme는 ThemeItem#applyTheme를 호출하면 각 view를 돌면서 ThemeData#applyTheme를 호출합니다. 다시 ResourceData#applyTheme를 호출하여 최종적으로 컬러와 배경색, 배경 이미지를 변경합니다.


ThemeData를 만들고 ImageResourceData와 ColorListResourceData를 추가하는 라인은 jsonParser.parseThemeItem(themeItem) 입니다. ThemeManager의 대략적인 코드 정리는 여기서 마무리합니다. 상세한 코드는 공개하지 않습니다. 데이타 구조나 다루는 방식은 구현하기 나름이고 저의 코드가 최선이라고 생각하지도 않습니다. 구조만 보더라도 어렵지 않은 코드입니다. Json 파서와 Theme Drawable cache는 다음에 정리합니다. 여기에는 참고할 만한 코드가 좀 더 있습니다.

top

posted at

2018. 6. 14. 22:41


CONTENTS

Seize the day
BLOG main image
김대정의 앱 개발 노트와 사는 이야기
RSS 2.0Tattertools
공지
아카이브
최근 글 최근 댓글
카테고리 태그 구름사이트 링크