一、工程目录结构

src存放java源文件
src

├ cn.eoe.app –存放程序全局性类的包

├ cn.eoe.app.adapter –存放适配器的实现类的包

├ cn.eoe.app.adapter.base –存放适配器基类的包

├ cn.eoe.app.biz –存放DAO类的包

├ cn.eoe.app.config --存放常量,配置和api接口等类的包

├ cn.eoe.app.db –关于sqlite操作相关的类的包

├ cn.eoe.app.db.biz –详细的增删改查类的包,暂时仅有一个类

├ cn.eoe.app.entity –实体类包

├ cn.eoe.app.entity.base –实体类基类包

├ cn.eoe.app.https –网络访问相关类的包

├ cn.eoe.app.indicator –导航相关的类包

├ cn.eoe.app.slidingmenu –滑动菜单相关类包

├ cn.eoe.app.ui –界面相关的包,activity的类

├ cn.eoe.app.ui.base –activity相关的基类包

├ cn.eoe.app.utils –工具类包

├ cn.eoe.app.view –Fragment相关类的包

├ cn.eoe.app.widget –自定义view组件包

├ com.google.zxing.camera –第三方定义,控制摄像头包

├ com.google.zxing.decoding – 二维码图像解码包

├ com.google.zxing.view – 自定义View,控制拍摄取景框和动画等

libs

libs目录存放项目引用的第三方jar包

├ android-support-v4.jar –v4兼容包

├ jackson-all-1.9.2.jar –解析json的包

├ umeng_sdk.jar –友盟的sdk

├ zxing-1.6.jar –二维码处理的包

二、Api接口数据分析

http://http://api.eoe.name/client/top

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
{
"response": {
"date": 1399446965,
"categorys": [
{
"name": "精选教程",
"url": "http://api.eoe.name//client/wiki?k=lists&t=top"
},
{
"name": "精选资讯",
"url": "http://api.eoe.name//client/news?k=lists&t=top"
},
{
"name": "精选博客",
"url": "http://api.eoe.name//client/blog?k=lists&t=top"
}
],
"list": [
{
"name": "精选教程",
"more_url": "http://api.eoe.name//client/news?k=lists&pageNum=2&t=top",
"items": [
{
"id": "17739",
"thumbnail_url": "",
"title": "给准备跳槽的互联网从业人员提个醒",
"time": "1394029410",
"short_content": "  早春三月,又到了一年一度的跳槽高峰。虽然东楼目...",
"detail_url": "http://api.eoe.name/client/news?k=show&id=17739"
},
……
]
},
{
"more_url": "http://api.eoe.name/client/wiki?k=lists&pageNum=2&t=top",
"name": "精选资讯",
"items": [
{
"title": "创建你的第一个Android项目",
"id": "242",
"time": "1353410798",
"detail_url": "http://api.eoe.name/client/wiki?k=show&id=242"
},
……
]
},
{
"name": "精选博客",
"more_url": "http://api.eoe.name//client/blog?k=lists&pageNum=2&t=top",
"items": [
{
"id": "3348",
"name": "过期的白砂糖",
"head_image_url": "http://bbs.eoe.name/uc_server/avatar.php?uid=739935&size=small",
"title": "GitHub上最火的40个Android开源项目(一)",
"time": "1367809230",
"short_content": "GitHub上最火的40个Android开源项目(一)GitHub上最...",
"detail_url": "http://api.eoe.name/client/blog?k=show&id=3348"
},
……
]
}
]
}
}
  • date:日期
  • categorys:类别(分别是精选教程、精选资讯、精选博客)
  • list:文章列表
    • name:名字
    • more_url:更多文章URL
    • items:文章数组

分析下文章数组的数据

  • 精选教程item
    • id:文章的id
    • thumbnail_url:缩略图url
    • title:标题
    • short_content:内容梗概
    • detil_url:文章的url
  • 精选资讯item
    • title:标题
    • time:发表时间
    • detail_url:文章url
    • id:文章id
  • 精选博客
    • id:文章id
    • name:作者名称
    • head_image_url:作者头像
    • title:文章标题
    • time:发表时间
    • short_content:文章梗概
    • detail_url:文章url

http://api.eoe.cn/client/news?k=lists&t=top
资讯推介

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
"response": {
"name": "推荐资讯",
"more_url": "http://api.eoe.cn//client/news?k=lists&pageNum=2&t=top",
"items": [
{
"id": "17739",
"thumbnail_url": "",
"title": "给准备跳槽的互联网从业人员提个醒",
"time": "1394029410",
"short_content": "  早春三月,又到了一年一度的跳槽高峰。虽然东楼目...",
"detail_url": "http://api.eoe.cn/client/news?k=show&id=17739"
},
……
]
}
}
  • 格式基本相同

推介博客
http://api.eoe.cn/client/blog?k=lists&t=top

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{
"response": {
"name": "推荐博客",
"more_url": "http://api.eoe.cn//client/blog?k=lists&pageNum=2&t=top",
"items": [
{
"id": "3348",
"name": "过期的白砂糖",
"head_image_url": "http://www.eoeandroid.com/uc_server/avatar.php?uid=739935&size=small",
"title": "GitHub上最火的40个Android开源项目(一)",
"time": "1367809230",
"short_content": "GitHub上最火的40个Android开源项目(一)GitHub上最...",
"detail_url": "http://api.eoe.cn/client/blog?k=show&id=3348"
},
……
]
}
}

