Create downloadable custom theme and apply it during run time

爷,独闯天下 提交于 2019-11-30 07:02:05

I've finally decided to solve this by making a custom system for handling drawables, strings etc so now i have a custom class called "ResourceManager" that handles what needs to be loadad and how, and themes are distributed as a zip file which app downloads, extracts and later uses.

I had to compile nine patch images by myself before putting them in zip file, and I did that using "abrc" from here: http://forum.xda-developers.com/showthread.php?t=785012

I've also created a simple bash script that goes recursively through custom folder and compiles all nine patch images with abrc.

I've also created a simple helper in the ResourceManager that checks and tells me the screen density so i can normally support images in hdpi, xhdpi etc densities, and finally i don't re-create the images every time i need them, i save them in a static list of HashMap so i can reuse the ones I've already created and that way i hope to prevent wasting too much phone's memory :).

OK that's all in short lines, if anyone has any questions please let me know, i'll be glad to share this experience with anyone.

Cheers!

============ EDIT ============

Here's the class I ended up writing for this purpose (it downloads the file, checks for it's version, loads strings from JSON file rather than strings.xml etc)

NOTE: This is not a full class so some parts are missing, but I think it's more than enough to get the idea how I solved all this :)

/**
 * Created by bojank on 7/28/2014.
 * Class that handles custom resources downloaded from server
 */
public class ResourceManager {

    // List of ninePatchImages in the application
    private static ArrayList<HashMap<String, NinePatchDrawable>> ninePatchHashMaps;
    private static ArrayList<HashMap<String, Drawable>> imagesHashMaps;

    private static ImageLoader imageLoader;

    // Context for methods
    public static Context ctx;

    // JSONObject with all strings
    private static JSONObject joString;

    // JSONObject with all styles
    private static JSONObject joStyles;

    // String with current active lang code
    private static String currentLanguage;

    private static String sdcardPath;

    // Private consturctor to prevent creating a class instance
    private ResourceManager() {
    }

    /**
     * Method that returns a translated string for given key
     *
     * @param key String
     * @return String
     */
    public static String getString(String module, String key) {
        String output = ""; //String.format("[%s - %s]", module, key);

        try {
            if (getStringsFile() != null && getStringsFile().getJSONObject(module).has(key))
                output = getStringsFile().getJSONObject(module).getString(key);
        } catch (Exception e) {

            // Force some default language if proper json file is missing for newly added language
            currentLanguage = "en-US";
            Helper.saveLocale(currentLanguage, ctx);
            Helper.logError("ErrorFetchingString", e);
        }

        return output;
    }

    /**
     * Method that returns JSONObject with string resources
     * @return JSONObject
     * @throws JSONException
     */
    public static JSONObject getStringsFile() throws JSONException {

        if (joString == null) {
            String stringFileName = getResourcesPath() + "languages/" + getCurrentLanguage() + "/values.json";
            String languageFile = Helper.readJsonFile(stringFileName);
            if (languageFile != null) {
                joString = new JSONObject(Helper.readJsonFile(stringFileName));
            } else {
                return null;
            }
        }

        return joString.getJSONObject("strings");
    }

    /**
     * Method that returns current language ("sr", "en"...)
     * @return String
     */
    public static String getCurrentLanguage() {

        if (currentLanguage == null)
            currentLanguage = Helper.getCurrentLanguage(ctx);

        return currentLanguage;
    }

    /**
     * Method that resets joString object and currentLanguage on language change
     */
    public static void resetLanguage() {
        joString = null;
        currentLanguage = null;
    }

    /**
     * Method that resets joStyles object on theme change
     */
    public static void resetStyle() {
        joStyles = null;
    }

    /**
     * Method that deletes a directory from filesystem
     * @param path File
     * @return boolean
     */
    public static boolean deleteDirectory(File path) {
        if( path.exists() ) {
            File[] files = path.listFiles();
            for(int i=0; i<files.length; i++) {
                if(files[i].isDirectory()) {
                    deleteDirectory(files[i]);
                }
                else {
                    files[i].delete();
                }
            }
        }
        return(path.delete());
    }

    /**
    * Method that get's the version of assets file
    * @param url String
    */
    public static String getAssetsVersion(String url) throws IOException {
        Helper.logInfo("REQUEST URL:", url);
        OkHttpClient client = new OkHttpClient();

        // set connection timeut to 5min
        client.setConnectTimeout(1, TimeUnit.MINUTES);

        Request request = new Request.Builder()
                .url(url)
                .build();

        Response response = client.newCall(request).execute();
        return response.body().string();
    }

