创建可下载的自定义主题,并在运行时应用它 [英] Create downloadable custom theme and apply it during run time

查看:151
本文介绍了创建可下载的自定义主题,并在运行时应用它的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我在做一个Android应用程序,需要允许客户以维持他们的服务器资源,包括字符串,可绘制等。

I'm making an Android app that needs to allow client to maintain the resources from their server which would include strings, drawables etc.

我已经建立了一个机制为下载所有这些文件的zip文件,而且他们能够改变字符串pretty的方便,我还建立了一个机制,允许客户端更改背景颜色为UI控件,改变宽度,高度等,但我有一种感觉,必须有一个更好的方式来创造这一切。

I've already created a mechanism for downloading a zip file with all these files, and they're able to change strings pretty easy, I've also created a mechanism that allows the client to change bg color for UI controls, to change width, height etc but I have a feeling that there must be a better way to create all this.

所以,我认为,真正的问题是:

So I believe the real question is:

什么是创建一个自定义主题,在服务器上进行部署,使应用程序下载,并将其应用到应用程序之后的最佳做法是什么?

What's the best practice to create a custom theme, deploy it on server, make the app download it, and apply it to app afterwards?

我知道如何创建自定义主题,以及如何与应用程序部署,以及如何将其运行过程中应用,但这里的问题是,资源是pre-编译的,一旦你创建APK有没有办法开发者改变他们这将需要以添加新的主题/可绘制/风格/串。

I know how to create custom theme and how to deploy it with the app, and how to apply it during runtime, but the problem here is that resources are pre-compiled and once you create APK there's no way for developer to change them which would be required in order to add new themes/drawables/styles/strings.

我是否需要创建一个自定义机制,这一切(加载图像,样式,字符串等从文件系统),并将它们在运行时创建自己的控件应用,会做,在构造函数,例如或有方法正确做到这一点:)? (如何Swiftkey做到这一点所有的键盘主题,以及如何做类似的应用程序做到这一点,允许用户下载主题,之后申请的话)?

Do I need to create a custom mechanism for all this (loading images, styles, strings etc from the file system) and to apply them during runtime by creating my own controls that would do that in constructor for example or is there a way to do this properly :)? ( how does Swiftkey do this with all the keyboard themes, and how do similar apps do it allowing the users to download theme and apply it after that )?

我很抱歉,如果我没有看到类似的问题,我真的试图找到在近2天回答自己,但我没能找到什么有用的东西,所以这是我最后的机会得到一个建设性的答案:) 。

I'm sorry if I didn't see similar question, I really tried to find an answer myself during past 2 days, but I failed to find anything useful, so this is my last chance to get a constructive answer :).

最接近的解决方案,我需要是这样的回答:<一href="http://stackoverflow.com/questions/22252678/changing-app-theme-at-runtime-using-using-external-theme-file">Changing应用主题采用与外部的主题文件运行时间,但我已经做了的功能,我知道我可以改变颜色那样的,但问题是,我希望能够改变的事情就像边界上按钮pressed状态等需要的资源比简单的颜色值等:(

The closest to solution i need was this answer: Changing app theme at runtime using using external theme file but I've already made that functionality, and i know i can change colors like that, but the problem is that i would like to be able to change things like borders, on button pressed state etc that require resources other than simple color value :(.

感谢堆!

P.S。我也看到了有关的扩展名的文件,以便是什么我需要考虑,同时考虑这一点,或者我需要到别处?与OBB文件的问题是,它们必须被部署在PlayStore这不是完美的客户,因为他们需要通过jobb收拾它,并把它部署到PlayStore这是太技术了他们,所以他们会preFER创建一个zip文件,把它放在服务器,应用程序应该做休息​​。)

P.S. I've also read about the extension files so is that something i need to consider while thinking about this, or do i need to look elsewhere? The problem with obb files is that they must be deployed over PlayStore and that's not "perfect" for the client because they need to pack it by using jobb, and to deploy it to PlayStore which is too technical for them, so they would prefer creating a zip file, putting it on server, and the app should do the rest :).

推荐答案

我终于决定通过自己的系统来处理可绘,字符串等所以现在我必须解决这个所谓的ResourceManager的自定义类处理是什么需要被loadad以及如何和主题分布作为其应用程序下载,提取物和更高版本使用的zip文件。

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.

我把它们放在压缩文件之前,编译9片图像由我自己,我这样做,使用ABRC从这里开始:的 http://forum.xda-developers.com/showthread.php?t=785012

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

我还创建了递归去通过自定义文件夹,一个简单的bash脚本,并编译所有九个补丁图像ABRC。

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

我还创建了一个简单的辅助,在ResourceManager的支票,并告诉我的屏幕像素密度,所以我可以正常支持华电国际,xhdpi等密度影像,最后我不每当我需要重新创建图像他们,我将它们保存在HashMap中的静态列表,这样我可以重用那些我已经创建,这样,我希望prevent浪费太多手机的内存。)

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.

干杯!

============ ============编辑

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

下面是类我最后写了这个目的(它下载的文件,检查它的版本,加载字符串从JSON文件,而不是的strings.xml等)

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;
    }

}

这篇关于创建可下载的自定义主题,并在运行时应用它的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