推荐教程
http://api.eoe.cn/client/wiki?k=lists&t=top

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
"response": {
"more_url": "http://api.eoe.cn/client/wiki?k=lists&pageNum=2&t=top",
"name": "推荐教程",
"items": [
{
"title": "创建你的第一个Android项目",
"id": "242",
"time": "1353410798",
"detail_url": "http://api.eoe.cn/client/wiki?k=show&id=242"
},
……
]
}
}

使用Volley

下载Volley源码并build jar包。

1
2
3
4
$ git clone https://android.googlesource.com/platform/frameworks/volley
$ cd volley
$ android update project -p ./
$ ant jar

把生成的jar包引用到项目中去。

介绍

Android-Universal-Image-Loader是一个开源的图片异步加载库,该项目的目的是提供一个可重复使用的图像异步加载、缓存和显示的工具。该库非常强大,国内外有很多有名的App都在使用。

特点

  • 多线程的图像加载
  • 尽可能多的配置选项(线程池,加载器,解析器,内存/磁盘缓存,显示参数等等)
  • 可以添加图片加载监听器
  • 可以自定义显示每一张图片时都带不同参数
  • 支持Widget
  • Android 1.5以上支持

使用方法

1.Android Manifest

1
2
3
4
5
6
7
<manifest>
<uses-permission android:name="android.permission.INTERNET" />
<user-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<application android:name="MyApplication">
...
</application>
</manifest>

2.Application class

1
2
3
4
5
6
7
8
9
10
public class MyApplication extends Application{
@Override
public void onCreate() {
super.onCreate;
ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(getApplicationContext())
...
.build();
ImageLoader.getInstance().init(config);
}
}

Configuration
所有的选项都是可选的,只选择你真正制定的去配置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
File cacheDir = StrongeUtils.getCacheDiretory(context);
ImageLoaderConfiguration config = new ImageLoaderConfiguration.Bulider(context).memoryCacheExtraOptions(480,800) //如果图片尺寸大于了这个参数,那么就会按照这个参数对图片大小进行限制并缓存,默认是屏幕大小
.discCacheExtraOptions(480, 800, CompressFormat.JPEG, 75, null)
.taskExecutor(...)
.taskExecutorForCachedImages(...)
.threadPoolSize(3) // default
.threadPriority(Thread.NORM_PRIORITY - 1) // default
.tasksProcessingOrder(QueueProcessingType.FIFO) // default
.denyCacheImageMultipleSizesInMemory()
.memoryCache(new LruMemoryCache(2 * 1024 * 1024))
.memoryCacheSize(2 * 1024 * 1024)
.memoryCacheSizePercentage(13) // default
.discCache(new UnlimitedDiscCache(cacheDir)) // default
.discCacheSize(50 * 1024 * 1024)
.discCacheFileCount(100)
.discCacheFileNameGenerator(new HashCodeFileNameGenerator()) // default
.imageDownloader(new BaseImageDownloader(context)) // default
.imageDecoder(new BaseImageDecoder()) // default
.defaultDisplayImageOptions(DisplayImageOptions.createSimple()) // default
.writeDebugLogs()
.build();

Display Options
显示参数可以分别被每一个显示任务调用(ImageLoader.displayImage(…))

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
DisplayImageOptions options = new DisplayImageOptions.Builder()
.showImageOnLoading(R.drawable.ic_stub) // 在显示真正的图片前,会加载这些资源
.showImageForEmptyUri(R.drawable.ic_empty) // 空的Url
.showImageOnFail(R.drawable.ic_error) // 错误时加载
.resetViewBeforeLoading(false) // default
.delayBeforeLoading(1000) // 延时1000ms加载图片
.cacheInMemory(false) // default
.cacheOnDisc(false) // default
.preProcessor(...)
.postProcessor(...)
.extraForDownloader(...)
.considerExifParams(false) // default
.imageScaleType(ImageScaleType.IN_SAMPLE_POWER_OF_2) // default
.bitmapConfig(Bitmap.Config.ARGB_8888) // default
.decodingOptions(...)
.displayer(new SimpleBitmapDisplayer()) // default
.handler(new Handler()) // default
.build();

可接受的URL

1
2
3
4
5
String imageUri = "http://site.com/image.png"; // from Web
String imageUri = "file:///mnt/sdcard/image.png"; // from SD card
String imageUri = "content://media/external/audio/albumart/13"; // from content provider
String imageUri = "assets://image.png"; // from assets
String imageUri = "drawable://" + R.drawable.image; // from drawables (only images, non-9patch)

完整版

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// Load image, decode it to Bitmap and display Bitmap in ImageView
imageLoader.displayImage(imageUri, imageView, displayOptions,
new ImageLoadingListener() {
@Override
public void onLoadingStarted(String imageUri, View view) {
...
}
@Override
public void onLoadingFailed(String imageUri, View view, FailReason failReason) {
...
}
@Override
public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {
...
}
@Override
public void onLoadingCancelled(String imageUri, View view) {
...
}
},new ImageLoadingProgressListener(){
@Override
public void onProgressUpdate(String imageUri, View view, int current, int total) {
...
}
});


ImageSize targetSize = new ImageSize(120, 80); // result Bitmap will be fit to this size
imageLoader.loadImage(imageUri, targetSize, displayOptions, new SimpleImageLoadingListener() {
@Override
public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {
// Do whatever you want with Bitmap
}
});
// 同步解码Bitmap
Bitmap bmp = imageLoader.loadImageSync(imageUri, targetSize, displayOptions);

