0%

iconfont极大地方便了我们应用的开发,一些小图标可以做成iconfont,生成一个iconfont.ttf的字体,使用时项目里不再需要多种不同分辨率的图标,只需要将Android TextView或iOS UILabel的字体设成iconfont,即可显示图标。Android设置字体的方法以前介绍过,这里不再写。

1、将生成的iconfont.ttf拖进项目里。生成iconfont可借助 http://www.iconfont.cn/. 2、切到项目属性–Build Phases–Copy Bundle Resources–把iconfont.ttf加到这里 3、在Info.plist的根节点下,添加UIAppFonts的Array,Array下添加item,值为iconfont.ttf. 4、项目中使用:

    writeLabel.font = UIFont(name: "iconfont", size: 20)
    //swift使用unicode码
    writeLabel.text = "\\u{e603}"

iconfont不是字体文件名,而是字体名

前面写了一个Android版,因为有属性动画,很简单几行代码就实现了。 iOS版复杂多了,当然也可能我没找到简单的方法。参考了开源项目 https://github.com/PigRiver/NumberJumpDemo,这个是用OC写的,我用Swift改写了一个版本。 说下原理:先确定使用的贝塞尔曲线, 确定两桢间的间隔,根据间隔和总时间计算得到总桢数,根据贝塞尔曲线算出每桢的时间和值,然后delay设置显示的string。

贝塞尔曲线可以在http://cubic-bezier.com/生成。我选了ease-in-out的效果,不过为了ease更明显,我改了下。

//存储贝塞尔曲线的点
import UIKit
class BezierPoint {
let x:Float!
let y:Float!
init(x:Float,y:Float) {
self.x = x
self.y = y
}
}

算点

import UIKit

class BezierCurve {
//根据t(位置,0-1)取点
class func PointOnCubicBezier(cp:[BezierPoint], t:Float) -> BezierPoint{
var ax,bx,cx:Float
var ay,by,cy:Float
var tSquared,tCubed:Float

    /*計算多項式係數*/
    
    cx = 3.0 * (cp\[1\].x - cp\[0\].x);
    bx = 3.0 * (cp\[2\].x - cp\[1\].x) - cx;
    ax = cp\[3\].x - cp\[0\].x - cx - bx;
    
    cy = 3.0 * (cp\[1\].y - cp\[0\].y);
    by = 3.0 * (cp\[2\].y - cp\[1\].y) - cy;
    ay = cp\[3\].y - cp\[0\].y - cy - by;
    /*計算位於參數值t的曲線點*/
    tSquared = t * t;
    tCubed = tSquared * t;
    var x = (ax * tCubed) + (bx * tSquared) + (cx * t) + cp\[0\].x;
    var y = (ay * tCubed) + (by * tSquared) + (cy * t) + cp\[0\].y;
    return BezierPoint(x: x, y: y)
}

}

使用CATextLayer来渲染

import UIKit

class CountLayer: CATextLayer {
//每帧间隔
let durationPerFrame:NSTimeInterval = 20.0;
//总时间
var durationTotal:NSTimeInterval = 1500.0
//开始值
var startNumber:Float!
//结束值
var endNumber:Float!
//总点数
var pointNumber:Int!
//当前画的点
var indexCurrent:Int
var lastTime:Float!
//开始点
let startPoint:BezierPoint = BezierPoint(x: 0, y: 0)
//两个bezier点,http://cubic-bezier.com/ 可以生成
let controlPoint1:BezierPoint = BezierPoint(x: 0.0, y: 0.78)
var controlPoint2:BezierPoint = BezierPoint(x: 0.0, y: 0.98)
//结束点,固定1,1
var endPoint:BezierPoint = BezierPoint(x: 1, y: 1)
//计算得到的所有点
var allPoints:NSMutableArray!

override init!() {
     indexCurrent = 0
    super.init()
    initDate()
}

required init(coder aDecoder: NSCoder) {
     indexCurrent = 0
    super.init(coder: aDecoder)
   initDate()
}

private func initDate() {
    allPoints = NSMutableArray()
   
    lastTime = 0
}

//动画跳动展示
func showNumberWithAnimation(duration:NSTimeInterval, startNumber:Float, endNumber:Float) {
    indexCurrent = 0
    lastTime = 0
    self.durationTotal = duration
    self.startNumber = startNumber
    self.endNumber = endNumber
    initBezierPoint()
    changeNumberBySelector()
}

//初始化h
private func initBezierPoint() {
    //总共多少点
    pointNumber = Int(ceil(durationTotal/durationPerFrame))
    var bezierPoints:\[BezierPoint\] = \[startPoint, controlPoint1, controlPoint2, endPoint\]
    allPoints.removeAllObjects()
    //读出每点的时间和值
    for index in 0 ..< pointNumber {
        var t = Float(index)/Float(pointNumber)
        var point:BezierPoint = BezierCurve.PointOnCubicBezier(bezierPoints, t: t)
        var durationTime = point.x * Float(durationTotal)
        var value:Float = point.y * (endNumber - startNumber) + startNumber;
        allPoints.addObject(NSArray(array: \[durationTime,value\]))
    }
    
    
}
func changeNumberBySelector() {
    if(indexCurrent >= pointNumber) {
        self.string = NSString(format: "%.2f", endNumber)
    }else{
        var currentPoint: NSArray = allPoints.objectAtIndex(indexCurrent) as NSArray
        var value = currentPoint.objectAtIndex(1) as Float
        var currentTime = currentPoint.objectAtIndex(0) as Float
        
        ++indexCurrent
        var timeDuration = currentTime - lastTime
        lastTime = currentTime;
        self.string = NSString(format: "%.2f", value)
        
        //延迟调用changeNumberBySelector方法,重新设string
        let delay = durationPerFrame * Double(NSEC\_PER\_MSEC)
        var time = dispatch\_time(DISPATCH\_TIME_NOW, Int64(delay))
        dispatch\_after(time, dispatch\_get\_main\_queue(), {
            NSThread.detachNewThreadSelector(Selector("changeNumberBySelector"), toTarget:self, withObject: nil)
        })

    }
}

}