    /**
     * Method that downloads assets file from server
     * @param url String
     * @return String
     * @throws IOException
     */
    public static String getAssetsFile(String url) throws IOException {

        Helper.logInfo("REQUEST URL:", url);
        OkHttpClient client = new OkHttpClient();

        // set connection timeut to 5min
        client.setConnectTimeout(1, TimeUnit.MINUTES);

        Request request = new Request.Builder()
                .url(url)
                .header("User-Agent", MyApplication.USER_AGENT)
                .build();

        Response response = client.newCall(request).execute();
        InputStream inputStreamFile = response.body().byteStream();

        try {

            // Output stream
            String outputFileName = Environment.getExternalStorageDirectory().toString() + "/assets.zip";

            File deleteFile = new File(outputFileName);
            deleteFile.delete();

            OutputStream output = new FileOutputStream(outputFileName);

            byte data[] = new byte[1024];

            int count;

            // writing data to file
            while ((count = inputStreamFile.read(data)) != -1)
                output.write(data, 0, count);

            // flushing output
            output.flush();

            // closing streams
            output.close();
            inputStreamFile.close();

            return outputFileName;

        } catch (Exception e) {
            Helper.logError("Download Resursa", e);
            return "ERROR";
        }
    }

    public static void setStyle(View v, String styleName) {

        try {
            if (styleName == null || styleName.equals("")) {
                if (v instanceof EditText)
                    processStyle(v, getStylesFile().getJSONObject("EditText"));
            } else
                processStyle(v, getStylesFile().getJSONObject(styleName));
        } catch (Exception e) {
            Helper.logError("Setting Styles", e);
        }

    }