用户信息

1.缓存默认情况下不启用。如果你想加载的图像会在内存或磁盘上的缓存,那么你应该在DisplayImageOptions启用缓存这种方式:

1
2
3
4
5
6
7
8
9
10
11
12
DisplayImageOptions defaultOptions = new DisplayImageOptions.Builder()
...
.cacheInMemory(true)
.cacheOnDisc(true)
...
.build();
ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(getApplicationContext())
...
.defaultDisplayImageOptions(defaultOptions)
...
.build();
ImageLoader.getInstance().init(config);

简介

SpriteFrameCache 主要服务于多张碎图合并出来的纹理图片。这种纹理在一张大图中包含了多张小图,直接通过TextureCache引用会有诸多不便,因而衍生出来精灵框帧的处理方式,即把截取好的纹理信息保存在一个精灵框帧内,精灵通过切换不同的框帧来显示出不同的图案。

SpriteFrameCache

SpriteFrameCache的内部封装了一个Map<std::string, SpriteFrame*> _spriteFrames对象。key为帧的名称。SpriteFrameCache一般用来处理plist文件(这个文件指定了每个独立的精灵在这张“大图”里面的位置和大小),该文件对应一张包含多个精灵的大图,plist文件可以使用TexturePacker制作。

SpriteFrameCache的常用接口和TextureCache类似,不再赘述了,唯一需要注意的是添加精灵帧的配套文件一个plist文件和一张大的纹理图。下面列举了SpriteFrameCache常用的方法:(详细API请看SpriteFrameCache API)

获取单例的SpriteFrameCache对象。sharedSpriteFrameCache方法在3.0中已经弃用。

1
SpriteFrameCache* cache = SpriteFrameCache::getInstance();  

销毁SpriteFrameCache对象。

1
SpriteFrameCache::destroyInstance(); 

使用SpriteFrameCache获取指定的精灵帧,创建精灵对象。

1
2
3
4
SpriteFrameCache *frameCache = SpriteFrameCache::getInstance(); 
frameCache->addSpriteFramesWithFile("boy.plist","boy.png");//boy.png里集合了boy1.png,boy2.png这些小图
auto frame_sp = Sprite::createWithSpriteFrameName("boy1.png");//从SpriteFrameCache缓存中找到boy1.png这张图片.
this->addChild(frame_sp,2);

SpriteFrameCache vs. TextureCache

  • SpriteFrameCache精灵框帧缓存。顾名思义,这里缓存的是精灵帧SpriteFrame,它主要服务于多张碎图合并出来的纹理图片。这种纹理在一张大图中包含了多张小图,直接通过TextureCache引用会有诸多不便,因而衍生出来精灵框帧的处理方式,即把截取好的纹理信息保存在一个精灵框帧内,精灵通过切换不同的框帧来显示出不同的图案。

  • 跟TextureCache功能一样,将SpriteFrame缓存起来,在下次使用的时候直接去取。不过跟TextureCache不同的是,如果内存池中不存在要查找的图片,它会提示找不到,而不会去本地加载图片。

  1. TextureCache时最底层也是最有效的纹理缓存,缓存的是加载到内存中的纹理资源,也就是图片资源。
  2. SpriteFrameCache精灵框帧缓存,缓存的时精灵帧。
  3. SpriteFrameCache是基于TextureCache上的封装。缓存的是精灵帧,是纹理指定区域的矩形块。各精灵帧都在同一纹理中,通过切换不同的框帧来显示出不同的图案。

几天前Facebook开源了旗下应用Paper所使用的动画引擎Pop。使用动态动画(dynamic animations)而不是传统的静态动画(static animations),Pop使常见的滑动(scrolling)、弹跳(bouncing)、折叠(unfolding)等效果充满了活力,也使Paper给人耳目一新的感觉。

为什么需要Pop

从第一代iPhone开始,iOS系统在对静态动画(static animations)方面的支持就很出色。苹果提供的Core Animation framework,使我们方便的使用线性动画(linear)、淡入效果(ease-in)、淡出效果(ease-out)等动画。

手势交互的革新迎来了新的一轮软件设计的浪潮。人们可以用手指直接操作屏幕上的元素,(而不是想以前,需要一支笔),这就降低了交互的间接性(反着说,就是交互更直接),于是,人们又提出更进一步的要求:既然触屏可以得到处理,那么不同速度的划屏操作也应该得到处理。

当我们在2010年连个创办Push Pop Press公司的时候,我们的目标就是创造一种可行的、基于物理效果(physics-everywhere)的体验。我们在寻求一种可以使人们非常愉悦、轻松的使用整个应用的解决方案,就像我们使用UIScrollView那样的顺滑。

Popular就是这种理念的最新表现,它使你可以使用你熟悉而且强大的Core Animation进行编程,并且它能够捕获手势操作的速度,更好的反应用户的意图。Paper给我们了一个重新定义这种理念和其背后的动画引擎的机会。

目标

Pop在三个不同的纬度提供了相应的工具。第一,我们想使一些常见动画在使用上更加的简单便利,除了iOS内建的4种静态动画(static animations),Pop增加三种原始的动画类型:弹簧效果(spring),衰变效果(decay)和自定义效果(custom)。