使用:

//赚到的钱
var earnLayer:CountLayer!

override func viewDidLoad() {
    super.viewDidLoad()
           
    earnLayer = CountLayer()
    earnLayer.frame = CGRectMake(85, 37, 151, 80)
    earnLayer.string = "asdf"
    earnLayer.fontSize = 30
    earnLayer.contentsScale = 2
    earnLayer.alignmentMode = "center"
    topBgView.layer.addSublayer(earnLayer)
   
    
}

override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
    // Dispose of any resources that can be recreated.
}

@IBAction func test(sender: AnyObject) {
    earnLayer.showNumberWithAnimation(1500, startNumber: 0.0, endNumber: 999999.65)
    
}

iOS里要设置渐变的背景不是很方便,Interface Builder只能设单色背景,渐变需要代码实现。 原理是往指定的View的layer里插入一层CAGradientLayer。 先定义一个GradientColors类方便构造指定颜色的CAGradientLayer。

import UIKit
class GradientColors {
let colorTop:CGColorRef
let colorCenter:CGColorRef
let colorBottom:CGColorRef
let gl: CAGradientLayer

init(colorTop:CGColorRef, colorCenter:CGColorRef, colorBottom:CGColorRef) {
    self.colorTop = colorTop
    self.colorCenter = colorCenter
    self.colorBottom = colorBottom
    gl = CAGradientLayer()
    gl.colors = \[ colorTop,colorCenter,colorBottom\]
}

}

构造方法是上中下,三种颜色。 因为UIColor构造方法创建指定的颜色不是很方便,需要计算rgba四个通道0~1的值,并不是常用的16进制颜色值,所以,我写了一个类来帮助转换。

import UIKit
class ColorUtils {
//把argb值转成UIColor
class func colorFromArgb(argb:Int) -> UIColor {
let blue:Float = Float(argb & 0xff) / 255.0
let green = Float(argb >> 8 & 0xff) / 255.0
let red = Float(argb >> 16 & 0xff) / 255.0
let alpha = Float(argb >> 24 & 0xff) / 255.0
return UIColor(red: CGFloat(red), green: CGFloat(green), blue: CGFloat(blue), alpha: CGFloat(alpha))
}
}

下面是controller中应用渐变:

@IBOutlet weak var topBgView: UIView!
let gradienColors = GradientColors(colorTop: ColorUtils.colorFromArgb(0xff2cb1e1).CGColor, colorCenter: ColorUtils.colorFromArgb(0xff1da9da).CGColor, colorBottom: ColorUtils.colorFromArgb(0xff0fa1d3).CGColor)

override func viewDidLoad() {
    super.viewDidLoad()
    topBgView.backgroundColor = UIColor.clearColor()
    var backgroundLayer = gradienColors.gl
    backgroundLayer.frame = topBgView.frame
    topBgView.layer.insertSublayer(backgroundLayer, atIndex: 0)
}

余额宝的金额在显示时是会跳动的,而且改变的速度是不一样的。下面自定义一个这种效果的View.

/**
* 动画显示数字
* Created by fhp on 15/1/7.
*/
public class CountView extends NumberView{
//动画时长 ms
int duration = 1500;
float number;
public CountView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public void showNumberWithAnimation(float number) {
//修改number属性,会调用setNumber方法
ObjectAnimator objectAnimator=ObjectAnimator.ofFloat(this,”number”,0,number);
objectAnimator.setDuration(duration);
//加速器,从慢到快到再到慢
objectAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
objectAnimator.start();
}

public float getNumber() {
    return number;
}

public void setNumber(float number) {
    this.number = number;
    setText(String.format("%1$07.2f",number));
}

}

NumberView继续自TextView,这里直接改成TextView即可,NumberView只是设置了一个适合数字显示的字体。 调用时,用showNumberWithAnimation方法。

2015年,开始一个伟大的计划,翻译iOS文档。具体时间计划待定,先进行一两周。

UIView

继承自:UIResponder:NSObject
遵循:UITraitEnvironment, UIDynamicItem, NSObject, UICoordinateSpace, UIAppearanceContainer, UIAppearance, NSCoding 框架:iOS 2.0及以后版本的UIKit


UIView类在屏幕和界面上定义了一块矩形区域,用来管理那块区域的内容。在运行时,一个View对象处理它所在区域的内容渲染和交互。UIView类会使用背景颜色填充所在的矩形区域。更复杂的内容可以用UIView的子类来呈现,你自己来实现必要的绘画和事件处理代码。UIKit框架同时提供了一些标准子类,从简单的按钮到复杂的表格,你可以直接使用。如,UILabel对象画文本字符串,UIImageView对象画图像。

