图片是应用里面最常用的资源,在canvas里面使用需要将其解析成Bitmap的方式(例如倒影效果)。这里给出几个可能会有的场景,探讨一下如何使用:
- 应用主题转化:当某个应用希望换掉整个生命周期中的一些背景图片,例如从卡通主题转化到草原主题,这样会涉及到Button、Tab、Layout等各式各样图片的替换。
- 动画播放:例如关机动画、闹铃提醒(当然简单的动作除外)
转载请注明http://ishelf.iteye.com/blog/1032563
场景一中,可以通过apk升级来实现,也可以通过调用外部资源来实现。这两种方式比较简单,问题也就显而易见。第一种apk升级很烦人,至少我自己都懒得去。第二种,外部资源是简单,不过图片的解析速度一定会比从apk中调用drawable资源要慢好几倍,对于配置很好的机器这个速度不是问题,可是中下的机器可就够受了(最近试了试中兴的平板,和三星的对比太鲜明了)。这两个的前提都是对图片显示要求不是非常的很高,例如图片预览
在场景二中,需要考虑到定制的问题。你给国内和国外的关机动画不能一样吧,给联通和给电信定制的不能一样吧(这个也根据需求而定,不排除都一样的可能)。这样当需求变化时,难道你的代码也要跟着变化么? 这样的话真给Java显眼了,O(∩_∩)O。 其实在场景一中的主题替换也是一个定制的问题。
如何能在保证即不修改代码又能保证图片解析速度尽可能快的前提下,做到图片资源的定制是接下来要讨论的问题。首先看代码
orginalbm = BitmapFactory.decodeFile(Path); //解析源图片文件,根据需要可以放在任何地方。 orginalbm = BitmapFactory.decodeResource(resources,source_id); //解析apk中的资源图片。可以放置到一个不包含任何Java文件的apk包中去。
第二种方式一定是比第一种方式速度快(大家有什么异议可以讨论一下)。接下来就是如何用apk解析的方式来动态的加载外部的图片资源(也可以包括Layout,anim和xml资源)。
第一步就是解决如何解析apk包,并将资源加载进来。这要分两种情况:一是在应用中使用,而是在framework框架中。
这样也就有两种方式,首先介绍第一种,直接看代码
try { // apk包的文件路径 // 这是一个Package 解释器, 是隐藏的 // 构造函数的参数只有一个, apk文件的路径 // PackageParser packageParser = new PackageParser(apkPath); Class pkgParserCls = Class.forName(PATH_PackageParser); Class[] typeArgs = new Class[1]; typeArgs[0] = String.class; Constructor pkgParserCt = pkgParserCls.getConstructor(typeArgs); Object[] valueArgs = new Object[1]; valueArgs[0] = apkPath; Object pkgParser = pkgParserCt.newInstance(valueArgs); Log.d("ANDROID_LAB", "pkgParser:" + pkgParser.toString()); // 这个是与显示有关的, 里面涉及到一些像素显示等等, 我们使用默认的情况 DisplayMetrics metrics = new DisplayMetrics(); metrics.setToDefaults(); // PackageParser.Package mPkgInfo = packageParser.parsePackage(new // File(apkPath), apkPath, // metrics, 0); typeArgs = new Class[4]; typeArgs[0] = File.class; typeArgs[1] = String.class; typeArgs[2] = DisplayMetrics.class; typeArgs[3] = Integer.TYPE; Method pkgParser_parsePackageMtd = pkgParserCls.getDeclaredMethod( "parsePackage", typeArgs); valueArgs = new Object[4]; valueArgs[0] = new File(apkPath); valueArgs[1] = apkPath; valueArgs[2] = metrics; valueArgs[3] = 0; Object pkgParserPkg = pkgParser_parsePackageMtd.invoke(pkgParser, valueArgs); // 应用程序信息包, 这个公开的, 不过有些函数, 变量没公开 // ApplicationInfo info = mPkgInfo.applicationInfo; Field appInfoFld = pkgParserPkg.getClass().getDeclaredField( "applicationInfo"); ApplicationInfo info = (ApplicationInfo) appInfoFld .get(pkgParserPkg); // uid 输出为"-1",原因是未安装,系统未分配其Uid。 Log.d("ANDROID_LAB", "pkg:" + info.packageName + " uid=" + info.uid); Class assetMagCls = Class.forName(PATH_AssetManager); Constructor assetMagCt = assetMagCls.getConstructor((Class[]) null); Object assetMag = assetMagCt.newInstance((Object[]) null); typeArgs = new Class[1]; typeArgs[0] = String.class; Method assetMag_addAssetPathMtd = assetMagCls.getDeclaredMethod( "addAssetPath", typeArgs); valueArgs = new Object[1]; valueArgs[0] = apkPath; assetMag_addAssetPathMtd.invoke(assetMag, valueArgs); Resources res = getResources(); typeArgs = new Class[3]; typeArgs[0] = assetMag.getClass(); typeArgs[1] = res.getDisplayMetrics().getClass(); typeArgs[2] = res.getConfiguration().getClass(); Constructor resCt = Resources.class.getConstructor(typeArgs); valueArgs = new Object[3]; valueArgs[0] = assetMag; valueArgs[1] = res.getDisplayMetrics(); valueArgs[2] = res.getConfiguration(); res = (Resources) resCt.newInstance(valueArgs);
代码是参考来源:http://blog.csdn.net/sodino/archive/2011/03/01/6215224.aspx 。这里主要使用了Java反射机制,得到了res(Resources)(这个博主挺nb的,O(∩_∩)O)。 Resources类是实现apk资源解码的核心,具体类的作用这里就不介绍了(不懂得看源码去)。
如果是在framework中使用就简单了,看代码
AssetManager assets = new AssetManager(); String resDir = "/sdcard/extern-sd/xxx.apk"; if (assets.addAssetPath(resDir) == 0) { Log.e(TAG, "parse failed"); return; } resources = new Resources(assets, null, null, null);
这样很简单就得到了Resources类。
第二步是解决如何得到drawble资源的id值。这里我们根据需要为每个drawable添加相应的参数,例如在哪个类里面使用、名称等各种信息。
先看代码:
int all_view_id = resources.getIdentifier("image_ids", "xml", "com.xxx.resource"); XmlResourceParser parser = resources.getXml(all_view_id); try { int type; while ((type = parser.next()) != XmlPullParser.START_TAG && type != XmlPullParser.END_DOCUMENT) {;} if (type != XmlPullParser.START_TAG) { Log.e(TAG, "No start tag found in package manager settings"); } int outerDepth = parser.getDepth(); while ((type = parser.next()) != XmlPullParser.END_DOCUMENT && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { continue; } String tagName = parser.getName(); } } catch (XmlPullParserException e) { Log.e(TAG, "Error reading package manager settings", e); } catch (java.io.IOException e) { Log.e(TAG, "Error reading package manager settings", e); }
通过getIdentifier可以得到需要的xml文件id值。
resources.getIdentifier("image_ids", "layout","com.xxx.resource");
//字段“layout”,表示资源文件在layout文件夹中
//字段“com.xxx.resource”表示该apk的包名(packagename)
需要注意一点在自己配置的xml文件中,要使用android:drawable这样的属性名定义,否则不会编译成int类型的id号。
最后一步就是如何调用通过id调用图片。
BitmapFactory.decodeResource(resources, source_id);
resources就是第一步得到的Resources类,id则是第二部得到的id号。需要注意一点就是变量的作用域。
经过上面三步可以很容易的实现使用apk的方式调用外部的图片资源。
前面说的都是用的Interface Builder来编辑.xib文件来给窗口添加各种控件以及给控件绑定数据(IBOutlet)、关联事件响应函数(IBAction)。
这章学习的是动态的添加view,不使用Interface Builder。这里用label和button示例:
找到新建工程XXXViewController.m的-(void)loadView方法,去掉注释并添加如下代码
- (void)loadView {
//创建一个UIView 对象
UIView *view =
[[UIView alloc] initWithFrame:[UIScreen mainScreen].applicationFrame];
view.backgroundColor = [UIColor lightGrayColor];
//创建一个label view
CGRect frame = CGRectMake(10, 15, 300, 20);
UILabel *label = [[UILabel alloc] initWithFrame:frame];
label.textAlignment = UITextAlignmentCenter;
label.backgroundColor = [UIColor clearColor];
label.font = [UIFont fontWithName:@”Verdana” size:20];
label.text = @”label test”;
label.tag = 1000;
//创建一个按钮view
frame = CGRectMake(10, 30, 300, 50);
UIButton *button = [UIButton buttonWithType:UIButtonTypeRoundedRect];
button.frame = frame;
[button setTitle:@”button test” forState:UIControlStateNormal];
button.backgroundColor = [UIColor clearColor];
button.tag = 2000;
/*下面这个调用用C++的格式来看就是button->addTarget(this->action, @selector(buttonClicked:), UIControlEventTouchUpInside);
中间的action:以及forControlEvent:实际上都是函数签名的一部分。@selector(buttonClicked:) 相当于函数指针(一个冒号表明函数有一个参数),这里指向的是buttonClicked函数
也就是下面定义的按钮响应函数*/
[button addTarget:self action:@selector(buttonClicked:) forControlEvent:UIControlEventTouchUpInside];
[view addSubview:label];
[view addSubview:button];
self.view = view;
[label release];
}
在这个文件中添加按钮响应函数
-(IBAtion) buttonClicked:(id)sender {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@”Action invoked”
message:@”button clicked”
delegate:self
cancelButtonTitle:”@ok”
otherButtonTitles:nil];
[alert show];
[alert release];
}
label的矩形区域是CGRectMake(10, 15, 300, 20); 既左上角坐标是10,15宽度高度分别是300, 20.
button的矩形区域的左上角坐标是10, 30 ,它们有重叠的地方。
这里遮挡是后加到view里面去的遮挡先加进去的。所以button遮挡了label。可以通过
[view exchangeSubviewAtIndex:1 withSubviewAtIndex:0];
来修改遮挡。我的理解是view按照控件加进去的顺给了个index,这个index从0开始递增。显示的时候index数值较大控件遮挡数值较小的。 上面这个函数交换了最先加进去的两个控件(实际上只有这两个)的index
为什么要做这些,等你需要的时候你就知道了
private void deleteOldestFile(File directory) { File[] files = directory.listFiles(); Arrays.sort(files, new Comparator<File>() { @Override public int compare(File f1, File f2) { return Long.valueOf(f1.lastModified()).compareTo(f2.lastModified()); }}); files[0].delete(); } private static long dirSize(File dir) { long result = 0; File[] fileList = dir.listFiles(); for(int i = 0; i < fileList.length; i++) { if(fileList[i].isDirectory()) { result += dirSize(fileList [i]); } else { // Sum the file size in bytes result += fileList[i].length(); } } return result; }
File dirlist = new File(Environment.getExternalStorageDirectory() + "/VideoList"); if(!(dirlist.exists())) dirlist.mkdir(); Long directorySize = dirSize(dirlist); if (directorySize > 1073741824) // this is 1GB in bytes { deleteOldestFile(dirlist); } File TempFile = new File(Environment.getExternalStorageDirectory() + "/VideoList", dateFormat.format(date) + fileFormat); mediaRecorder.setOutputFile(TempFile.getPath());
或者
while (directorySize > 1073741824) { deleteOldestFile(dirlist); direcotrySize = dirSize(dirlist); }