弹簧效果和衰变效果是动态动画,正是它们让Paper充满了活力动感。弹簧动画使Paper上的元素优雅的弹跳(attractive bounce)。衰变动画可以使元素的移动(movement)缓慢的停止。这两种动画都是把速度作为输入,并且在处理用户手势时的理想方案之一。

第二,Pop是一个可灵活扩展的库。自定义动画(custom animation)允许开发者插自己的动画代码,使你可以容易的创造出独特的动画效果。通过把动画从展示层(display)解耦,Pop是一个适用范围更广的库,你可以对任何对象属性做动态的改变,就像做动画那样。

第三,我们的目标是构建一个开发者有好但是功能强大的编程模型。比如,Core Animation开发者应该对下面的API比较熟悉:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@interface CALayer
/* Attach an animation to the layer. */
- (void)addAnimation:(CAAnimation *)anim forKey:(NSString *)key;

/* Remove all animation attached to the layer. */
- (void)removeAllAnimations;

/* Remove any animation attached to the layer for 'key'. */
- (void)removeAnimationForKey:(NSString *)key;

/* Returns an array containing the keys of all animations currently attached to the receiver. */
- (NSArray *)animationKeys;

/* Returns the animation added to the layer with identifier 'key', or nil if no such animation exists. */
- (CAAnimation *)animationForKey:(NSString *)key;

@end

开发者可以通过这些接口,使用Core Animation来开始和结束动画。最显著的一点是,它也支持查询正在中的动画,这是终端动画和构建流畅的用户界面的关键。下面是Popular实现相同功能提供的API:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@interface NSObject (pop)

/* Attach an animation to the layer. */
- (void)pop_addAnimation:(POPAnimation *)anim forKey:(NSString *)kay;

/* Remove all animations attached to the layer. */
- (void)pop_removeAllAnimations;

/* Remove any animation attached to the layer for 'key'. */
- (void)pop_removeAnimationForKey:(NSString *)key;

/* Returns an array containing the keys of all animations currently attached to the receiver. */
- (NSArray *)pop_animationKeys;

/* Returns the animation added to the layer with identifier 'key', or nil if no such animation exists. */
- (POPAnimation *)pop_animationForKey:(NSString *)key;

@end

最基本的动画类型是POPAnimation,由于Pop是在NSObject上扩展的类别,所以可以在任何对象上做动画。除了上面提到的区别外,这些API接口形式在其他方面都没有什么区别。

下面的例子展示了如何对layer的bounds属性做弹簧动画:

1
2
3
4
POPSpringAnimation *anim = [POPSpringAnimation animation];
anim.property = [POPAnimatableProperty propertyWithName:kPOPLayerBounds];
anim.toValue = [NSValue valueWithCGRect:CGRectMake(0,0,400,400)];
[layer pop_addAnimation:anim forKey:@"size"];

如果你会使用Core Animation构建动画,那么你也会用Pop,它们在使用上是相同的。

  • UITapGestureRecognizer(点一下)
  • UIPinchGestureRecognizer(两指往内或往外拨动,就是常用的缩放)
  • UIRotationGestureRecognizer(旋转)
  • UISwipeGestureRecognizer(滑动)
  • UIPanGestureRecognizer(拖动,慢速移动)
  • UILongPressGestureRecognizer(长按)

UIGestureRecognizer的继承关系如下:

使用手势的步骤

  1. 创建手势实例,指定一个回调方法,当手势开始,改变或结束时,回调方法被调用。
  2. 添加到需要识别的View中,每个手势只能对应一个View,当屏幕触摸在View的边界内,如果手势和预定的一样,就会回调方法。

ps:一个手势只能对应一个View,但一个View可以对应多个手势。

Pan拖动手势

1
2
3
4
5
6
UIImageView *snakeImageView = [[UIImageView alloc] initWithImage:[UIImage imageName:@"snake.png"]];
snakeImageView.frame = CGReckMake(50,50,100,160);
UIPanGestureRecognizer *panGestureRecongnizer = [[UIPanGestureRecoginizer alloc] initWithTarget:self action:@selector(handlePan:)];
[snakeImageView addGestureRecognizer:panGestureRecongnizer];
[self.view setBackgroundColor:[UIColor whiteColor]];
[self.view addSubview:snakeImageView];

添加手势回调方法

1
2
3
4
5
6
- (void) handlePan:(UIPanGestureRecognizer*) recognizer
{
CGPoint translation = [recognizer translationInView:self.view];
recognizer.view.center = CGPointMake(recognizer.view.center.x + translation.x,recognizer.view.center.y + translation.y);
[recognizer setTranslation:CGPointZero inView:self.view];
}

Pinch缩放手势

1
2
3
4
5
6
7
8
UIPinchGestureRecoginzer *pinchGestureRecognizer = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(handlePinch:")];
[snakeImageView addGestureRecognizer:pinchGestureRecognizer];

- (void)handlePinch:(UIPinchGestureRecongnizer*) recognizer
{
recognizer.view.transform = CGAffineTransformScale(recognizer.view.transform,recognizer.scale, recognizer.scale);
recognizer.scale = 1;
}

Rotation旋转手势

1
2
3
4
5
6
7
8
UIRotationGestureRecognizer *rotateRecognizer = [[UIRotationGestureRecognizer alloc]  initWithTarget:self action:@selector(handleRotate:)];  
[snakeImageView addGestureRecognizer:rotateRecognizer];

- (void) handleRotate:(UIRotationGestureRecognizer*) recognizer
{
recognizer.view.transform = CGAffineTransformRotate(recognizer.view.transform, recognizer.rotation);
recognizer.rotation = 0;
}

多个View添加手势

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
- (void)viewDidLoad  
{
[super viewDidLoad];

UIImageView *snakeImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"snake.png"]];
UIImageView *dragonImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"dragon.png"]];
snakeImageView.frame = CGRectMake(120, 120, 100, 160);
dragonImageView.frame = CGRectMake(50, 50, 100, 160);
[self.view addSubview:snakeImageView];
[self.view addSubview:dragonImageView];

