Intereting Posts
Android Custom EditText не показывает курсор в ICS Как я могу обновить ActionBar, когда onPrepareOptionsMenu переключил записи меню? Как программно копировать текст в приложении для Android? Как реализовать шаблон «Загрузка изображений» (непрозрачность, экспозиция и насыщенность) из новых руководств по дизайну материалов Как начать общий переход элементов с помощью фрагментов? Прокручиваемый макет в Android CursorAdapter для деятельности GoogleMaps Ошибка «Нет равных сертификатов» в Android 2.3, но НЕ в 4 Является пешеходным переходом быстрее, чем кокон Как добавить левую выделенную в Spinner View в Android Android: анимация подрезается родительским представлением Как напечатать длинный текст? Как удалить активность из списка последних приложений? Android: не удается заставить javascript работать в WebView даже с помощью setJavaScriptEnabled (true) Может ли один эмулятор отправить SMS на себя

Создавайте загружаемую настраиваемую тему и применяйте ее во время выполнения

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

Я уже создал механизм для загрузки zip-файла со всеми этими файлами, и они могут легко изменять строки, я также создал механизм, который позволяет клиенту изменять цвет bg для элементов управления пользовательского интерфейса, изменять ширину , Высота и т. Д., Но я чувствую, что должен быть лучший способ создать все это.

Поэтому я считаю, что реальный вопрос:

Какова наилучшая практика создания настраиваемой темы, ее развертывания на сервере, загрузки приложения и последующего применения ее к приложению?

Я знаю, как создавать настраиваемые темы и как их развернуть с помощью приложения, и как применять его во время выполнения, но проблема в том, что ресурсы предварительно скомпилированы, и как только вы создаете APK, разработчик не может изменить их, что Требуется для добавления новых тем / чертежей / стилей / строк.

Нужно ли создавать пользовательский механизм для всего этого (загрузка изображений, стилей, строк и т. Д. Из файловой системы) и их применение во время выполнения, создавая мои собственные элементы управления, которые будут делать это в конструкторе, например, или есть способ сделать Это правильно :)? (Как Swiftkey делает это со всеми темами клавиатуры и как делают подобные приложения, позволяя пользователям загружать тему и применять ее после этого)?

Извините, если я не видел подобного вопроса, я действительно пытался найти ответ сам за последние 2 дня, но мне не удалось найти ничего полезного, так что это мой последний шанс получить конструктивный ответ :).

Самый близкий к решению, который мне нужен, – это ответ: изменение темы приложения во время выполнения с использованием внешнего файла темы, но я уже сделал эту функциональность, и я знаю, что могу менять цвета, но проблема в том, что я хотел бы быть в состоянии Изменять такие вещи, как границы, на нажатие кнопки и т. Д., Которые требуют ресурсов, отличных от простого значения цвета :(.

Спасибо, кучи!

PS Я также читал о файлах расширений, так что это то, что мне нужно учитывать, думая об этом, или мне нужно искать в другом месте? Проблема с файлами obb заключается в том, что они должны быть развернуты поверх PlayStore, и это не «идеально» для клиента, потому что они должны упаковывать его с помощью jobb и разворачивать его в PlayStore, что является слишком техническим для них, поэтому они предпочли бы создавать Zip-файл, помещая его на сервер, а приложение должно делать все остальное :).

Solutions Collecting From Web of "Создавайте загружаемую настраиваемую тему и применяйте ее во время выполнения"

Я, наконец, решил решить эту проблему, создав специальную систему для обработки чертежей, строк и т. Д., Так что теперь у меня есть пользовательский класс под названием «ResourceManager», который обрабатывает то, что нужно загрузить и как, и темы распространяются как zip-файл, приложение Загрузок, выписок и последующего использования.

Мне пришлось скомпилировать девять патч-образов, прежде чем положить их в zip-файл, и я сделал это с помощью «abrc» отсюда: http://forum.xda-developers.com/showthread.php?t=785012

Я также создал простой скрипт bash, который рекурсивно перейдет в пользовательскую папку и скомпилирует все девять патч-изображений с помощью abrc.

Я также создал простой помощник в ResourceManager, который проверяет и сообщает мне плотность экрана, поэтому я могу нормально поддерживать изображения в hdpi, xhdpi и т. Д. Плотности, и, наконец, я не воссоздаю изображения каждый раз, когда они мне нужны, i Сохраните их в статическом списке HashMap, чтобы я мог повторно использовать те, которые я уже создал, и таким образом я надеюсь избежать потери слишком большого количества памяти телефона :).

ОК, это все в коротких строках, если у кого есть какие-либо вопросы, пожалуйста, дайте мне знать, я буду рад поделиться этим опытом с кем угодно.

Ура!

============ РЕДАКТИРОВАНИЕ ============

Вот класс, который я написал для этой цели (он загружает файл, проверяет его версию, загружает строки из файла JSON, а не strings.xml и т. Д.)

ПРИМЕЧАНИЕ. Это не полный класс, поэтому некоторые части отсутствуют, но я думаю, что этого более чем достаточно, чтобы понять, как я решил все это 🙂

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