因为View对象是应用与用户交互的主要途径,他们有一系列的职责。以下列出一些:

  • 绘画和动画:
    • View在他们的矩形区域使用UIKit,Core Graphics和OpenGL ES之类的技术绘画
    • 一些视图属性可以动画到新的值
  • 布局和子View管理
    • 一个View可以包含0个或多个子View
    • 每个View都定义了相对父View大小改变的默认行为
    • 如有需要,一个View可以定义子View的大小和位置
  • 事件处理
    • View是一个响应者,可以处理UIResponder类定义的触摸和其他事件
    • View可以使用addGestureRecognizer方法安装手势识别器来处理常用手势

View可以内嵌到其他View,创建复杂的可视层级。这会在嵌入的View(子View)和被嵌入的View(父View)间创建父子关系。正常情况下,子View的可视区域不会被父View的边界剪辑,但在iOS里你可以使用clipsToBounds属性来修改这个行为。一个父View可能有多个子View,但一个子View只有一个父View,它负责确定子View的位置。

View的几何结构是通过它的框架(frame),边界(bounds)和中心(center)属性来定义的。frame定义了View相对父View坐标系的原点和大小,一般在View布局和调整尺寸或位置时使用。center属性可以在不改变view的大小来调整view的位置。bounds定义了view的内部尺寸,几乎只在自定义绘画代码时使用。frame的size部分和bounds矩形部分耦合在一起,所以可以使用两者中的一个或两个同时来改变view的大小。

更多关于如何使用UIView类,参见iOS View编程向导(待翻)

在iOS 2.x时,UIView的最大尺寸是1024*1024点,在iOS 3.0以后,不再强制限制大小,而是受限于内存消耗。让View尽可能小,无论运行在哪个版本的iOS上,都应该考虑避免View明显大于屏幕尺寸

创建一个View

用代码来创建View:
swift:

let viewRect = CGRect(x: 10, y: 10, width: 100, height: 100)
let myView = UIView(frame: viewRect)

Objective-c

CGRect viewRect = CGRectMake(10, 10, 100, 100);
UIView* myView = [[UIView alloc] initWithFrame:viewRect];

这段代码创建了view,并把它放在父view坐标系的(10,10)点(一旦它被加到父view)。添加一个子view到另一个view上,用addSubview:方法。在iOS里,同级的view可以相互覆盖,而不会有任何问题,允许复杂的view布置。addSubview放置指定的view到它的同级view的顶部。你可以使用insertSubview:aboveSubview: 和 insertSubview:belowSubview: 方法指定子view的相对z坐标。你也可以使用exchangeSubviewAtIndex:withSubviewAtIndex: 方法交换已经添加的子view的位置。

View绘画周期

View绘画发生在需要时。当一个view第一次展示时,或者在布局变化时,它的整体或部分变得可见时,系统请求view绘出它的内容。对那些使用UIKit或Core Graphics包含自定义内容的view来说,系统会调用它的drawRect: 方法。你对该方法的实现,负责将view的内容画进当前的图形上下文(graphics context),系统自动优先调用该方法。这里创建了view内容的静态可视展现,接着会被展示在屏幕上。

当view的真实内容发生改变时,你有责任通知系统你的view需要重绘。通过调用view的setNeedsDisplay或setNeedsDisplayInRect:方法来通知。这些方法让系统知道它应该在下次绘画周期更新View.因为它一直等待直到下次绘画周期来更新View,你可以在多个view上调用这些方法,同时更新它们.

如果你用OpenGL ES来绘画,应该用GLKView类来代替UIView.更多信息关于如何用OpenGL ES绘画,参见 iOS OpenGL ES编程向导(待翻)

更多关于View绘画周期和你的view的角色,参见iOS View编程向导(待翻)

动画

改变多个View属性可以被动画展现,在一个短周期时间内,改变属性可以创建动画。UIView类做了很多执行动画的工作,但你仍然必须判断你需要哪些属性改变被动画。下面是两种不同的初始化动画方法:

  • 在iOS4.0及以后的版本中,使用基于块的动画方法(推荐)
  • 使用开始/提交(begin/commit)动画方法。

基于块的动画方法(像animateWithDuration:animations:)极大地简化了动画创建。只要调用一个方法,指定动画的参数,并且执行动画。然后,基于块的动画仅在iOS4及以后版本中可用。如果你的设备运行老版本的iOS,必须使用beginAnimations:context: 和 commitAnimations类方法标记动画开始和结束。

下面的属性是可以动画的:

  • @property frame

  • @property bounds

  • @property center

  • @property transform

  • @property alpha

  • @property backgroundColor

  • @property contentStretch

线程注意事项

操作用户界面必须发生在主线程。因此,你必须在应用主线程调用UIView类的方法。只有在创建view对象自身时可以不用严格遵守,但其他操作必须在主线程中。

子类化(继承)说明

对同样需要用户交互的可见内容来说,UIView类是一个关键的子类化点。尽管有很多好理由继承UIView,但我们只推荐在基本的UIView和系统自带的其他组件不能满足需要时继承UIView.继承UIView会在你实现的代码里消耗更多性能.(Apple写的代码比你的更好,有现成的就用现成的)。

避免子类化的更多信息, 见 ** 选择子类化 (待翻) **

覆盖方法