for (UIView *view in self.view.subviews) {
UIPanGestureRecognizer *panGestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePan:)];

UIPinchGestureRecognizer *pinchGestureRecognizer = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(handlePinch:)];

UIRotationGestureRecognizer *rotateRecognizer = [[UIRotationGestureRecognizer alloc] initWithTarget:self action:@selector(handleRotate:)];

[view addGestureRecognizer:panGestureRecognizer];
[view addGestureRecognizer:pinchGestureRecognizer];
[view addGestureRecognizer:rotateRecognizer];
[view setUserInteractionEnabled:YES];
}
[self.view setBackgroundColor:[UIColor whiteColor]];
}

监听手势

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
- (void) handlePan:(UIPanGestureRecognizer*) recognizer  
{
CGPoint translation = [recognizer translationInView:self.view];
recognizer.view.center = CGPointMake(recognizer.view.center.x + translation.x,
recognizer.view.center.y + translation.y);
[recognizer setTranslation:CGPointZero inView:self.view];

if (recognizer.state == UIGestureRecognizerStateEnded) {

CGPoint velocity = [recognizer velocityInView:self.view];
CGFloat magnitude = sqrtf((velocity.x * velocity.x) + (velocity.y * velocity.y));
CGFloat slideMult = magnitude / 200;
NSLog(@"magnitude: %f, slideMult: %f", magnitude, slideMult);

float slideFactor = 0.1 * slideMult; // Increase for more of a slide
CGPoint finalPoint = CGPointMake(recognizer.view.center.x + (velocity.x * slideFactor),
recognizer.view.center.y + (velocity.y * slideFactor));
finalPoint.x = MIN(MAX(finalPoint.x, 0), self.view.bounds.size.width);
finalPoint.y = MIN(MAX(finalPoint.y, 0), self.view.bounds.size.height);

[UIView animateWithDuration:slideFactor*2 delay:0 options:UIViewAnimationOptionCurveEaseOut animations:^{
recognizer.view.center = finalPoint;
} completion:nil];

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
import java.io.IOException;

public final class MIUIUtils{
private static final String KEY_MIUI_VERSION_CODE = "ro.miui.ui.version.code";
private static final String KEY_MIUI_VERSION_NAME = "ro.miui.ui.version.name";
private static final String KEY_MIUI_INTERNAL_STORAGE = "ro.miui.internal.storage";

public static boolean isMIUI() {
try {
final BuildProperties prop = BuildProperties.newInstance();
return prop.getProperty(KEY_MIUI_VERSION_CODE, null) != null
|| prop.getProperty(KEY_MIUI_VERSION_NAME, null) != null
|| prop.getProperty(KEY_MIUI_INTERNAL_STORAGE, null) != null;
} catch (final IOException e) {
return false;
}
}
}
//工具类
import android.os.Environment;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Collection;
import java.util.Enumeration;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.Set;

public class BuildProperties{
private final Properties properties;

private BuildProperties() throws IOException {
properties = new Properties();
properties.load(new FileInputStream(new File(Environment.getRootDirectory(), "build.prop")));
}
public boolean containsKey(final Object key) {
return properties.containsKey(key);
}

public boolean containsValue(final Object value) {
return properties.containsValue(value);
}

public Set<Entry<Object, Object>> entrySet() {
return properties.entrySet();
}

public String getProperty(final String name) {
return properties.getProperty(name);
}

public String getProperty(final String name, final String defaultValue) {
return properties.getProperty(name, defaultValue);
}

public boolean isEmpty() {
return properties.isEmpty();
}

public Enumeration<Object> keys() {
return properties.keys();
}

public Set<Object> keySet() {
return properties.keySet();
}

public int size() {
return properties.size();
}

public Collection<Object> values() {
return properties.values();
}

public static BuildProperties newInstance() throws IOException {
return new BuildProperties();
}
}

import android.os.Build;

import java.lang.reflect.Method;

pulic final class FlymeUtils{
public static boolean isFlyme(){
try{
// Invoke Build.hasSmartBar()
Invoke Method method = Build.class.getMethod("hasSmartBar");
return method != null;
}catch(final Exception e){
return false;
}
}
}

如何使用

  • UIButton+Bootstrap,NSString+FontAwesome,FontAwesome.ttf文件添加进工程
  • 导入UIButton+Bootstrap.h
  • Info.plist中的Fonts provided by application中包含FontAwesome.ttf

创建自定义的UIButoon(把type属性设置为Custom)
设置风格

1
2
[yourButton primaryStyle];
[yourOtherButton successStyle];

设置图标

1
2
[yourButton addAwesomeIcon:FAIconBookmark beforeTitle:YES];
[yourOtherButton addAwesomeIcon:FAIconCalendar beforeTitle:NO];

CommonUtil

  • boolean hasSDCard() 判断设备是否存在SD卡
  • String getRootFilePath() 返回文件系统的根目录,存在SD卡返回SD卡目录
  • boolean checkNetState(Context context) 判断是否存在网络
  • void showToask(Context context,String tip) 显示Toast通知
  • int getScreenWidth(Context context)\int getScreenHeight(Context context) 返回屏幕的宽\长

FileHelper

  • boolean fileIsExist(String filePath) 判断文件是否存在
  • InputStream readFile(String filePath) 获取文件的input流
  • boolean createDirectory(String filePath) 创建文件目录
  • boolean deleteDirectroy(String filePath) 删除文件目录
  • boolean writeFile(String filePath,InputStream inputStream) 写入字符串到某文件

添加Action Bar

Android 3.0及以上版本

Android 3.0(API level 11)开始,所有使用Theme.Holo主题的Activity都内置了action bar,此主题是targetSdkVersion或minSdkVersion属性大于等于11时的默认主题。

1
2
3
<manifest ...>
<uses-sdk android:minSdkVersion="11" .../>
</manifest>

如果创建一个自定义主题,需呀确保它使用一个Theme.Holo主题作为父主题

支持Android 2.1及以上版本

可以通过天剑Support库或者v7 appcompat库集成Action Bar到低版本

  1. activity继承ActionBarActivity
1
public class MainActivity extends ActionBarActivity {...}
  1. 在mainfest文件中设置application或设置单独的activity的主题为Theme.AppCompat
1
<activity android:theme="@style/Theme.AppCompat.Light" ... >

移除action bar

可以把该Activity的主题设置为Theme.Holo.NoActionBar

1
<activity android:theme=@"android:style/Theme.Holo.NoActionBar">

也可以在运行时通过调用hide()来隐藏action bar

1
2
ActionBar actionBar = getActionBar();
actionBar.hide();

也可以用show()来重新显示ActionBar。

添加Action项

当acitivity第一次被启动时,系统会调用activity中的onCreateOptionsMenu()来构建Action和滑动菜单。菜单项通过XML格式的菜单资源来包装。
在此XML文件中,通过将元素声明为带android:showAsAction="ifRoom"属性,你可以让一个菜单项显示为action项。通过这种方式,菜单项将会仅当有空间时才显示在action bar中,便于快速访问。如果没有空间的话,菜单项将会显示在滑动菜单中。
如果菜单项同时具有标题和文本——带有android:titleandroid:icon属性——则action项默认只显示图标。如果需要显示文本标题,只要在android:showAsAction属性中加入withText即可

1
2
3
4
5
6
7
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id=@"+id/menu_save"
android:icon=@"drawable/ic_menu_save"
android:title=@"string/menu_save"
android:showAsAction="ifRoom|withText"/>
</menu>

在Activity中实现onCreateOptionMenu()回调方法来inflate菜单资源从而获取Menu对象。

1
2
3
4
5
6
7
@Override
public boolean onCreateOptionsMenu(Menu menu)
{
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.main_action_menu,menu);
return super.onCreateOptionsMenu(menu);
}

用户选中一个action项,activity将会收到一个对onPtionsItemSelected()调用,并传入android:id属性给出的ID——选项菜单中的所有项都会被收到该回调方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
@Override
public boolean onOptionsItemSelected(MenuItem item){
switch (item.getItemId()){
case R.id.action_search:
openSearch();
return true;
case R.id.action_settings:
openSettings();
return true;
default:
return super.onOptionsItemSelected(item);
}
}

使用split action bar

Android 4.0(API level 14)及以上版本时,action bar增加一个名为”split action bar”的模式。启用split action bar后,如果在窄屏上(比如纵向显示的手持设备)运行activity,屏幕底部将会显示一个单独的bar,用于显示所有的action项,把action bar拆分开、让多个action项分开显示,可以确保在窄屏上以合理的间隔显示所有的action项,并为顶部的导航条和标题栏留下足够的空间。
启用split action bar只要把uiOptions=”splitActionBarWhenNarrow”加入你的manifest元素即可。

用应用程序图标导航

默认情况下,应用程序图标显示在action bar的左侧。如果需要,可以把让图标成为一个action项。作为用户对图标Action的响应,应用程序必须执行以下两件事之一:

  • 返回应用程序的初始home activity
  • 应用程序向上回退至上一层
    当用户触摸图标时,系统会调用Activity的onOptionsItemSelected()方法,并带入android.R.id.homeID,作为响应,你应该启动home activity或让用户回到应用程序的上一侧。
    如果你把返回home activity作为应用程序图标的响应,则你应该在Intent中包含FLAG_ACTIVITY_CLEAR_TOP标志。如果你要启动的Activity已经存在于当前任务中了,则用这个标志,可以将所有在其之上的Activity都销毁,并把该activity推到前台。因为回到home等同于返回的Action,通常不应该创建一个新的home activity实例,所以加入这个标志是非常重要的。否则的话,当前的任务栈中可能会有一长串activity,其中包含了home activity的多个实例。
1
2
3
4
5
6
7
8
9
10
11
12
@Override
public boolean onOptionsItemSeleted(MenuItem item) {
switch(item.getItemId()) {
case android.R.id.home:
Intent intent = new Intent(this,HomeActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLER_TOP);
startActivity(intent);
return true;
}
default:
return super.onPtionsItemSelected(item);
}

用户可以从其他应用程序中进入当前Activity,这时你也许还想加入FLAG_ACTIVITY_NEW_TASK标志,次标志表示,不管用户导航至home还是返回上一层,新的activity都不加入当前任务栈,而是放入一个属于你的应用程序的栈。比如说,如果用户通过其他应用提交一个itent,启动了一个属于你的应用的activity,然后选中了action bar图标来回到home或向上回退一级,FLAG_ACTIVITY_CLEAR_TOP标志将会启动属于你的应用程序任务中的Activity(而不是当前任务)。系统或是启动一个新的任务并把这个你的这个新activity作为根activity,或者,假如后台任务中存在该activity的实例的话,则把任务推到前台,该activity会收到onNewIntent()。因此,如果你的activity要从其他应用接收intent(声明了任何通用的intent过来器),你通常应该在intent中加入FLAG_ACTIVITY_NEW_TASK

1
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);

