文件包是以一个.bundle
为扩展名的文件包。和普通文件夹相比它们有 2
个主要特点:
1.
Cocoa Touch
提供了一个界面,通过这个界面你可以进入相应的文件包简单获取里边已有资源。
2.如果在 Xcode 左侧导航窗口增加一个文件包,任何文件增加或者移出文件包将分别立即出现或者消失在 Xcode 导航窗口。相反,如果你在 Xcode 导航增加了一个文件夹,然后再去删除磁盘中这个文件夹中的文件,在没有 Xcode 的帮助下,这个文件将会变成红色并且不能立即删除。文件包非常有用,特别是你想在文件夹中通过 Finder 而非 Xcode 手动增加文件。
每个 IOS 应用至少有一个文件包,叫做主文件包。主文件包包含你的应用软件中的二进制代码和其他在应用软件中使用的资源,比如影像、声音、HTML 文件和其他相关文件。换言之,主文件包包含了你提交给APPStore 或者发布到自己组织内部的最终二进制文件中的资源。这些资源随后可以用 NSBundle 类的mainBundle 类的方法动态加载。
每个App在提交AppStore时他的主文件夹包在磁盘中一个水平层次结构,这就意味着所有装在App文件包的文件放在主文件夹包的根文件夹内。也就是说,主文件包有一个唯一的文件夹—根文件夹,所有文件和资源都放在这个文件夹里。即使你在磁盘中有一个只有几张图片的文件夹,无论拖进或者拖出 Xcode, 这个文件夹中的文件都将放在主文件夹里,而不是在这个文件夹自己内部。
从主文件加载内容时常用的方法是:
[NSBundle mainBundle]pathForResource:@"0" ofType:@"jpg"
如果从自定义的包中加载内容,则使用下面的方法:
//从文件包加载数据 NSString *bundlePath=[[NSBundle mainBundle]pathForResource:@"image" ofType:@"bundle"];//获取bundle路径,我的bundle包名为image.bundle if ([bundlePath length]>0) {//判断路径是否获取成功 //定义bundle,获取自定义包 NSBundle *imageBundle=[NSBundle bundleWithPath:bundlePath]; if (imageBundle!=nil) { NSString *path=[imageBundle pathForResource:@"610" ofType:@"jpg"inDirectory:@"images.bundles"]; if ([path length]>0) { UIImage *image=[UIImage imageWithContentsOfFile:path]; if (image!=nil) { NSLog(@"从文件包中加载成功!"); } else NSLog(@"保存失败"); } else NSLog(@"路径不存在"); } else NSLog(@"bundle不存在"); }
XML文件中给Button控件设置android:background属性或者在代码里直接调用View.setBackgroundDrawable函数设置背景,这些恐怕每个android开发人员都干过。的确,为了让我们的应用表现的更加人性化,这些控件的状态变化是必不可少的。可是,对于为什么这样用就起作用,不知道大家分析过没有,最近我分析了一下,现在和大家共享一下。
一,从XML中解析出StateListDrawable的过程。见下图:
图1 从XML中解析出StateListDrawable的过程
从图1中,我们可以看到一个完整的从XML中解析出StateListDrawable的过程,觉得图已经画的比较清楚了,所以就不在多说了。
二,View的DrawableState的设置过程
图2 View的DrawableState的设置过程
图2有些地方感觉还要多说两句,
1,onCreateDrawableState函数里有完整当前视图控件状态判断的方法,具体代码如下:
int viewStateIndex = (((privateFlags & PRESSED) != 0) ? 1 : 0); // 下压状态判断。这个状态的设置一般是在setPressed函数里进行的。 viewStateIndex = (viewStateIndex << 1) + (((mViewFlags & ENABLED_MASK) == ENABLED) ? 1 : 0); // 使能状态判断。这个状态的设置一般是setEnabled函数里进行的。 viewStateIndex = (viewStateIndex << 1) + (isFocused() ? 1 : 0); // 焦点状态判断。这个状态的设置一般是requestFocus函数里进行的。 viewStateIndex = (viewStateIndex << 1) + (((privateFlags & SELECTED) != 0) ? 1 : 0); // 选择状态判断。这个状态的设置一般是setSelected函数里进行的。 final boolean hasWindowFocus = hasWindowFocus(); viewStateIndex = (viewStateIndex << 1) + (hasWindowFocus ? 1 : 0); // 视图所在窗口焦点状态判断。 drawableState = VIEW_STATE_SETS[viewStateIndex];
2,一个窗口中可以有多个视图处于selected状态,但是只能有一个处于focused状态;
3,视图处于focused状态时,不一定处于window_focused状态。典型的情况是,窗口初次创建时,ViewRoot会执行performTraversals(),执行中根视图会执行requestFocus()操作,此时一般会有一个视图处于focused状态;但是这个时候视图并不一定是处于window_focused状态,因为window_focused是由系统发出WINDOW_FOCUS_CHANGED消息时,让ViewRoot执行windowFocusChanged()来改变的。这些可以通过重写View的requestFocus()和onWindowFocusChanged()来证明;
4,数组View.VIEW_STATE_SETS是一个非常重要的成员变量,它记录View的所有可能状态。View.VIEW_STATE_SETS这个数组里的部分状态组合是用View.stateSetUnion函数计算出来的。考虑到这个数组成员在后面StateSet.stateSetMatches函数里将被使用来判断状态是否匹配,觉得有必要具体说一下,下面是它的实现代码:
private static int[] stateSetUnion(final int[] stateSet1, final int[] stateSet2) { final int stateSet1Length = stateSet1.length; final int stateSet2Length = stateSet2.length; final int[] newSet = new int[stateSet1Length + stateSet2Length]; // 新状态集合newSet大小是stateSet1和stateSet2大小之和,默认值都是0 int k = 0; int i = 0; int j = 0; // This is a merge of the two input state sets and assumes that the // input sets are sorted by the order imposed by ViewDrawableStates. for (int viewState : R.styleable.ViewDrawableStates) { if (i < stateSet1Length && stateSet1[i] == viewState) { newSet[k++] = viewState; // 把stateSet1里的状态值赋给newSet i++; } else if (j < stateSet2Length && stateSet2[j] == viewState) { newSet[k++] = viewState; // 把stateSet2里的状态值赋给newSet j++; } if (k > 1) { assert(newSet[k - 1] > newSet[k - 2]); } } return newSet; }
我们来看一下R.styleable.ViewDrawableStates的定义:
<declare-styleable name="ViewDrawableStates"> <attr name="state_pressed" /> <attr name="state_focused" /> <attr name="state_selected" /> <attr name="state_window_focused" /> <attr name="state_enabled" /> </declare-styleable>
从这里我们看到,其实数组R.styleable.ViewDrawableStates里其实就是R.attr.state_pressed,R.attr.state_focused,R.attr.state_selected ,R.attr.state_window_focused和 R.attr.state_enabled 的集合。用语言总结一下View.stateSetUnion函数的作用,将状态集合stateSet1和stateSet2里的状态值,按照状态值在数组R.styleable.ViewDrawableStates里的先后顺序赋给新状态集合newSet。
5,考虑到完整性和对称性,这里把StateSet.stateSetMatches的代码也列出来,从而让大家清楚的理解状态的比较方法。
public static boolean stateSetMatches(int[] stateSpec, int[] stateSet) { if (stateSet == null) { return (stateSpec == null || isWildCard(stateSpec)); } int stateSpecSize = stateSpec.length; int stateSetSize = stateSet.length; for (int i = 0; i < stateSpecSize; i++) { int stateSpecState = stateSpec[i]; if (stateSpecState == 0) { // We've reached the end of the cases to match against. return true; } final boolean mustMatch; if (stateSpecState > 0) { mustMatch = true; } else { // We use negative values to indicate must-NOT-match states. mustMatch = false; stateSpecState = -stateSpecState; } boolean found = false; for (int j = 0; j < stateSetSize; j++) { final int state = stateSet[j]; if (state == 0) { // We've reached the end of states to match. if (mustMatch) { // We didn't find this must-match state. return false; } else { // Continue checking other must-not-match states. break; } } if (state == stateSpecState) { if (mustMatch) { found = true; // Continue checking other other must-match states. break; } else { // Any match of a must-not-match state returns false. return false; } } } if (mustMatch && !found) { // We've reached the end of states to match and we didn't // find a must-match state. return false; } } return true; }
三,View的DrawableState变化过程。有了上面的基础,这里就非常简单了,我就举一个例子来具体说明一下吧,下图是View.setPressed的时序图:
图3 View.setPressed时序图
把图3和图2结合起来看,就可以清楚地理解状态变化是如何实现的了。
四,相关的一些小技巧
有时候UI可能不给开发人员背景图资源,但是还是希望开发人员做到Button下压时,Button上的文字颜色变暗。这个时候,就可以用这个小技巧来实现了:设置android:textColor属性。具体例子请参见《Android 中设置TextView的颜色setTextColor》
备注:
1,Button是TextView的子类。
2,相关代码实现在TextView.drawableStateChanged函数里
protected void drawableStateChanged() { super.drawableStateChanged(); // background在这里起作用了 if (mTextColor != null && mTextColor.isStateful() || (mHintTextColor != null && mHintTextColor.isStateful()) || (mLinkTextColor != null && mLinkTextColor.isStateful())) { updateTextColors(); // 就是这里textColor开始起作用了 } final Drawables dr = mDrawables; if (dr != null) { int[] state = getDrawableState(); if (dr.mDrawableTop != null && dr.mDrawableTop.isStateful()) { dr.mDrawableTop.setState(state); } if (dr.mDrawableBottom != null && dr.mDrawableBottom.isStateful()) { dr.mDrawableBottom.setState(state); } if (dr.mDrawableLeft != null && dr.mDrawableLeft.isStateful()) { dr.mDrawableLeft.setState(state); } if (dr.mDrawableRight != null && dr.mDrawableRight.isStateful()) { dr.mDrawableRight.setState(state); } } }
转载说明出处:(谢谢)
http://blog.csdn.net/a21064346/article/details/8135794
点击打开链接
补充一下 以前转载的一篇文章,里面是别人的东西,发现他解释得不是很清楚。
补充的文章地址为:
Navigation backBarButtonItem 设置
http://blog.csdn.net/a21064346/article/details/7744391
经过查找 官方文档,里面指出
根据苹果官方指出:backbarbuttonItem不能定义customview,所以,只能贴图或者,让leftBarButtonItem变成自定义返回按钮,自己写个方法进行[self.navigationController pop当前Item
那么这段话的意思就是说,你不能 改变backbarbutton
//定义返回按钮 UIButton *backbutton = [[[UIButton alloc] initWithFrame:CGRectMake(0, 0, 53, 31)] autorelease]; [backbutton setTitle:NSLocalizedString(@"Ba1ck",nil) forState:UIControlStateNormal]; [backbutton.titleLabel setShadowColor:LIGHT_ORANGE_SHADDOW]; [backbutton.titleLabel setShadowOffset:CGSizeMake(0, 1)]; [backbutton.titleLabel setTextColor:[UIColor whiteColor]]; [backbutton.titleLabel setFont:[UIFont fontWithName:@"Baskerville-SemiBoldItalic" size:14]]; [backbutton.titleLabel setBackgroundColor:[UIColor clearColor]]; [backbutton.titleLabel setTextAlignment:UITextAlignmentCenter]; [backbutton addTarget:self action:@selector(NavPopControllerSelf) forControlEvents:UIControlEventTouchUpInside]; UIImage *img = [UIImage imageNamed:@"ILSBCImage.bundle/Edit_Normal.png"]; [backbutton setBackgroundImage:[img stretchableImageWithLeftCapWidth:20 topCapHeight:15] forState:UIControlStateNormal]; UIImage *imgSelected = [UIImage imageNamed:@"ILSBCImage.bundle/Edit_Selected.png"]; [backbutton setBackgroundImage:[imgSelected stretchableImageWithLeftCapWidth:20 topCapHeight:15] forState:UIControlStateHighlighted]; UIBarButtonItem *backItem = [[[UIBarButtonItem alloc] initWithCustomView:backbutton] autorelease]; self.navigationItem.leftBarButtonItem =backItem; //生成 三个导入数据的button [self newImportButton]; // self.navigationItem.backBarButtonItem = backItem;
任何 传递一个 customView给backBarButtonItem 的操作,都会被取消,而使用系统默认的back按钮。
但是下面的形式,又是可行的。
其中,style 属性 并没有去实现,barButtonItem childView并没有发生改变。只是单纯的名字换了一下。
//生效 self.navigationItem.backBarButtonItem = [[[UIBarButtonItem alloc] initWithTitle:@"custom" style:UIBarButtonItemStylePlain target:self action:nil] autorelease]; [self.navigationItem.backBarButtonItem setImage:[UIImage imageNamed:@"icon57.png"]]; [self.navigationController.navigationBar setTintColor:[UIColor redColor]]; //不生效 [self.navigationItem.backBarButtonItem setBackButtonBackgroundImage:[UIImage imageNamed:@"icon57.png"] forState:UIControlStateNormal barMetrics:UIBarMetricsDefault];
个人建议:
还是不要去设置backBarButtonItem,利用leftBarButtonItem 代替back,因为 leftBarButtonItem 没有限制。