当子类化UIView时,只有少数的方法你必须覆盖,大多数方法你可以按需覆盖。因为UIView是一个高度可配的类,不用覆盖父类方法,同样有很多实现复杂的行为的途径,这些在 选择子类化 一节中介绍。你可以在你的UIView子类中考虑覆盖下面列表中的方法:

  • 初始化
    • initWithFrame 推荐覆盖。你同样可以实现自定义的初始化方法添加或代替此方法
    • initWithCoder: 如果你从一个Interface Builder的nib文件加载view,并且需要自定义初始化,覆盖该方法。
    • layerClass 仅当你的view需要用不同的Core Animation层后备保存时才要覆盖。例如,当你的view需要用平铺方式显示一块很大的可滚动区域时,你可能想要覆盖该方法返回CATiledLayer类.(不是很明白,原文: Implement this method only if you want your view to use a different Core Animation layer for its backing store. For example, if your view uses tiling to display a large scrollable area, you might want to override this method and return the CATiledLayer class.)
  • 绘画和打印
    • drawRect: 如果你的View画自定义的内容,就要实现该方法,否则避免覆盖该方法。
    • **drawRect:**forViewPrintFormatter: 仅当你需要在打印时,打印不同内容(与显示不同)才需要实现该方法。
  • 约束
    • requiresConstraintBasedLayout - 如果你的View类需要约束才能正常工作,实现该方法
    • updateConstraints 如果你的view需要在子view间创建约束,需要实现该方法
    • alignmentRectForFrame:, frameForAlignmentRect: - 实现这些方法覆盖你的view如何与其他view对齐
  • 布局
    • sizeThatFits: - 当你想在执行resize操作时有一个不同于默认的size,实现该方法。比如,你可以用这个方法阻止view收缩到子view不能正确显示的点
    • layoutSubviews - 如果你需要更精确控制子view,而不是使用限制或autoresizing行为,就需要实现该方法。
    • didAddSubview: , willRemoveSubview: 跟踪子view添加或删除事件
    • willMoveToSuperview:, didMoveToSuperview 跟踪当前view在view层次里的运动
    • willMoveToWindow:,didMoveToWindow 跟踪view(即将或已经)移动到另一个Window
  • 事件处理:
    • touchesBegan:withEvent:, touchesMoved:withEvent:, touchesEnded:withEvent:, touchesCancelled:withEvent: 直接处理触摸事件(如果是手势,使用gesture recognizers)
    • **gestureRecognizerShouldBegin: ** 如果需要直接处理触摸事件,那么需要覆盖该方法,阻止手势识别器触发额外动作。

替代子类化

很多view行为可以配置而不用子类化。在你开始覆盖这些方法时,考虑是否可以修改下面的属性或行为来提供你需要的功能。 * addConstraint: - 为view和它的子view定义自动布局行为. * autoresizingMask - 当父view的frame改变时,提供自动布局行为。这些行为可以和约束(Constraint)合并 * contentMode -为view内容提供布局行为,与view的frame相反。这个属性同样影响内容应用view的缩放,是缓存还是重绘。 * contentStretch-定义view的部分可伸缩。这个行为通常用于实现按钮和其他复杂布局、可变尺寸、重绘代价高的view。 * hiddenalpha 改变view的不透明度或隐藏view * backgroundColor - 设置view的背景颜色 * Subviews 不在drawRect方法里绘制你的内容,使用嵌入图片或文本子view等方式 * Gesture recognizers - 使用手势识别器替代自己手工处理touch事件 * Animations - 使用内建动画支持代替自己写动画。Core Animation提供的动画支持很快很好用 * 基于图片的背景 - 对那些显示相对静态内容的view来说,考虑使用UIImageView对象加上手势识别替代子类化和自己绘制图片。同样,你也可以使用一般的UIView对象,分配你的图片作为view的CALayer对象内容。

动画是不需要子类化和实现复杂代码而让视觉改变的另一种方式。很多UIView的属性是可以动画的,意味着改变这些属性可以触发系统生成动画。启动动画只需要很少的一行代码指示那些改变需要被动画。更多view动画的信息,参考Animations

更多关于外观和行为配置的信息,见UIKit User Interface目录里的About Views

初始化View对象

-initWithFrame: 使用指定的frame矩形,初始化并且返回新的View对象。

声明:
SWIFT:

init(frame aRect: CGRect)

OBJECTIVE-C:

- (instancetype)initWithFrame:(CGRect)aRect

参数
aRect view的frame矩形,使用point测量。frame的原点是相对你将添加到的父view的。这个方法用frame矩形来设置中心点和边界。

返回值:
一个初始化的对象,如果不能创建,返回nil.

讨论
一个新的view对象必须插入到一个windows的view层级里才能使用。如果你用程序创建了一个view对象,这个方法是UIView类的指定初始器。子类可以覆盖这个方法来执行一些自定义的初始化,但必须在第一行调用super实现。
如果你用Interface Builder来设计你的界面,这个方法在从nib文件创建view对象时不会被调用。 nib里的对象使用**initWithCoder:**方法重建和初始化,它会修改view属性来匹配存储在nib文件里的属性。关于view如何从nib文件载入的详细信息,参见Resource Programming Guide.

导入声明
import UIKit

可用性
iOS 2.0及以后版本

配置View的可见外观

backgroundColor:View的背景颜色
声明
SWIFT:

@NSCopying var backgroundColor: UIColor?

OBJECTIVE-C

@property(nonatomic, copy) UIColor *backgroundColor

讨论: 改变这个属性可以动画,默认值是nil,表现为透明

导入声明
import UIKit

可用性
iOS 2.0及以后版本

参见

  • alpha
  • opaque

hidden一个判断view是否隐藏的boolean值

声明
SWIFT:

var hidden: Bool

OBJECTIVE-C

@property(nonatomic, getter=isHidden) BOOL hidden

讨论: 设置这个值为YES隐藏view,NO显示View,默认是NO