如果用图标来返回home activity,请注意自Android 4.0(API level 14)开始,你必须显式地调用setHomeButtonEnabled(true)将图标用作action项(之前的版本中,图标默认就是作为action项使用的)。

向上导航

调用ActionBar的setDisplayHomeAsEnabled(true)

1
2
3
4
5
6
7
8
protected void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);

setContentView(R.id.main);
ActionBar actionBar = getActionBar();
actionBar.setDisplayHomeAsUpEnabled(true);
...
}

当用户触摸图标时,系统会调用activity的onOptionsItemSelected()方法,并带入android.R.id.homeID.
记得在Intent上使用FLAG_ACTIVITY_CLEAR_TOP标志,这样就不会创建已存在的父activity的新实例。
manifest文件中声明父Activity

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<application ...>
<activity
android:name="com.example.myfirstapp.MainActivity" ...>
...
</activity>
<activity
android:name="com.example.myfirstapp.DisplayMessageActivity"
android:label="@string/title_activity_display_message"
android:parentActivityName="com.example.myfirstapp.MainActivity" >
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="com.example.myfirstapp.MainActivity" />
</activity>
</application>

ActionBar风格化

Android的基本主题有暗、淡两个


在manifest文件中设置applicationactivityandroid:theme属性

1
<application android:theme="@android:style/Theme.Holo.Light" ... />