    private static void setBackground(View v, Drawable d) {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
            v.setBackgroundDrawable(d);
        } else {
            v.setBackground(d);
        }
    }

    public static JSONObject getStylesFile() throws JSONException {

        if (joStyles == null) {
            String stylesFileName = getResourcesPath() + "styles/properties.json";
            joStyles = new JSONObject(Helper.readJsonFile(stylesFileName));
        }

        return joStyles;

    }

    public static void processStyle(View v, JSONObject joStyle) {

        if(joStyle != null) {
            try {

                // used for layout margins
                LinearLayout.LayoutParams layoutParams = null;

                if (Helper.isValidParameter(joStyle, "backgroundColor"))
                    v.setBackgroundColor(Color.parseColor(joStyle.getString("backgroundColor")));

                if (Helper.isValidParameter(joStyle, "backgroundImage"))
                    setBackground(v, loadNinePatchFromFilesystem(getImagesPath() + joStyle.getString("backgroundImage")));

                if (v instanceof TextView) {

                   applyTextViewParameters(v, joStyle);

                } else if (v instanceof  ListView) {

                    if (Helper.isValidParameter(joStyle, "dividerColor")) {
                        ((ListView) v).setDivider(new ColorDrawable(Color.parseColor(joStyle.getString("dividerColor"))));
                        ((ListView) v).setDividerHeight(Helper.convertDpToPixel(1));
                    }
                    if (Helper.isValidParameter(joStyle, "dividerHeight")) {
                        ((ListView) v).setDividerHeight(Helper.convertDpToPixel(joStyle.getInt("dividerHeight")));
                    }
                } else if (v instanceof UnderlinePageIndicator) {
                    if (Helper.isValidParameter(joStyle, "backgroundColor")) {
                        v.setBackgroundColor(Color.parseColor(joStyle.getString("backgroundColor")));
                    }
                    if (Helper.isValidParameter(joStyle, "selectedColor")) {
                        ((UnderlinePageIndicator) v).setSelectedColor(Color.parseColor(joStyle.getString("selectedColor")));
                    }
                } else if (v instanceof StyleableBackground) {
                    if (Helper.isValidParameter(joStyle, "backgroundColor")) {
                        View background = v.findViewById(R.id.llBackground);
                        if (background != null) {
                            background.setBackgroundColor(Color.parseColor(joStyle.getString("backgroundColor")));
                        }
                    }
                    if (Helper.isValidParameter(joStyle, "borderTopColor")) {
                        View topBorder = v.findViewById(R.id.llTopBorder);
                        if (topBorder != null) {

                            topBorder.setBackgroundColor(Color.parseColor(joStyle.getString("borderTopColor")));

                            if (Helper.isValidParameter(joStyle, "borderTopHeight")) {
                                topBorder.setMinimumHeight(Helper.convertDpToPixel(joStyle.getInt("borderTopHeight")));
                            }
                        }
                    }
                    if (Helper.isValidParameter(joStyle, "borderBottomColor")) {
                        View bottomBorder = v.findViewById(R.id.llBottomBorder);
                        if (bottomBorder != null) {

                            bottomBorder.setBackgroundColor(Color.parseColor(joStyle.getString("borderBottomColor")));

                            if (Helper.isValidParameter(joStyle, "borderBottomHeight")) {
                                bottomBorder.setMinimumHeight(Helper.convertDpToPixel(joStyle.getInt("borderBottomHeight")));
                            }
                        }
                    }
                    if (Helper.isValidParameter(joStyle, "backgroundImage")) {

                        ImageView ivBackgroundImage = (ImageView) v.findViewById(R.id.ivBackgroundImage);
                        if (ivBackgroundImage != null) {
                            BitmapDrawable d = (BitmapDrawable) ResourceManager.loadImageFromFilesystem(ResourceManager.getImagesPath() + joStyle.getString("backgroundImage"));

                            d.setTileModeXY(Shader.TileMode.REPEAT, Shader.TileMode.REPEAT);
                            d.setGravity(Gravity.FILL_HORIZONTAL | Gravity.FILL_VERTICAL);
                            setBackground(ivBackgroundImage, d);
                        }
                    }
                }

                if(Helper.isValidParameter(joStyle, "width"))
                    v.setMinimumWidth(joStyle.getInt("width"));

                if(Helper.isValidParameter(joStyle, "height"))
                    v.setMinimumHeight(joStyle.getInt("height"));

                if(Helper.isValidParameter(joStyle, "padding"))
                    v.setPadding(joStyle.getInt("padding"), joStyle.getInt("padding"), joStyle.getInt("padding"), joStyle.getInt("padding"));

                if(Helper.isValidParameter(joStyle, "paddingLeft"))
                    v.setPadding(joStyle.getInt("paddingLeft"), v.getPaddingTop(), v.getPaddingRight(), v.getPaddingBottom());

                if(Helper.isValidParameter(joStyle, "paddingTop"))
                    v.setPadding(v.getPaddingLeft(), joStyle.getInt("paddingTop"), v.getPaddingRight(), v.getPaddingBottom());

                if(Helper.isValidParameter(joStyle, "paddingRight"))
                    v.setPadding(v.getPaddingLeft(), v.getPaddingTop(), joStyle.getInt("paddingRight"), v.getPaddingBottom());

                if(Helper.isValidParameter(joStyle, "paddingBottom"))
                    v.setPadding(v.getPaddingLeft(), v.getPaddingTop(), v.getPaddingRight(), joStyle.getInt("paddingBottom"));

                if(Helper.isValidParameter(joStyle, "margin")) {
                    layoutParams = new LinearLayout.LayoutParams(v.getLayoutParams());
                    layoutParams.setMargins(joStyle.getInt("margin"), joStyle.getInt("margin"), joStyle.getInt("margin"), joStyle.getInt("margin"));
                }

                if(Helper.isValidParameter(joStyle, "marginLeft")) {
                    layoutParams = new LinearLayout.LayoutParams(v.getLayoutParams());
                    layoutParams.setMargins(joStyle.getInt("marginLeft"), layoutParams.topMargin, layoutParams.rightMargin, layoutParams.bottomMargin);
                }

                if(Helper.isValidParameter(joStyle, "marginTop")) {
                    layoutParams = new LinearLayout.LayoutParams(v.getLayoutParams());
                    layoutParams.setMargins(layoutParams.leftMargin, joStyle.getInt("marginTop"), layoutParams.rightMargin, layoutParams.bottomMargin);
                }

                if(Helper.isValidParameter(joStyle, "marginRight")) {
                    layoutParams = new LinearLayout.LayoutParams(v.getLayoutParams());
                    layoutParams.setMargins(layoutParams.leftMargin, layoutParams.topMargin, joStyle.getInt("marginRight"), layoutParams.bottomMargin);
                }

                if(layoutParams != null)
                    v.setLayoutParams(layoutParams);


                RelativeLayout.LayoutParams relativeLayoutParams = null;

                if (Helper.isValidParameter(joStyle, "alignParentTop") && joStyle.getBoolean("alignParentTop")) {
                    relativeLayoutParams = new RelativeLayout.LayoutParams(v.getLayoutParams());
                    relativeLayoutParams.addRule(RelativeLayout.ALIGN_PARENT_TOP);
                }

                if (Helper.isValidParameter(joStyle, "alignParentLeft") && joStyle.getBoolean("alignParentLeft")) {
                    relativeLayoutParams = new RelativeLayout.LayoutParams(v.getLayoutParams());
                    relativeLayoutParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT);
                }

                if (Helper.isValidParameter(joStyle, "alignParentBottom") && joStyle.getBoolean("alignParentBottom")) {
                    relativeLayoutParams = new RelativeLayout.LayoutParams(v.getLayoutParams());
                    relativeLayoutParams.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
                }

                if (Helper.isValidParameter(joStyle, "alignParentRight") && joStyle.getBoolean("alignParentRight")) {
                    relativeLayoutParams = new RelativeLayout.LayoutParams(v.getLayoutParams());
                    relativeLayoutParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
                }

                if(Helper.isValidParameter(joStyle, "marginLeft")) {
                    relativeLayoutParams = new RelativeLayout.LayoutParams(v.getLayoutParams());
                    relativeLayoutParams.setMargins(joStyle.getInt("marginLeft"), relativeLayoutParams.topMargin, relativeLayoutParams.rightMargin, relativeLayoutParams.bottomMargin);
                }

                if(Helper.isValidParameter(joStyle, "marginTop")) {
                    relativeLayoutParams = new RelativeLayout.LayoutParams(v.getLayoutParams());
                    relativeLayoutParams.setMargins(relativeLayoutParams.leftMargin, joStyle.getInt("marginTop"), relativeLayoutParams.rightMargin, relativeLayoutParams.bottomMargin);
                }

                if(Helper.isValidParameter(joStyle, "marginRight")) {
                    relativeLayoutParams = new RelativeLayout.LayoutParams(v.getLayoutParams());
                    relativeLayoutParams.setMargins(relativeLayoutParams.leftMargin, relativeLayoutParams.topMargin, joStyle.getInt("marginRight"), relativeLayoutParams.bottomMargin);
                }

                if (relativeLayoutParams != null) {
                    v.setLayoutParams(relativeLayoutParams);
                }

            } catch (Exception e) {
                Helper.logError("", e);
            }
        }
    }

    public static String getSdcardPath() {

        if(sdcardPath == null)
            sdcardPath = ctx.getApplicationInfo().dataDir;

        return sdcardPath;
    }

    public static String getResourcesPath() {

        return getSdcardPath() + "/resources/";
    }

    public static String getCSSPath() {

        return getResourcesPath() + "default.css";
    }

    public static String getImagesPath() {

        return getResourcesPath() + "images/" + ResourceConstants.getScreenDPI(ctx) + "/";
    }

    public static String getImagesPathNoDpi() {

        return getResourcesPath() + "images/";
    }

    public static NinePatchDrawable loadNinePatchFromFilesystem(String filename) {

        if(ninePatchHashMaps == null)
            ninePatchHashMaps = new ArrayList<HashMap<String, NinePatchDrawable>>();

        // check if we already have this filename so we can reuse it
        for (int i = 0; i < ninePatchHashMaps.size(); i++) {
            HashMap<String, NinePatchDrawable> row = ninePatchHashMaps.get(i);

            if(row.containsKey(filename))
                return row.get(filename);
        }

        NinePatchDrawable patchy = null;
        try {
            BitmapFactory.Options options = new BitmapFactory.Options();
            options.inPreferredConfig = Bitmap.Config.ARGB_8888;
            Bitmap bitmap = BitmapFactory.decodeFile(filename, options);
            byte[] chunk = bitmap.getNinePatchChunk();
            boolean result = NinePatch.isNinePatchChunk(chunk);
            if (result)
                patchy = new NinePatchDrawable(bitmap, chunk, new Rect(), null);
        } catch (Exception e){
            Helper.logError("NinePatchLoading",e);
        }

        if(patchy != null) {

            HashMap<String, NinePatchDrawable> drawableImage = new HashMap<String, NinePatchDrawable>();
            drawableImage.put(filename, patchy);
            ninePatchHashMaps.add(drawableImage);
        }

        return patchy;

    }

    public static Drawable loadImageFromFilesystem(String filename) {

        if(imagesHashMaps == null)
            imagesHashMaps = new ArrayList<HashMap<String, Drawable>>();

        // check if we already have this filename so we can reuse it
        for (int i = 0; i < imagesHashMaps.size(); i++) {
            HashMap<String, Drawable> row = imagesHashMaps.get(i);

            if(row.containsKey(filename))
                return row.get(filename);
        }

        Drawable image = null;
        try {
            BitmapFactory.Options options = new BitmapFactory.Options();
            options.inPreferredConfig = Bitmap.Config.ARGB_8888;
            Bitmap bitmap = BitmapFactory.decodeFile(filename, options);

            if(bitmap == null)
                bitmap = BitmapFactory.decodeFile(filename.replace(ResourceConstants.getScreenDPI(ctx) + "/", ""), options);

            image = new BitmapDrawable(bitmap);
        } catch (Exception e){
            Helper.logError("ImageLoadingError",e);
        }

        if(image != null) {
            HashMap<String, Drawable> drawableImage = new HashMap<String, Drawable>();
            drawableImage.put(filename, image);
            imagesHashMaps.add(drawableImage);
        }

        return image;
    }

}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!