Android 创建可下载的自定义主题,并在运行时应用它
我正在制作一个Android应用程序,它需要允许客户端维护他们服务器上的资源,包括字符串、绘图等 我已经创建了一种机制来下载包含所有这些文件的zip文件,它们可以很容易地更改字符串,我还创建了一种机制,允许客户端更改UI控件的bg颜色,更改宽度、高度等,但我觉得必须有更好的方法来创建所有这些 因此,我认为真正的问题是: 创建自定义主题、将其部署到服务器上、让应用程序下载并随后应用到应用程序的最佳实践是什么 我知道如何创建自定义主题,如何在应用程序中部署它,以及如何在运行时应用它,但这里的问题是,资源是预编译的,一旦您创建了APK,开发人员就无法更改它们,这是添加新主题/绘图表/样式/字符串所必需的 我是否需要为所有这些创建一个自定义机制(从文件系统加载图像、样式、字符串等),并在运行时通过创建自己的控件(例如在构造函数中)来应用它们,或者是否有正确的方法:)?(Swiftkey是如何处理所有键盘主题的,类似的应用程序是如何做到的,允许用户下载主题并在之后应用) 很抱歉,如果我没有看到类似的问题,我真的试图在过去的两天内找到一个答案,但我没有找到任何有用的,所以这是我最后一次得到建设性答案的机会:) 我需要的最接近解决方案是这个答案:但我已经实现了这个功能,我知道我可以像那样更改颜色,但问题是我希望能够更改边框、按钮按下状态等需要资源而不是简单颜色值的内容:( 谢谢你Android 创建可下载的自定义主题,并在运行时应用它,android,android-theme,Android,Android Theme,我正在制作一个Android应用程序,它需要允许客户端维护他们服务器上的资源,包括字符串、绘图等 我已经创建了一种机制来下载包含所有这些文件的zip文件,它们可以很容易地更改字符串,我还创建了一种机制,允许客户端更改UI控件的bg颜色,更改宽度、高度等,但我觉得必须有更好的方法来创建所有这些 因此,我认为真正的问题是: 创建自定义主题、将其部署到服务器上、让应用程序下载并随后应用到应用程序的最佳实践是什么 我知道如何创建自定义主题,如何在应用程序中部署它,以及如何在运行时应用它,但这里的问题是,
我还阅读了扩展文件,这是我在思考这个问题时需要考虑的问题,还是我需要去别处看看?OBB文件的问题是,它们必须部署在PoalSt铺上,而不是“完美”。对于客户端,因为他们需要使用jobb进行打包,并将其部署到PlayStore,这对他们来说太技术化了,所以他们更喜欢创建一个zip文件,将其放在服务器上,应用程序应该完成其余的工作:)。我最终决定通过制作一个用于处理可拖动文件的自定义系统来解决这个问题,所以现在我有了一个名为“ResourceManager”的自定义类,它处理需要加载的内容以及加载方式,主题作为zip文件分发,应用程序下载、提取和以后使用 在将它们放入zip文件之前,我必须自己编译九个补丁图像,我使用“abrc”从这里开始: 我还创建了一个简单的bash脚本,它递归地遍历自定义文件夹,并使用abrc编译所有九个补丁映像 我还在ResourceManager中创建了一个简单的助手,它可以检查并告诉我屏幕密度,这样我就可以正常支持hdpi、xhdpi等密度的图像,最后我不会在每次需要时都重新创建图像,我将它们保存在HashMap的静态列表中,这样我就可以重用我已经创建的,这样我希望避免浪费太多手机内存:) 好的,这些都是简短的文字,如果有任何人有任何问题,请让我知道,我很高兴与任何人分享这一经验 干杯 ================编辑============ 下面是我为此目的编写的类(它下载文件,检查其版本,从JSON文件而不是strings.xml加载字符串等) 注意:这不是一个完整的类,因此缺少一些部分,但我认为这足以让我了解如何解决这一切:)
/**
*bojank于2014年7月28日创建。
*类,该类处理从服务器下载的自定义资源
*/
公共类资源管理器{
//应用程序中的九个签名列表
私有静态ArrayList ninePatchHashMaps;
私有静态ArrayList imagesHashMaps;
私有静态图像加载器;
//方法的上下文
公共静态上下文;
//包含所有字符串的JSONObject
私有静态JSONObject joString;
//具有所有样式的JSONObject
私有静态JSONObject joStyles;
//包含当前活动语言代码的字符串
私有静态字符串语言;
私有静态字符串sdcardPath;
//防止创建类实例的Private constructor
私人资源经理(){
}
/**
*方法,该方法返回给定键的已翻译字符串
*
*@param键字符串
*@返回字符串
*/
公共静态字符串getString(字符串模块,字符串键){
字符串输出=”;//字符串.格式(“[%s-%s]”,模块,键);
试一试{
if(getStringsFile()!=null&&getStringsFile().getJSONObject(模块).has(键))
output=getStringsFile().getJSONObject(模块).getString(键);
}捕获(例外e){
//如果新添加的语言缺少正确的json文件,则强制使用某些默认语言
currentLanguage=“en US”;
saveLocale(currentLanguage,ctx);
logError(“ErrorFetchingString”,e);
}
返回输出;
}
/**
*方法返回带有字符串资源的JSONObject
*@return-JSONObject
*@JSONException
*/
公共静态JSONObject getStringsFile()抛出JSONException{
if(joString==null){
字符串stringFileName=getResourcesPath()+“languages/”+getCurrentLanguage()+“/values.json”;
String languageFile=Helper.readJsonFile(stringFileName);
if(languageFile!=null){
joString=newjsonobject(Helper.readJsonFile(stringFileName));
}否则{
/**
* 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;
}
}