使用Support库时,必须使用Theme.AppCompat主题替代

  • Theme.AppCompat,一个“暗”的主题
  • Theme.AppCompat.Light,一个“淡”的主题
  • Theme.AppCompat.Light.DarkActionBar,一个带有“暗” action bar 的“淡”主题

自定义

为了自定义主题,通过重写actionBarStyle属性来改变action bar的背景。通过指定一个drawable资源来重写background属性。

Android 3.0可以这样写
res/values/themes.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- the theme applied to the application or activity -->
<style name="CustomActionBarTheme"
parent="@android:style/Theme.Holo.Light.DarkActionBar">
<item name="android:actionBarStyle">@style/MyActionBar</item>
</style>

<!-- ActionBar styles -->
<style name="MyActionBar"
parent="@android:style/Widget.Holo.Light.ActionBar.Solid.Inverse">
<item name="android:background">@drawable/actionbar_background</item>
</style>
</resources>

然后,将你的主题应该到你的 app 全局或单个的 activity 之中:

1
<application android:theme="@style/CustomActionBarTheme" ... />

Android 2.1 版本可以这么写
res/values/themes.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- the theme applied to the application or activity -->
<style name="CustomActionBarTheme"
parent="@style/Theme.AppCompat.Light.DarkActionBar">
<item name="android:actionBarStyle">@style/MyActionBar</item>

<!-- Support library compatibility -->
<item name="actionBarStyle">@style/MyActionBar</item>
</style>

<!-- ActionBar styles -->
<style name="MyActionBar"
parent="@style/Widget.AppCompat.Light.ActionBar.Solid.Inverse">
<item name="android:background">@drawable/actionbar_background</item>

<!-- Support library compatibility -->
<item name="background">@drawable/actionbar_background</item>
</style>
</resources>

然后,将你的主题应该到你的 app 全局或单个的 activity 之中:

1
<application android:theme="@style/CustomActionBarTheme" ... />

自定义本文颜色

  • action bar的标题:指定textColor属性
  • action bar的页签:重写 actionBarTabTextStyle
  • action bar按钮:重写 actionMenuTextColor
    Android 3.0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- the theme applied to the application or activity -->
<style name="CustomActionBarTheme"
parent="@style/Theme.Holo">
<item name="android:actionBarStyle">@style/MyActionBar</item>
<item name="android:actionBarTabTextStyle">@style/MyActionBarTabText</item>
<item name="android:actionMenuTextColor">@color/actionbar_text</item>
</style>

<!-- ActionBar styles -->
<style name="MyActionBar"
parent="@style/Widget.Holo.ActionBar">
<item name="android:titleTextStyle">@style/MyActionBarTitleText</item>
</style>

<!-- ActionBar title text -->
<style name="MyActionBarTitleText"
parent="@style/TextAppearance.Holo.Widget.ActionBar.Title">
<item name="android:textColor">@color/actionbar_text</item>
</style>

<!-- ActionBar tabs text styles -->
<style name="MyActionBarTabText"
parent="@style/Widget.Holo.ActionBar.TabText">
<item name="android:textColor">@color/actionbar_text</item>
</style>
</resources>

Android 2.1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- the theme applied to the application or activity -->
<style name="CustomActionBarTheme"
parent="@style/Theme.AppCompat">
<item name="android:actionBarStyle">@style/MyActionBar</item>
<item name="android:actionBarTabTextStyle">@style/MyActionBarTabText</item>
<item name="android:actionMenuTextColor">@color/actionbar_text</item>