一个隐藏的view从窗口消失并且不接收输入事件。然而,它还留在它的父view的子view列表中,并且跟往常一样参与autoresizing。隐藏一个带有子view的view,跟隐藏这些子view(包括他们的子view)有一样的效果。这个效果是含蓄的并且不会修改子view的hidden属性.
如果隐藏的view是窗口的当前第一响应者,会导致下一个view成为第一响应者(得到焦点,是这个意思么?)

导入声明
import UIKit

可用性
iOS 2.0及以后版本

alpha View的不透明度

声明
SWIFT:

var alpha: CGFloat

OBJECTIVE-C

@property(nonatomic) CGFloat alpha

讨论: 这个属性的值是一个浮点值,范围从0.0到1.0,0.0代表完全透明,1.0代表完全不透明。这个值只能当前view有效,不影响内嵌的子view
改变这个属性可以动画

导入声明
import UIKit

可用性
iOS 2.0及以后版本

参见

  • backgroundColor
  • opaque

未完待续

代码来自:http://blog.csdn.net/huli870715/article/details/39378349 原文对三种方法做了测试,这里直接上最优代码。个人感觉效果仍然不是那么好,在ZTE Grand S II(骁龙800+2G RAM)上运行大概需要十几ms,第一次启动会更长。 布局:

Controller的代码:

@ViewById
ImageView image;
@ViewById
TextView text;

@AfterViews
public void afterViews()
{
    applyBlur();
}
private void applyBlur() {
    image.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
        @Override
        public boolean onPreDraw() {
            image.getViewTreeObserver().removeOnPreDrawListener(this);
            image.buildDrawingCache();
            Bitmap bmp = image.getDrawingCache();
            blur2(bmp, text);
            return true;
        }
    });
}

@Override
public void setTopBar() {

}

private void blur2(Bitmap bkg, View view) {
    long startMs = System.currentTimeMillis();
    float radius = 2;
    float scaleFactor = 20;
    Bitmap overlay = Bitmap.createBitmap((int)(view.getMeasuredWidth()/scaleFactor), (int)(view.getMeasuredHeight()/scaleFactor), Bitmap.Config.ARGB_8888);
    Canvas canvas = new Canvas(overlay);
    canvas.translate(-view.getLeft()/scaleFactor, -view.getTop()/scaleFactor);
    canvas.scale(1 / scaleFactor, 1 / scaleFactor);
    Paint paint = new Paint();
    paint.setFlags(Paint.FILTER\_BITMAP\_FLAG);
    canvas.drawBitmap(bkg, 0, 0, paint);
    overlay = ImageUtil.fastblur(getActivity(), overlay, (int) radius);
    view.setBackground(new BitmapDrawable(getResources(), overlay));
    PToast.show("cost " + (System.currentTimeMillis() - startMs) + "ms");
}

fastblur:

/**
* Android api 17实现的虚化
* 某些机型上可能会Crash
*
* @param context
* @param sentBitmap
* @param radius 大于1小于等于25
* @return
*/
@SuppressLint(“NewApi”)
public static Bitmap fastblur(Context context, Bitmap sentBitmap, int radius) {
if (sentBitmap == null) {
return null;
}
if (Build.VERSION.SDK_INT > 16) {
Bitmap bitmap = sentBitmap.copy(sentBitmap.getConfig(), true);

        final RenderScript rs = RenderScript.create(context);
        final Allocation input = Allocation.createFromBitmap(rs,
                sentBitmap, Allocation.MipmapControl.MIPMAP_NONE,
                Allocation.USAGE_SCRIPT);
        final Allocation output = Allocation.createTyped(rs,
                input.getType());
        final ScriptIntrinsicBlur script = ScriptIntrinsicBlur.create(rs,
                Element.U8_4(rs));
        script.setRadius(radius /* e.g. 3.f */);
        script.setInput(input);
        script.forEach(output);
        output.copyTo(bitmap);
        return bitmap;
    }
    return stackblur(sentBitmap, radius);
}

/\*\*
 \* 纯Java实现的虚化,适用老版本api,外部只需调fastblur,会自动判断
 \*
 \* @param sentBitmap
 \* @param radius
 \* @return
 */
private static Bitmap stackblur(Bitmap sentBitmap,
                                int radius) {

    Bitmap bitmap = null;
    try {
        bitmap = sentBitmap.copy(sentBitmap.getConfig(), true);
    } catch (OutOfMemoryError e) {
        e.printStackTrace();
        return sentBitmap;
    }

    if (radius < 1) {
        return (null);
    }

    int w = bitmap.getWidth();
    int h = bitmap.getHeight();

    int\[\] pix = new int\[w * h\];
    bitmap.getPixels(pix, 0, w, 0, 0, w, h);

    int wm = w - 1;
    int hm = h - 1;
    int wh = w * h;
    int div = radius + radius + 1;

    int r\[\] = new int\[wh\];
    int g\[\] = new int\[wh\];
    int b\[\] = new int\[wh\];
    int rsum, gsum, bsum, x, y, i, p, yp, yi, yw;
    int vmin\[\] = new int\[Math.max(w, h)\];

    int divsum = (div + 1) >> 1;
    divsum *= divsum;
    int dv\[\] = new int\[256 * divsum\];
    for (i = 0; i < 256 * divsum; i++) {
        dv\[i\] = (i / divsum);
    }

    yw = yi = 0;

    int\[\]\[\] stack = new int\[div\]\[3\];
    int stackpointer;
    int stackstart;
    int\[\] sir;
    int rbs;
    int r1 = radius + 1;
    int routsum, goutsum, boutsum;
    int rinsum, ginsum, binsum;

    for (y = 0; y < h; y++) {
        rinsum = ginsum = binsum = routsum = goutsum = boutsum = rsum = gsum = bsum = 0;
        for (i = -radius; i <= radius; i++) {
            p = pix\[yi + Math.min(wm, Math.max(i, 0))\];
            sir = stack\[i + radius\];
            sir\[0\] = (p & 0xff0000) >> 16;
            sir\[1\] = (p & 0x00ff00) >> 8;
            sir\[2\] = (p & 0x0000ff);
            rbs = r1 - Math.abs(i);
            rsum += sir\[0\] * rbs;
            gsum += sir\[1\] * rbs;
            bsum += sir\[2\] * rbs;
            if (i > 0) {
                rinsum += sir\[0\];
                ginsum += sir\[1\];
                binsum += sir\[2\];
            } else {
                routsum += sir\[0\];
                goutsum += sir\[1\];
                boutsum += sir\[2\];
            }
        }
        stackpointer = radius;

        for (x = 0; x < w; x++) {

            r\[yi\] = dv\[rsum\];
            g\[yi\] = dv\[gsum\];
            b\[yi\] = dv\[bsum\];

            rsum -= routsum;
            gsum -= goutsum;
            bsum -= boutsum;

            stackstart = stackpointer - radius + div;
            sir = stack\[stackstart % div\];

            routsum -= sir\[0\];
            goutsum -= sir\[1\];
            boutsum -= sir\[2\];

            if (y == 0) {
                vmin\[x\] = Math.min(x + radius + 1, wm);
            }
            p = pix\[yw + vmin\[x\]\];

            sir\[0\] = (p & 0xff0000) >> 16;
            sir\[1\] = (p & 0x00ff00) >> 8;
            sir\[2\] = (p & 0x0000ff);

            rinsum += sir\[0\];
            ginsum += sir\[1\];
            binsum += sir\[2\];

            rsum += rinsum;
            gsum += ginsum;
            bsum += binsum;

            stackpointer = (stackpointer + 1) % div;
            sir = stack\[(stackpointer) % div\];

            routsum += sir\[0\];
            goutsum += sir\[1\];
            boutsum += sir\[2\];

            rinsum -= sir\[0\];
            ginsum -= sir\[1\];
            binsum -= sir\[2\];

            yi++;
        }
        yw += w;
    }
    for (x = 0; x < w; x++) {
        rinsum = ginsum = binsum = routsum = goutsum = boutsum = rsum = gsum = bsum = 0;
        yp = -radius * w;
        for (i = -radius; i <= radius; i++) {
            yi = Math.max(0, yp) + x;

            sir = stack\[i + radius\];

            sir\[0\] = r\[yi\];
            sir\[1\] = g\[yi\];
            sir\[2\] = b\[yi\];

            rbs = r1 - Math.abs(i);

            rsum += r\[yi\] * rbs;
            gsum += g\[yi\] * rbs;
            bsum += b\[yi\] * rbs;

            if (i > 0) {
                rinsum += sir\[0\];
                ginsum += sir\[1\];
                binsum += sir\[2\];
            } else {
                routsum += sir\[0\];
                goutsum += sir\[1\];
                boutsum += sir\[2\];
            }

            if (i < hm) {
                yp += w;
            }
        }
        yi = x;
        stackpointer = radius;
        for (y = 0; y < h; y++) {
            // Preserve alpha channel: ( 0xff000000 & pix\[yi\] )
            pix\[yi\] = (0xff000000 & pix\[yi\]) | (dv\[rsum\] << 16)
                    | (dv\[gsum\] << 8) | dv\[bsum\];

            rsum -= routsum;
            gsum -= goutsum;
            bsum -= boutsum;

            stackstart = stackpointer - radius + div;
            sir = stack\[stackstart % div\];

            routsum -= sir\[0\];
            goutsum -= sir\[1\];
            boutsum -= sir\[2\];

            if (x == 0) {
                vmin\[y\] = Math.min(y + r1, hm) * w;
            }
            p = x + vmin\[y\];

            sir\[0\] = r\[p\];
            sir\[1\] = g\[p\];
            sir\[2\] = b\[p\];

            rinsum += sir\[0\];
            ginsum += sir\[1\];
            binsum += sir\[2\];

            rsum += rinsum;
            gsum += ginsum;
            bsum += binsum;

            stackpointer = (stackpointer + 1) % div;
            sir = stack\[stackpointer\];

            routsum += sir\[0\];
            goutsum += sir\[1\];
            boutsum += sir\[2\];

            rinsum -= sir\[0\];
            ginsum -= sir\[1\];
            binsum -= sir\[2\];

            yi += w;
        }
    }

    bitmap.setPixels(pix, 0, w, 0, 0, w, h);
    return (bitmap);
}

UICollectionView类似于Android里的GridView,九宫格效果。使用起来也不算复杂,跟TableView差不多,之所以写篇博文,是因为地学习过程中遇到了一个大坑,希望对其他新人有所帮助。 环境:Xcode 6.1.1 Swift语言 新建SingleView Application,删除默认的ViewController类,清空Main.storyboard。 往Main.storyboard拖进一个Collection View Controller,默认会有一个Collection View,往Collection View里拖进一个Collection View Cell,这个Cell就相当于是一个Item,接下来往Cell里随便拖个label,button之类的控件,用于测试。 选中cell,切换到Attributes inspector,将Identifier设为”cell”,无引号,字符串。这个id是用于复用item View的,跟Android的Adapter的复用机制一样。 选中Collection View Controller,切到Attributes inspector,将View Controller下的Is Initial View Controller勾选,这一步是声明这个View Controller是第一个ViewController,打开应用时展示这个。 新建Cocoa Touch Class,继承UICollectionViewController,名为CollectionViewController,打开会发现自动生成了一堆代码,很多已经注释掉,我们暂时先不管他们。 注意几个方法:

override func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
    //#warning Incomplete method implementation -- Return the number of sections
    return 1
}

这个是Section数量,所谓Section就是分组。这里返回1,只有一组。

override func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
    //#warning Incomplete method implementation -- Return the number of items in the section
    return 50
}

这个是对应分组的item数量,因为没有分组,不需要判断section,直接返回50

override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(reuseIdentifier, forIndexPath: indexPath) as UICollectionViewCell

    return cell
}

这个是最重要的方法,相当于Android的getView,返回一个item的View,reuseIdentifier就是我们之前在Main.storyboard给Cell定义的id,这里需要把reuseIdentifier常量改为”cell”,与前面定义相同。 运行… 发现问题了没?什么都没有显示…并没有展示我们之前往cell里拖的控件。坑就在这,根据Apple的文档,不能直接给UICollectionViewCell添加subView,而是要添加到UICollectionViewCell的contentView里,我们添加UICollectionViewCell时,并没有自动添加一个contentView,就相当于直接加到了UICollectionViewCell里。但是拖进UITableViewCell是有一个contentView的。这应该是xCode的一个bug吧,解决方法,只有用代码写cell的布局了.

override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
    let cell = collectionView.dequeueReusableCellWithReuseIdentifier(reuseIdentifier, forIndexPath: indexPath) as UICollectionViewCell
    //如果是复用的Cell,viewWithTag就不会返回nil
    var label:UILabel? = cell.contentView.viewWithTag(TAG\_CELL\_LABEL) as? UILabel
    if label == nil {
        //非复用,创建并添加到contentView里
        println("label == nil\\(indexPath.item)")
        label = UILabel(frame: CGRect(x: 0, y: 0, width: 20, height: 20))
        label!.tag = TAG\_CELL\_LABEL
        cell.contentView.addSubview(label!)
    }
    label!.text = "\\(indexPath.item)"
    return cell
}

设计给的验证码输入效果是这样的: verifycode 校验码固定为6位数字,未输入位用一条横线占位,注意,这不是-,而是一条横线,中间的间隔比其他地方的间隔大。这显然不是简单的EditText能实现的。参考了TextView源码后,自定义了一个View实现这个效果。 实现原理,自定义View继承自View,点击时弹出键盘接收用户输入,在onDraw时绘画。代码如下:

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.text.InputType;
import android.util.AttributeSet;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputMethodManager;

import com.pocketdigi.plib.core.PLog;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import me.mailang.secretary.R;

/**
* 验证码输入View
* Created by fhp on 14/12/18.
*/
public class VerifyCodeView extends View {
StringBuilder verifyCodeBuilder;
//一个字符或横线占用的宽度
private int characterWidth;
//一个数字后中间的间隔
private int centerSpacing;
//两字符间隔
private int characterSpacing;
private int textSize;
Paint textPaint;
float textBaseY;
public VerifyCodeView(Context context, AttributeSet attrs) {
super(context, attrs);
//能获取焦点才能弹出软键盘
setFocusableInTouchMode(true);
verifyCodeBuilder = new StringBuilder(6);
textPaint=new Paint(Paint.ANTI_ALIAS_FLAG);
centerSpacing=getResources().getDimensionPixelSize(R.dimen.reg_verifycode_center_spacing);
characterSpacing=getResources().getDimensionPixelSize(R.dimen.reg_verifycode_character_spacing);
textSize=getResources().getDimensionPixelSize(R.dimen.reg_verifycode_textsize);
}

@Override
public boolean onTouchEvent(MotionEvent event) {
    //在View上点击时弹出软键盘
    InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Context.INPUT\_METHOD\_SERVICE);
    //反射调隐藏的focusIn方法,如果不调,在VerifyCodeView之前有EditText等可输入控件时,不会弹出输入法
    //可能有其他google提供的方法,但我没找到
    Class immClass = imm.getClass();
    try {
        Method focusIn = immClass.getDeclaredMethod("focusIn", View.class);
        focusIn.invoke(imm,this);
    } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
        e.printStackTrace();
    }
    imm.viewClicked(this);
    imm.showSoftInput(this, 0);
    return super.onTouchEvent(event);
}

@Override
public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
    //定义软键盘样式为数字键盘
    outAttrs.inputType = InputType.TYPE\_CLASS\_NUMBER;
    return super.onCreateInputConnection(outAttrs);
}

@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
    //接收按键事件,67是删除键(backspace),7-16就是0-9
    if (keyCode == 67 && verifyCodeBuilder.length() > 0) {
        verifyCodeBuilder.deleteCharAt(verifyCodeBuilder.length() - 1);
        //重新绘图
        invalidate();
    } else if (keyCode >= 7 && keyCode <= 16 && verifyCodeBuilder.length() < 6) {
        verifyCodeBuilder.append(keyCode - 7);
        invalidate();
    }
    //到了6位自动隐藏软键盘
    if (verifyCodeBuilder.length() >= 6) {
        InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Context.INPUT\_METHOD\_SERVICE);
        imm.hideSoftInputFromWindow(getWindowToken(), 0);
    }
    return super.onKeyDown(keyCode, event);
}

/\*\*
 \* 获取输入的校验码
 \* @return
 */
public String getVerifyCodeStr() {
    return verifyCodeBuilder.toString();
}