<!-- Support library compatibility -->
<item name="actionBarStyle">@style/MyActionBar</item>
<item name="actionBarTabTextStyle">@style/MyActionBarTabText</item>
<item name="actionMenuTextColor">@color/actionbar_text</item>
</style>

<!-- ActionBar styles -->
<style name="MyActionBar"
parent="@style/Widget.AppCompat.ActionBar">
<item name="android:titleTextStyle">@style/MyActionBarTitleText</item>

<!-- Support library compatibility -->
<item name="titleTextStyle">@style/MyActionBarTitleText</item>
</style>

<!-- ActionBar title text -->
<style name="MyActionBarTitleText"
parent="@style/TextAppearance.AppCompat.Widget.ActionBar.Title">
<item name="android:textColor">@color/actionbar_text</item>
<!-- The textColor property is backward compatible with the Support Library -->
</style>

<!-- ActionBar tabs text -->
<style name="MyActionBarTabText"
parent="@style/Widget.AppCompat.ActionBar.TabText">
<item name="android:textColor">@color/actionbar_text</item>
<!-- The textColor property is backward compatible with the Support Library -->
</style>
</resources>

自定义Tab Indicator

重写actionBarTabStyrle指定navigation tabs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_focused="false" android:state_selected="false"
android:state_pressed="false"
android:drawable="@drawable/tab_unselected" />
<item android:state_focused="false" android:state_selected="true"
android:state_pressed="false"
android:drawable="@drawable/tab_selected" />
<item android:state_focused="true" android:state_selected="false"
android:state_pressed="false"
android:drawable="@drawable/tab_unselected_focused" />
<item android:state_focused="true" android:state_selected="true"
android:state_pressed="false"
android:drawable="@drawable/tab_selected_focused" />
<item android:state_focused="false" android:state_selected="false"
android:state_pressed="true"
android:drawable="@drawable/tab_unselected_pressed" />
<item android:state_focused="false" android:state_selected="true"
android:state_pressed="true"
android:drawable="@drawable/tab_selected_pressed" />
<item android:state_focused="true" android:state_selected="false"
android:state_pressed="true"
android:drawable="@drawable/tab_unselected_pressed" />
<item android:state_focused="true" android:state_selected="true"
android:state_pressed="true"
android:drawable="@drawable/tab_selected_pressed" />
</selector>

Android 3.0

1
2
3
4
5
6
7
8
9
10
11
12
13
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="CustomActionBarTheme"
parent="@style/Theme.Holo">
<item name="android:actionBarTabStyle">@style/MyActionBarTabs</item>
</style>

<style name="MyActionBarTabs"
parent="@style/Widget.Holo.ActionBar.TabView">
<!-- tab indicator -->
<item name="android:background">@drawable/actionbar_tab_indicator</item>
</style>
</resources>

Android 2.1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="CustomActionBarTheme"
parent="@style/Theme.AppCompat">
<item name="android:actionBarTabStyle">@style/MyActionBarTabs</item>

<!-- Support library compatibility -->
<item name="actionBarTabStyle">@style/MyActionBarTabs</item>
</style>

<style name="MyActionBarTabs"
parent="@style/Widget.AppCompat.ActionBar.TabView">
<item name="android:background">@drawable/actionbar_tab_indicator</item>

<item name="background">@drawable/actionbar_tab_indicator</item>
</style>
</resources>

Action Bar 覆盖叠加

  • 启动叠加模式
    自定义主题 设置android:windowActionBarOverlay的属性为true
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!-- android 3.0-->
<resources>
<style name="CustomActionBarTheme"
parent="@android:style/Theme.Holo">
<item name="android:windowActionBarOverlay">true</item>
</style>
</resources>
<!-- android 2.1 -->
<resources>
<style name="CustomActionBarTheme"
parent="@android:style/Theme.AppCompat">
<item name="android:windowActionBarOverlay">true</item>

<!-- Support library compatibility -->
<item name="windowActionBarOverlay">true</item>
</style>
</resources>
  • 指定布局的顶部边距
1
2
3
4
5
6
7
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingTop="?android:attr/actionBarSize">
<!-- Support库中 移除android:前缀 paddingTop="?attr/actionBarSize" -->
...
</RelativeLayout>

添加一个Action View

action view是一个显示于action bar中的widget,用于替代某个action项按钮.

在菜单资源中声明一个action view项,使用android:actionLayoutandroid:actionViewClass属性指定一个layout资源或所需的widget类即可。比如:

1
2
3
4
5
6
7
8
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id=@"+id/menu_search"
android:title=@"string/menu_search"
android:icon=@"drawable/ic_menu_search"
android:showAsAction="ifRomm|collapseActionView"
android:actionViewClass="android.widget.SearchView" />
</menu>

注意android:showAsAction属性还包含了collapseActionView。这是可选项,表示action view应该折叠进入按钮中,当用户选中了按钮,action view就会展开。否则,action viewm默认就是可见的。
action view可以通过下单项的ID调用findItem(),然后调用getActionView()

1
2
3
4
5
6
7
8
@Override
public boolean onCreateOptionsMenu(Menu menu){
getMenuInflater().inflate(R.menu.options,menu);
SearchView searchView = (SavechView)menu.findItem(R.id.menu_search).getActionView();
// ...

return super.onCreateOptionsMenu(menu);
}