/\*\*
 \* 设置显示的校验码
 \* @param verifyCode
 */
public void setVerifyCode(String verifyCode) {
    if(verifyCodeBuilder.length()>0) {
        verifyCodeBuilder.delete(0, verifyCodeBuilder.length());
    }
    verifyCodeBuilder.append(verifyCode);
    invalidate();
}

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    //计算一个字符的宽度
    if(characterWidth==0) {
        int w = getWidth() - getPaddingLeft() - getPaddingRight();
        characterWidth=(w-centerSpacing-4*characterSpacing)/6;
    }
    textPaint.setTextSize(textSize);
    textPaint.setTextAlign(Paint.Align.CENTER);
    Paint.FontMetrics fontMetrics = textPaint.getFontMetrics();
    float fontHeight = fontMetrics.bottom - fontMetrics.top;
    if(textBaseY==0)
        textBaseY = getHeight() - (getHeight() - fontHeight) / 2 - fontMetrics.bottom;
    //写已输入的验证码
    if(verifyCodeBuilder.length()>0) {
        textPaint.setColor(getResources().getColor(R.color.white));
        String verifyCodeStr = getVerifyCodeStr();
        char\[\] chars = verifyCodeStr.toCharArray();
        int x,y=(int)textBaseY;
        for(int i=0;i

为了防止被反编译,打算把关键代码写到so里(比如加解密),在so里加上判断APk包签名是否一致的代码,避免so被二次打包。其实用JNI读签名就是用了Java的反射机制。 先看Java读取签名的方法:

        try {
            PackageInfo info = context.getPackageManager().getPackageInfo(context.getPackageName(), 64);
            Signature sign = info.signatures[0];
            Log.i("test", "hashCode : " + sign.hashCode());
        } catch (NameNotFoundException e) {
            e.printStackTrace();
        }

然后我们用JNI c语言实现,同样需要传递Context:

int getSignHashCode(JNIEnv *env, jobject context) {
    //Context的类
    jclass context_clazz = (*env)->GetObjectClass(env, context);
    // 得到 getPackageManager 方法的 ID
    jmethodID methodID_getPackageManager = (*env)->GetMethodID(env, context_clazz,
            "getPackageManager", "()Landroid/content/pm/PackageManager;");

    // 获得PackageManager对象
    jobject packageManager = (*env)->CallObjectMethod(env, context,
            methodID_getPackageManager);
//	// 获得 PackageManager 类
    jclass pm_clazz = (*env)->GetObjectClass(env, packageManager);
    // 得到 getPackageInfo 方法的 ID
    jmethodID methodID_pm = (*env)->GetMethodID(env, pm_clazz, "getPackageInfo",
            "(Ljava/lang/String;I)Landroid/content/pm/PackageInfo;");
//
//	// 得到 getPackageName 方法的 ID
    jmethodID methodID_pack = (*env)->GetMethodID(env, context_clazz,
            "getPackageName", "()Ljava/lang/String;");

    // 获得当前应用的包名
    jstring application_package = (*env)->CallObjectMethod(env, context,
            methodID_pack);
    const char *str = (*env)->GetStringUTFChars(env, application_package, 0);
    __android_log_print(ANDROID_LOG_DEBUG, "JNI", "packageName: %s\n", str);

    // 获得PackageInfo
    jobject packageInfo = (*env)->CallObjectMethod(env, packageManager,
            methodID_pm, application_package, 64);

    jclass packageinfo_clazz = (*env)->GetObjectClass(env, packageInfo);
    jfieldID fieldID_signatures = (*env)->GetFieldID(env, packageinfo_clazz,
            "signatures", "[Landroid/content/pm/Signature;");
    jobjectArray signature_arr = (jobjectArray)(*env)->GetObjectField(env,
            packageInfo, fieldID_signatures);
    //Signature数组中取出第一个元素
    jobject signature = (*env)->GetObjectArrayElement(env, signature_arr, 0);
    //读signature的hashcode
    jclass signature_clazz = (*env)->GetObjectClass(env, signature);
    jmethodID methodID_hashcode = (*env)->GetMethodID(env, signature_clazz,
            "hashCode", "()I");
    jint hashCode = (*env)->CallIntMethod(env, signature, methodID_hashcode);
    __android_log_print(ANDROID_LOG_DEBUG, "JNI", "hashcode: %d\n", hashCode);
    return hashCode;
}

第二个参数jobject不是JNI函数的默认参数,而是传入的Context实例。

xcode设置里有下载文档功能,但是,速度奇慢,不到10k,下了半天也不见进度条动。于是想找找有没有其他方法,比如说国内很好用的迅雷去下载。网上搜索到大多是介绍xcode 4.x的下载方法,已经不适用。今天折腾了大半天,总算搞定,跟大家分享下。 原理很简单,用到了一个代理软件Charles,最新版3.9.3,它可不仅仅可以架设代理服务器,也可以分析通过这个代理服务器访问的所有请求,包括使用SSL的请求,另外,它还有将请求映射到本地文件的功能,说到这,大家应该知道怎么玩了。开启Charles,先在xcode上点下载,Charles得到文档的url,用迅雷下载到该文件,再在charles上配置映射功能,将文档的url映射到本地文件,然后再次点下载,几秒钟下载完(因为文件在本地). 有一点需要注意:因为Apples使用了SSL,所以需要配置Charles使用SSL,添加Apple的Host.Charles如果要使用SSL需要安装Charles的证书,下载地址:http://www.charlesproxy.com/ssl.zip,解压安装证书。