Android 3.0(Honeycomb)之前,Android支持两种动画tween animation,frame animation,3.0引入了一个新的动画————property animation。可通过NineOldAndroids实现对低版本的支持。

View Animation(Tween Animation)补间动画

给出两个关键帧,通过一些算法将给定属性值在给定的时间内在两个关键帧间渐变。View Animation只能应用于View对象,而且只支持一部分属性,如支持缩放旋转不支持背景颜色的改变。
ViewAnimation就是一系列View形状的变换,如大小的缩放、透明度的改变、位置的改变,动画的定义既可以用代码定义也可以使用XML定义。
用XML定义的动画在/res/anim/文件夹内,XML文件的根元素可以是,,,,。默认情况下,所有动画是同时进行的,可以通过startOffset属性设置哥哥动画的开始偏移量来达到动画顺序播放的效果。可以设置属性改变动画的渐变效果,如AccelerateInterpolator,开始时慢,然后逐渐加快。

1
2
Animation hyperspaceJumpAnimation=AnimationUtils.loadAnimation(this, R.anim.hyperspace_jump);
spaceshipImage.startAnimation(hyperspaceJumpAnimation);

Drawable Animation(Frame Animation)

Drawable Animation(Frame Animation)帧动画,通过一系列Drawable依次显示来模拟动画

1
2
3
4
5
6
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
android:oneshot="true">
<item android:drawable="@drawable/rocket_thrust1" android:duration="200" />
<item android:drawable="@drawable/rocket_thrust2" android:duration="200" />
<item android:drawable="@drawable/rocket_thrust3" android:duration="200" />
</animation-list>

必须以为根元素,以表示要轮换显示的图片,duration属性表示各项显示的时间,XML文件要放在/res/drawable/目录下。

1
2
3
4
5
imageView.setBackgroundResource(R.drawable.drawable_anim);
anim = (AnimationDrawable) imageView.getBackground();

anim.stop()
anim.start()

Property Animation

属性动画

  • Duration:动画的持续时间
  • TimeInterpolation:属性值的计算方式,比如先快后慢
  • TypeEvaluator:根据属性的开始、结束值与TimeInterpolation计算出的因子计算出当前时间的属性值。
  • Repeat Count and behavoir:重复次数与方式,比如播放3次、5次、无限循环,可以一直重复或播放完再反向播发
  • Animation Sets:动画集合,即可以同时对一个对象应用几个动画,这些动画可以同时播放也可以对不同动画设置不同时间开始偏移
  • Frame refreash delay:多少时间刷新一次,即每隔多少时间计算一次属性值,默认是10ms,最终刷新时间还受到系统进程调度与硬件的影响

Property Animation的工作方式

这面的动画该对象在40ms在x轴移动40pixel。默认10ms刷新一次,这个对象移动了4次,每次移动40/4=10pixel


也可以改变属性值的改变方法,设置不同的interpolation,比如下图运动的速度逐渐增大后再逐渐减小

下图显示了与上述动画相关的关键对象


ValueAnimator表示一个动画,包含动画的开始值、结束值、持续时间等属性
ValueAnimator封装了一个TimeInterpolator,定义了属性在开始值与结束值之间的插值计算方法
ValueAnimator还封装了一个TypeAnimator,根据开始值、结束值与TimeIniterpolator计算得到的值计算出属性值
ValueAnimator根据动画已进行的时间跟动画总时间(duration)的比计算出一个时间因子(0~1),然后根据TimeInterpolator计算出另一个因子,最后TypeAnimator通过这个因子计算出属性值,如上例中10ms时:
首先计算出时间因子,即经过的时间百分比:t=10ms/40ms=0.25
经插值计算(inteplator)后的插值因子:大约为0.15,上述例子中用了AccelerateDecelerateInterpolator,计算公式为(input即为时间因子):

1
(Math.cos((input + 1) * Math.PI) / 2.0f) + 0.5f;

最后根据TypeEvaluator计算出在10ms时的属性值:0.15*(40-0)=6pixel。上例中TypeEvaluator为FloatEvaluator,计算方法为 :

1
2
3
4
public Float evaluate(float fraction, Number startValue, Number endValue) {
float startFloat = startValue.floatValue();
return startFloat + fraction * (endValue.floatValue() - startFloat);
}

ValueAnimator

ValueAnimator包含Property Animation动画的所有核心功能,如动画时间,开始、结束属性值,相应时间属性值计算方法等。应用Property Animation有两个步聚:

  1. 计算属性值
  2. 根据属性值执行相应的动作,如改变对象的某一属性。

ValuAnimiator只完成了第一步工作,如果要完成第二步,需要实现ValueAnimator.onUpdateListener接口,这个接口只有一个函数onAnimationUpdate(),在这个函数中会传入ValueAnimator对象做为参数,通过这个ValueAnimator对象的getAnimatedValue()函数可以得到当前的属性值如:

1
2
3
4
5
6
7
8
9
10
ValueAnimator animation = ValueAnimator.ofFloat(0f,1f);
animation.setDuration(1000);
animation.addUpdateListener(new AnimatorUpdateListener(){
@Override
public void onAnimationUpdate(ValueAnimator animation) {
Log.i("update",((Float)animation.getAnimatedValue()).toString());
}
});
animation.setInterpolator(new CycleInterpolator(3));
animation.start();

onAnimationUpdate()通过监听这个事件的值更新时执行的操作,对ValueAnimation一般要监听此事件执行相应的动作,不然则没有意义。ObjectAnimator中会自动更新属性,如无必要不必监听。在函数中会传递一个ValueAnimator参数,通过此参数的getAnimatedValue()取得当前动画属性值。
AnimatorListener用于监听动画的执行过程,有如下一些回调方法

1
2
3
4
onAnimationStart()
onAnimationEnd()
onAnimationRepeat()
onAnimationCancel()

可以继承AnimatorListenerAdapter而不是实现AnimatorListener接口来简化操作,这个类对AnimatorListener中的函数都定义了一个空函数体,这样我们就只用定义想监听的事件而不用实现每个函数却只定义一空函数体。

1
2
3
4
5
6
7
8
ObjectAnimator oa = ObjectAnimator.ofFloat(tv,"alpha",0f,1f);
oa.setDuration(3000);
oa.addListener(new AnimatorListenerAdapter(){
public void on AnimationEnd(Animator animation){
Log.i("Animation","end");
}
});
oa.start();

ObjectAnimator

继承自ValueAnimator,要指定一个对象即带对象的一个属性,当属性值计算完成时,自动设置为该对象的相应属性。使用ObjectAnimator,需要满足下面条件

  • 对象应该有一个setter函数
  • 创建时,使用工厂方法,第一个参数是对象名,第二个为属相名,后面的参数为可变参数,只设置一个值为目的值,属性值的变化范围为当前值到目的值,为了获得当前值,对象必须有相应的getter方法
  • getter方法与其setter方法返回类型一致
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    tv=(TextView)findViewById(R.id.textview1);
    btn=(Button)findViewById(R.id.button1);
    btn.setOnClickListener(new OnClickListener() {
      @Override
      public void onClick(View v) {
        ObjectAnimator oa=ObjectAnimator.ofFloat(tv, "alpha", 0f, 1f);
        oa.setDuration(3000);
        oa.start();
      }
    });

View对象属性:

  1. translationX和translationY:增量位置,View从它布局容器的左上角坐标开始的位置
  2. rotation、rotationX和ratationY:这三个属性控制View对象围绕支点进行2D和3D的旋转。
  3. scaleX、scaleY:控制对象围绕其支点进行2D缩放
  4. pivotX、pivotY:控制支点的位置,默认为View对象的中心点
  5. x、y:控制View对象在它容器中的最终位置
  6. alpha:View对象的透明度,默认为1(不偷明),0代表完全透明

    通过AnimationSet应用多个动画

    AnimationSet提供一个把多个动画组合成一个组合的机制,并可以设置组中动画的时序关系。
    1
    2
    3
    4
    5
    6
    7
    // anim1播发完,同时播发anim2、anim3、anim4,之后播发anim5
    AnimationSet bouncer = new AnimatorSet();
    bouncer.play(anim1).before(anim2);
    bouncer.play(anim2).with(anim3);
    bouncer.play(anim2).with(anim4);
    bouncer.play(anim4).with(anim2);
    bouncer.start();

TypeEvalutors

根据属性的开始值、结束值与TimeInterpolation计算出的计算因子计算出当前时间的属性值,Android提供方了以下几个evalutor:

  • IntEvaluator:属性的值为int
  • FloatEvaluator:属性的值为float
  • ArgbEvaluator:属性值为十六进制颜色值
  • TypeEvaluator:一个接口,可以通过实现该接口自定义Evaluator
    自定义TypeEvalutor很简单,只需要实现一个方法
    1
    2
    3
    4
    5
    6
    public class FloatEvaluator implements TypeEvaluator {
    public Object evaluate(float fraction,Object startValue,Object endValue) {
    float startFloat = ((Number)startValue).floatValue();
    return startFloat + fraction * (((Number) endValue).floatValue() - startFloat();
    }
    }

TimeInterplator

Time interplator定义了属性值变化的方式,在Property Animation中是TimeInterplator,在View Animation中是Interplator,这两个是一样的,在3.0之前只有Interplator,3.0之后实现代码转移至了TimeInterplator。Interplator继承自TimeInterplator,内部没有任何其他代码。

  • AccelerateInterpolator      加速,开始时慢中间加速
  • DecelerateInterpolator       减速,开始时快然后减速
  • AccelerateDecelerateInterolator  先加速后减速,开始结束时慢,中间加速
  • AnticipateInterpolator       反向 ,先向相反方向改变一段再加速播放
  • AnticipateOvershootInterpolator  反向加回弹,先向相反方向改变,再加速播放,会超出目的值然后缓慢移动至目的值
  • BounceInterpolator        跳跃,快到目的值时值会跳跃,如目的值100,后面的值可能依次为85,77,70,80,90,100
  • CycleIinterpolator         循环,动画循环一定次数,值的改变为一正弦函数:Math.sin(2 mCycles Math.PI * input)
  • LinearInterpolator         线性,线性均匀改变
  • OvershottInterpolator       回弹,最后超出目的值然后缓慢改变到目的值
  • TimeInterpolator         一个接口,允许你自定义interpolator,以上几个都是实现了这个接口

    Layout改变时应用动画

    ViewGroup中的子元素可以通过setVisibility使其Visible、Invisible或Gone,当有子元素可见性改变时(VISIBLE、GONE),可以向其应用动画,通过LayoutTransition类应用此类动画:
    1
    transition.setAnimator(LayoutTransition.DISAPPEARING,customDisappearingAnim);

通过setAnimator应用动画,第一个参数表示应用环境,第二个参数是一个动画,环境可以是以下4种:

  • APPEARING 当一个元素在其父元素中变为Visible时对这个元素应用动画
  • CHANGE_APPEARING 当一个元素在其父类中变为Visible时,因系统要重新布局有一些元素需要移动,对这些要移动的元素应用动画
  • DISAPPEARING 当一个元素在其父元素中变为GONE时对其应用动画
  • CHANGE_DISAPPEARING 当一个元素在其父元素中变为GONE时,因系统要重新布局有一些元素需要移动,这些要移动的元素应用动画
    1
    mTransitioner.setStagger(LayoutTransition.CHANGE_APPEARING, 30);

此函数设置动画延迟时间,参数分别为类型与时间。

Keyframes

keyFrame是一个时间/值对,通过它可以定义一个在特定时间的特定状态,即关键帧,而且在两个keyFrame之间可以定义不同的Interpolator,就像多个动画的拼接,第一个动画的结束点是第二个动画的开始点,KeyFrame是一个抽象类,要通过ofInt()、ofFloat()、ofObject()获得适当的KeyFrame,然后通过PropertyValuesHolder.ofKeyframe获得PropertyValueHolder对象,如下例子:

1
2
3
4
5
6
7
8
Keyframe kf0 = Keyframe.ofInt(0, 400);
Keyframe kf1 = Keyframe.ofInt(0.25f, 200);
Keyframe kf2 = Keyframe.ofInt(0.5f, 400);
Keyframe kf4 = Keyframe.ofInt(0.75f, 100);
Keyframe kf3 = Keyframe.ofInt(1f, 500);
PropertyValuesHolder pvhRotation = PropertyValuesHolder.ofKeyframe("width", kf0, kf1, kf2, kf4, kf3);
ObjectAnimator rotationAnim = ObjectAnimator.ofPropertyValuesHolder(btn2, pvhRotation);
rotationAnim.setDuration(2000);

上述代码设置了btn对象的width属性值:

  1. 开始动画时,width=400
  2. 开始动画1/4时,width=200
  3. 开始动画1/2时,width=400
  4. 开始动画3/4时,width=100
  5. 结束动画时,width=500
    第一个参数是时间百分比,第二个参数是在第一个参数时间点的属性值
    定义了一些Keyframe后,通过PropertyValuesHolder类的方法ofKeyframe一个PropertyValuesHolder对象,然后通过ObjectAnimator.ofPropertyValuesHolder获得一个Animator对象。
    用下面的代码可以实现同样的效果(上述代码时间值是线性,变化均匀):
    1
    2
    3
    ObjectAnimator oa = ObjectAnimator.ofInt(btn2,"width",400,200,400,100,500);
    oa.setDuration(2000);
    oa.start();

Animating Views

  • 第一类:属性值为truefalse
属性 说明
android:layout_centerHrizontal 水平居中
android:layout_centerVertical 垂直居中
android:layout_centerInparent 相对于父元素完全居中
android:layout_alignParentBottom 贴紧父元素的下边缘
android:layout_alignParentLeft 贴紧父元素的左边缘
android:layout_alignParentRight 贴紧父元素的右边缘
android:layout_alignParentTop 贴紧父元素的上边缘
android:layout_alignWithParentIfMissing 如果对应的兄弟元素找不到的话就以父元素做参照物
  • 第二类:属性值必须为id的引用名“@id/id-name”
属性 说明
android:layout_below 在某元素的下方
android:layout_above 在某元素的的上方
android:layout_toLeftOf 在某元素的左边
android:layout_toRightOf 在某元素的右边
android:layout_alignTop 本元素的上边缘和某元素的的上边缘对齐
android:layout_alignLeft 本元素的左边缘和某元素的的左边缘对齐
android:layout_alignBottom 本元素的下边缘和某元素的的下边缘对齐
android:layout_alignRight 本元素的右边缘和某元素的的右边缘对齐
  • 第三类:属性值为具体的像素值,如30dip,40px
属性 说明
android:layout_marginBottom 离某元素底边缘的距离
android:layout_marginLeft 离某元素左边缘的距离
android:layout_marginRight 离某元素右边缘的距离
android:layout_marginTop 离某元素上边缘的距离

Handler直接继承Object,一个Handler允许发送和处理一个Message或者Runnable对象,并且会关联到主线程的MessageQueue中。没个Handler具有一个单独的线程,并且关联到一个消息对象的线程,就是说一个Handler有一个固定的消息队列。
Handler主要有两个作用:

  • 在工作线程中发送消息
  • 在UI线程中获取、处理消息

Handler把压入消息队列分为Post和sendMessage:

  • Post:Post运行把一个Runnable对象压入消息队列,它的方法有:post(Runnable)、postAtTime(Runnable,long)、postDelayed(Runnable,long)。
  • sendMessage:sendMessage允许把一个包含消息数据的Message对象压入到消息队列中,方法有sendEmptyMessage(int)、sendMessage(Message)、sendMessageAtTime(Message,long)、sendMessageDelayed(Message,long)。

Post

对于Handler的Post方式来说,它会传递一个Runnable对象到消息队列中,在这个Runnable对象中,重写run()方法。一般在这个run()方法中写入需要在UI线程上的操作。

在Handler中,关于Post方式的方法有:

  • boolean post(Runnable r):把一个Runnable入队到消息队列中,UI线程从消息队列中取出这个对象后,立即执行。
  • boolean postAtTime(Runnable r,long uptimeMillis):把一个Runnable入队到消息队列中,UI线程从消息队列中取出这个对象后,在特定的时间执行。
  • boolean postDelayed(Runnable r,long delayMillis):把一个Runnable入队到消息队列中,UI线程从消息队列中取出这个对象后,延迟delayMills秒执行
  • void removeCallbacks(Runnable r):从消息队列中移除一个Runnable对象。

Message

Message是一个final类,所有不可继承,Message封装了线程中传递的消息,对于一般的数据Message提供了getData()setData()方法来获取与设置数据,其中操作的数据是一个Bundle对象,这个Bundle对象提供了一系列的getXxx()setXxx()方法用于传递基本数据类型的键值对。对于复杂的数据类型,Bundle提供了两个方法,专门用来传递对象,但是这两个方法也有相应的限制,需要实现特定的接口。

  • putParcelable(String key,Parcelable value):需要传递的对象类实现Parcelable接口。
  • pubSerializable(String key,Serializable value):需要传递的对象类实现Serializable接口。
    除此之外Message自带的obj属性也可以用于传值,它是一个Object类型,可以传递任何类型的对象,Message自带的如下几个属性:
  • int arg1:参数一,传递不复杂数据
  • int arg2:参数二,传递不复杂数据
  • Object obj:传递任意的对象
  • int what:定义消息码,一般用于消息的标志

对于Message对象,一般不推介直接使用构造方法创建,而是使用Message.obtain()这个静态方法或者Handler.obtai()获取,此两者都是从消息池中获取,消息的数量是有上限的,为10个。

向Handler发送消息一般分两种:一种是根据Handler对象,使用handler.sendMessage()方法来发送消息,一种是根据Handle获取Message,如handler.obtai()或者Message.obtain(handler),该Message会有一个属性Target,调用sendToTarget()方法,会发送到创建时的Handler中去。
Handler中,与Message发送消息相关的方法有:

  • Message obtainMessage():获取一个Message对象。
  • boolean sendMessage():发送一个Message对象到消息队列中,并在UI线程取到消息后,立即执行。
  • boolean sendMessageDelayed():发送一个Message对象到消息队列中,在UI线程取到消息后,延迟执行。
  • boolean sendEmptyMessage(int what):发送一个空的Message对象到队列中,并在UI线程取到消息后,立即执行。
  • boolean sendEmptyMessageDelayed(int what,long delayMillis):发送一个空Message对象到消息队列中,在UI线程取到消息后,延迟执行。
  • void removeMessage():从消息队列中移除一个未响应的消息。

ids.xml

/res/values/ids.xml

ids.xml是区别于R文件的一种设置控件ID的方式。使用示例如下:

  1. 控件定义时
1
2
3
4
<Button 
android:id = "@id/button_ok"
...
/>
  1. 在ids文件中添加
1
2
3
4
<resources>
<item type="id" name="button_ok">false</item>
...
</resources>

3.在调用控件时

1
2
Button bn = new Button(context);
bn.setId(R.id.button_ok); // 区别于 bn.setId(context.getResources().getInteger(R.id.button_ok));

使用ids.xml的优点如下

  1. 命名方便,可以先将控件先命名好,在布局时直接命名
  2. 使用代码布局时,不需要转换
  • 注意:在ids.xml中的每一项也会生成到R文件中

arrays.xml

用于包装数组
// 在arrays.xml中定义

1
2
3
4
5
6
7
8
9
10
11
<resources>
<string-array name="week">
<item>Sunday</item>
<item>Monday</item>
<item>Tuesday</item>
<item>Wednesday</item>
<item>Thursday</item>
<item>Friday</item>
<item>Saturday</item>
</string-array>
</resource>

在Java中调用

1
CharSequence[] items = this.getResources().getStringArray(R.array.reboot_item);

attrs.xml

attrs.xml用于设定自定义属性

  1. res/values文件夹下定义一个attrs.xml文件

    1
    2
    3
    4
    5
    6
    <?xml version="1.0" encoding="utf-8" ?>
    <resources>
    <declare-styleable name="MyView">
    <attr name="textColor" format="color" />
    </declare-styleable>
    </resource>
  2. 在Java调用自定义属性

    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
    public class MyView extends View {
    private Paint mPaint;
    private Context mContext;

    public MyView(Context context) {
    super(context);
    mPaint = new Paint();
    }

    public MyView(Context context,AttributeteSet atts){
    super(context,attrs);
    mPaint = new Paint();
    TypedArray a = context.obtainStyledAttributes(attrs,R.styleable.MyView); 
    // R.styleable.MyView_textColor是读取attrs中参数名,以"样式名_参数名"的形式
    // 第二个参数为默认值,如果从xml中获取不到则使用默认值
    int textColor = a.getColor(R.styleable.MyView_textColor,0XFFFFFFFF);
    mPaint.setTextColor(textColor);
    a.recycle();
    }
    @Override    
      protected void onDraw(Canvas canvas) {    
     super.onDraw(canvas);     
         //设置填充     
         mPaint.setStyle(Style.FILL);      
         //画一个矩形,前俩个是矩形左上角坐标,后面俩个是右下角坐标     
         canvas.drawRect(new Rect(10, 10, 100, 100), mPaint);         
         mPaint.setColor(Color.BLUE);     
         //绘制文字     
         canvas.drawText(mString, 10, 110, mPaint);        
      }
    }
  3. 布局时使用属性

    1
    2
    3
    4
    5
    6
    7
    <?xml version="1.0" encoding="utf-8"?>   
    <com.android.tutor.MyView   
        android:layout_width="fill_parent"    
        android:layout_height="fill_parent"    
        test:textSize="20px"  
        test:textColor="# fff"  
    />

  • anim
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
<?xml version="1.0" encoding="utf-8"?>
<!-- oneshot="false" 循环播放 -->
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
android:oneshot="false" >

<item android:duration="60">
<clip
android:clipOrientation="horizontal"
android:drawable="@drawable/loading_1"
android:gravity="left" >
</clip>
</item>
<item android:duration="60">
<clip
android:clipOrientation="horizontal"
android:drawable="@drawable/loading_2"
android:gravity="left" >
</clip>
</item>
<item android:duration="60">
<clip
android:clipOrientation="horizontal"
android:drawable="@drawable/loading_3"
android:gravity="left" >
</clip>
</item>
<item android:duration="60">
<clip
android:clipOrientation="horizontal"
android:drawable="@drawable/loading_4"
android:gravity="left" >
</clip>
</item>
<item android:duration="60">
<clip
android:clipOrientation="horizontal"
android:drawable="@drawable/loading_5"
android:gravity="left" >
</clip>
</item>
<item android:duration="60">
<clip
android:clipOrientation="horizontal"
android:drawable="@drawable/loading_6"
android:gravity="left" >
</clip>
</item>
<item android:duration="60">
<clip
android:clipOrientation="horizontal"
android:drawable="@drawable/loading_7"
android:gravity="left" >
</clip>
</item>
<item android:duration="60">
<clip
android:clipOrientation="horizontal"
android:drawable="@drawable/loading_8"
android:gravity="left" >
</clip>
</item>

</animation-list>
  • layout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<!-- android:indeterminate="false" 不明确滚动的数值 -->

<ProgressBar
android:id="@+id/progressBar"
android:layout_width="63dp"
android:layout_height="63dp"
android:layout_gravity="center"
android:indeterminate="false"
android:indeterminateDrawable="@anim/progress_bar_anim"
android:scaleType="centerInside" />

</FrameLayout>

例子

模块与SDK包

AVOS Cloud基本存储模块

  • avoscloud- 版本号.jar
  • android-async-http-1.4.4-fix.jar
  • fastjson.jar
  • httpmime-4.2.4.jar

AVOS Cloud 推送模块

  • AVOS Cloud 基础存储模块
  • avospush- 版本号.jar

AVOS Cloud 统计模块

  • AVOS Cloud 基础存储模块
  • avosstatistics- 版本号.jar

AVOS Cloud SNS 模块

  • AVOS Cloud 基础存储模块
  • weibo.sdk.android.sso.jar
  • qq.sdk.1.6.1.jar

简介

AVOSCloud提供了一个完整的后端解决方案

应用程序

在 AVOS Cloud 平台注册后,您创建的每个应用程序都有其自己的应用程序 ID 和 Key, 在您的应用程序中将凭此 ID 和 Key 使用 AVOS Cloud SDK。您的账户可以创建容纳多个应用程序,这是非常方便和有用的。即使您只有一个应用程序,也可以使用不同的版本进行测试和生产。

对象

AVOS Cloud存储的数据是建立在AVObject基础上,每个AVObject包含键(key)-值(value)对的JSON兼容的数据。
键必须是字母、数字的字符串,值可以是字符串、数字、布尔值、JSON数组和AVObject对象等。每个AVObject有一个类名,你可以用它来区分各种不同的数据。

保存对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
   AVObject myObj = new AVObject("MyObject");
myObj.put("value1",123);
myObj.put("value2",true);
myObj.put("value3","hello");
try{
myObj.save();
}
catch (AVException e) {
e.getMessage();
}
myObj.saveInBackground(new SaveCallback() {
@Override
public void done(AVException e) {
if (e == null) {
// 保存成功
} else {
// 保存失败
}
}
});

检索对象

使用AVQuery通过ObjectID检索到一个完整的AVObject

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
AVQuery<AVObject> query = new AVQuery<AVObject>("MyObject");
AVObject myObj;
try {
myObj = query.get("51c912bee4b012f89e344ae9");
} catch (AVException e) {
// e.getMessage();
}
query.getInBackground("51c912bee4b012f89e344ae9", new GetCallback<AVObject>() {
@Override
public void done(AVObject obj, AVException e) {
if (e == null) {
// 获取成功
} else {
// 获取失败
}
}
});

更新对象

获取AVObject对象,然后进行修改值后保存数据

1
2
3
4
5
6
7
8
   AVQuery<AVObject> query = new AVQuery<AVObject>("MyObject");
AVObject myObj;
try {
myObj = query.get("51c912bee4b012f89e344ae9");
myObj.put("value3", "hello world");
} catch (AVException e) {
// e.getMessage();
}

计数器

1
2
myObj.increment("value1");
// myObj.increment(key,amount);方法可以递增递减任意幅度的数字

更新后获取最新值

设置fetchWhenSave属性为true会使更新后,AVObject获得最新值

1
2
3
4
5
6
7
8
myObj.setFetchWhenSave(true);
myObj.increment("value1");
myObj.saveInBackground(new SavaCallback(){
@Override
public void done(AVException e){
//
}
});

删除对象

从服务器删除对象

1
2
3
4
5
6
7
myObj.deleteInBackground();
// 删除value3字段
myObj.remove("value3");
myObj.saveInBackground();
// 批量删除对象
List<AVObject> objects = ...
AVObject.deleteAll(objects);

关联数据

对象可以与其他对象相联系,就像数据库中的主外键关系一样,数据表A的某一个字段是数据表B的外键,只有表B中存在的数据才插入进A中的字段。

1
2
3
4
5
6
7
8
AVObject myWeibo = new AVObject("Post");
myWeibo.put("content", "正文");
AVObject myConment = new AVObject("Comment");
myConment.put("content", "评论");
myConment.put("post",myWeibo);
myConment.saveInBackground();
// 通过objectId来关联已用对象
myComment.put("post",AVObject.createWithoutData("Post", "1zEcyElZ80"));

默认情况下,获取一个对象的时候,关联的AVObject不会被获取,这些对象的值无法获取,直到调用fetch

1
2
3
4
5
6
myConment.getAVObject("post").fetchIfNeededInBackground(new GetCallback<AVObject>() {
@Override
public void done(AVObject object, AVException e) {
String content = object.getString("content");
}
});

使用AVRelation来建模多对多关系。
比如一个User喜欢很多Post,可以用getRelation方法保存一个用户喜欢的用户的Post集合。

1
2
3
4
5
6
AVUser user = AVUser.getCurrentUser();
AVRelation<AVObject> relation = user.getRelation("likes");
relation.add(post);
user.saveInBackground();
// 从AVRelation中移除一个Post
relation.remove(post)

默认情况,处于关系的对象集合不会被下载,可以通过getQuery方法返回的AVQuery对象,使用它的findInBackground方法来获取Post链表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
relation.getQuery().findInBackground(new FindCallback<AVObject>(){
void done(List<AVObject> result,AVException e){
if(e == null){
//
} else {
//
}
}
});
// 获取链表的一个子集合,可以添加更多的约束条件到`getQuery`返回`AVQuery`对象
AVQuery<AVObject> query = relation.getQuery();
// 已持有一个post对象,想知道它被哪些User所喜欢,反查询
AVQuery<AVObject> userQuery = AVRelation.reverseQuery("_User","likes",myPost);
userQuery.findInBackground(new FindCallBack<AVObject>(){
@Override
public void done(List<AVObject> users,AVException e){
//
}
});

数据类型

支持的数据类型有StringIntBooleanAVObject,同时支持java.util.Datebyte[]JSONObjectJSONArray数据类型。

查询

基本查询

先创建一个AVQuery对象,然后通过添加不同的条件,使用findInBackground方法结合FindCallback回调类来查询与条件匹配的AVObject数据,使用whereEqualTo方法来添加条件值

1
2
3
4
5
6
7
8
9
10
11
AVQuery<AVObject> query = new AVQuery<AVObject>("MyObject");
query.whereEqualTo("value1","value2");
query.findInBackgroud(new FindCallback<AVObject>(){
public void done(List<AVOject> object,AVException e){
if (e == null) {
//
} else {
//
}
}
});

查询条件
  • whereNotEqualTo() 不等于
  • setLimit() 限制结果的个数
  • setSkip() 忽略多少个
  • orderByAscending() 升序排列
  • orderByDescending() 降序排列
  • whereLessThan() 小于
  • whereLessThanOrEqualTo() 小于等于
  • whereGreaterThan() 大于
  • whereGreaterThanOrEqualTo() 大于等于

想查询匹配几个不同值的数据,如:要查询”steve”、”chard”、”jack”三个人的成绩,可以使用whereContainedIn方法来实现,排除可以使用whereNotContainedIn方法

1
2
String[] names = {"steve","chard","jack"};
query.whereContainedIn("playName",Arrays.asList(names));

使用whereMatches方法可以使用任何正确的正则表达式来检索匹配的值

1
2
3
4
5
6
7
8
9
// 比较name字段的值是以大写字母和数字开头
AVQuery<AVObject> query = new AVQuery<AVObject>("GameScore");
query.whereMatches("name", "^[A-Z]\\d");

query.findInBackground(new FindCallback<AVObject>() {
public void done(List<AVObject> sauceList, AVException e) {

}
});

查询字符串中包含“XX“内容,可用如下方法:

1
2
3
4
5
6
7
8
9
10
11
// 查询playerName字段的值中包含“ste“字的数据
AVQuery query = new AVQuery("GameSauce");
query.whereContains("playerName", "ste");

// 查询playerName字段的值是以“cha“字开头的数据
AVQuery query = new AVQuery("GameSauce");
query.whereStartsWith("playerName", "cha");

// 查询playerName字段的值是以“vj“字结尾的数据
AVQuery query = new AVQuery("GameSauce");
query.whereEndsWith("playerName", "vj");

数组查询

如果key对应的值是一个数组,可以查询key的数组包含了数字2的所有对象

1
query.whereEqualTo("arrayKey",2);

同样,你可以查询出 Key 的数组同时包含了 2,3 和 4 的所有对象:

1
2
3
4
5
6
//查找出所有arrayKey对应的数组同时包含了数字2,3,4的所有对象。
ArrayList<Integer> numbers = new ArrayList<Integer>();
numbers.add(2);
numbers.add(3);
numbers.add(4);
query.whereContainsAll("arrayKey", numbers);

字符串的查询

使用 whereStartsWith 方法来限制字符串的值以另一个字符串开头。非常类似 MySQL 的 LIKE 查询,这样的查询会走索引,因此对于大数据集也一样高效:

1
2
3
//查找出所有username以avos开头的用户
AVQuery<AVObject> query = AVQuery.getQuery("_User");
query.whereStartsWith("username", "avos");

查询对象个数

query使用count替代find可以统计多少个对象满足查询

1
2
3
4
5
6
7
8
query.countInBackgroud(new CountCallback(){
@Ovrride
public void done (int count,AVException e) {
if(e == null){
// count 就是符合查询条件的个数
}
}
});

字符串和字符(String and Character)

Swift的String类型和Foundation的NSString类进行了无缝桥接,所有NSString API都可以调用Swift的String类型的值

字符串字面量(String Literals)

字符串字面量是由双引号(“”)包裹的具有固定顺序的文本字符集
字符串字面量可以包含以下特殊字符:

  • 转义字符\0(空字符)、\(反斜线)、\t(水平制表符)、\n(换行符)、\r(回车符)、\”(双引号)、\’(单引号)
  • 单字节 Unicode 标量,写成\xnn,其中nn为两位十六进制数
  • 双字节 Unicode 标量,写成\unnnn,其中nnnn为四位十六进制数
  • 四字节 Unicode 标量,写成\Unnnnnnnn,其中nnnnnnnn为八位十六进制数

    初始化空字符串

    1
    2
    3
    4
    5
    var emptyString = ""
    var antherEmptyString = String()
    if emptyString.isEmpty {
    //
    }

字符串可变性(String Mutability)

可以通过分配一个变量来对字符串进行修改,或者分配一个常量保证其不被修改

字符串是值类型

String类型进行常量、变量赋值操作或在函数\方法中传递,会进行拷贝,是值传递

字符

Swift的String类型表示特定序列的character类型值的集合

1
2
3
for character in "Hello world"}
println(character)
}

可以表明Character类型来创建字符常量或者变量

1
let a:Character = "1"

计算字符数量

通过调用全局的countElement函数,并将字符串作为参数传递,可以获取字符串的字符数量

1
2
let str = "I have a word"
pritln(\(coutElemnts(str)));

连接字符串和字符

使用+相加就可以连接字符串
使用+=可以讲一个字符串加上另一个字符串存到原有的字符串上

字符串插值

字符串插值是一种构建新字符串的方式,可以在其中包含常量、变量、字面量和表达式

1
\(Double(3) * 2.5) // "7.5"

比较字符串

Swift 提供了三种方式来比较字符串的值:字符串相等、前缀相等和后缀相等。

字符串相等

1
2
3
4
5
6
let str = "一样样的"
str == "一样样的" // true
#### 前缀/后缀相等
调用字符串的`hasPrefix`/`hasSuffix`方法来检查字符串是否拥有特定前缀/后缀。
```swift
"I am a boy".hasPrefix("I am"); // true

大写和小写字符串

通过字符串的uppercaseStringlowercaseString属性来方位大写/小写版本的字符串

Unicode

Unicode是一个国际标准,用于文本的编码和表示。
每一个字符都可以被Unicode解释成一个或多个unicode标量。字符的unicode标量是一个唯一的21位数字,例如U+0061表示小写的拉丁字母a
当Unicode字符串被写进文本文件或其他存储结构当中,这些unicode标量将会按照Unicode定义的集中格式之一进行编码,其中包括UTF-8(以8位代码单元进行编码)和UTF-16(以16位代码单元进行编码)

字符串的Unicode表示

Swift提供了几种不同的方法来访问字符串的Unicode

  • UTF-8 代码单元集合 (利用字符串的utf8属性进行访问)
  • UTF-16 代码单元集合 (利用字符串的utf16属性进行访问)
  • 21位的 Unicode 标量值集合 (利用字符串的unicodeScalars属性进行访问)
    utfb属性其为UTF8View类型的属性,是无符号8位(UInt)值的集合,同理utf16属性是UTF16View类型的属性
    1
    2
    3
    4
    5
    6
    7
    let dogString "Dog!"
    for codeUnit in dogString.utf8 {
    pritln("\(codeUnit)");
    }
    for code16Unit in dogString.utf16 {
    println("\(code16Unit)");
    }

Unicode标量

unocodeScalars属性为UnicodeScalarView类型的属性,是UnicodeScalar的集合,UnicodeScalar是21位的 Unicode 代码点。

1
2
3
for scale in dogString.unicodeScalars {
println("\(scale.value)")
}

集合类型(Collection Types)

数组

数组使用有序列表存储同一类型的多个值,相同的值可以多次出现在一个数组的不同位置中
在Swift中。数据值在被存储进入某个数组之前类型必须明确,方法是通过显示的类型标注或类型推断,Swift的数组是类型安全的,并且它包含的类型必须明确,这点和NSArray和NSMutableArray很不同。

数组的简单语法

数组遵循Array<SomeType>这样的形式,其中SomeType是这个数组中唯一允许存在的数据类型。也可以使用像SomeType[]这样的简单语法。

数组构造语句

形如[value1,value2,value3]

1
var names:String[] = ["joy","jack"]

变量被声明为字符串类型的数组
由于Swift的类型推断机制,也可以这样写

1
var names = ["jay","jack"]

访问和修改数组

可以通过数组的方法和属性来访问和修改数组,或者下表语法。还可以使用数组的只读属性count来获取数组中的数据数量,使用isEmpty属性可以检测数组是否为空,使用append方法在数组后面添加新的数据项,也可以使用(+=)添加单个数据项或者拥有相同数据类型的数组
也可以通过索引获取数组项

1
2
3
4
5
6
7
8
var names = ["joy","jack"]
names.count // 2
names.isEmpty // false
names.append("alisa")
names+="Demi"
names+=["Carry","Carry"]
names[0] //"joy"
names[2...3] // ["alisa","Demi"]

调用insert(atIndex:)可以再在指定位置插入数据,removeAtIndex方法可以移除数组中的某一项,removeLast方法可以移除最后一项

数组的遍历

使用for-in循环来遍历所有数组中的数据项

1
2
3
for item in shoppingList{
println(item)
}

也可以使用全局enumerate函数来进行数组遍历

1
2
3
for (index,value) in enumerate(shoppingList) {
println("Item \(index+1) : \(value)")
}

创建或构造一个数组

1
2
3
var someInts = Int[]()
var threeDoubles = Double[](count:3,repeatedValue:0.0) // 指定大小,初始值
var anotherThreeDoubles = Array(count:3,repeatedValue:2.5) // 类型推到

字典

字典是一种存储多个相同类型的值的容器,没个值都关联唯一的键,键作为字典中的这个值数据的标识符。字典的数据项没有具体的顺序,需要通过键访问数据。
与Objective-C中的NSDictionaryNSMutableDictionary类可以使用任何类型的对象做键和值不同,Swift在某个特定字典中可以存储的键和值必须提前定义,方法是通过显式标注或者类型推断
Swift的字典可以使用Dictionary<KeyType,ValueType>定义,其中keyType是键的数据类型,ValueType是值的数据类型。keyType的唯一限制是可哈希的,这样可以保证它的唯一性,所有Swift的基本类型(String,Int,Double和Bool)都是可哈希的,未关联的枚举成员也是可哈希的。

字典字面量

[key1:value1,key2:value2,key3:value3]可以创建字典

1
2
var airports:Dictionary<String,String> = ["TYO":"Tokyo","DUB":"Dublin"]
var airports = ["TYO":"Tokyo","DUB":"Dubin"]

读取和修改字典

使用下标语法或者字典的方法属性可以读取字典,只读属性count来获取字典的数据项的数量
也可以使用下标法添加新的数据项
updateValue(forkey:)方法可以设置或更新特定键对应的值,根据键值是否存在判断。该函数会返回包含一个字典值类型的可选值

1
2
3
4
5
airports["LHR"] = "London Heathrow" // 添加数据项
airports["LHR"] = "London" // 修改
if let oldValue = airports.updateValue("Dublin Internation",forKey:"DUB"){
oldValue // 返回的是原值
}

使用下标法也可以访问对应键的值,如果不存在,返回nil,通过下标法设置某键的值为nil,也可以删除数据项,也可以使用removeValueForKey方法移除

1
2
3
if let removedValue = airports.removeValueForKey("DUB"){
// removedValue为被移除的值,不存在的话返回nil
}

字典遍历

使用for-in语法便可遍历,每一个字典的数据项都由(key,value)元组形式返回

1
2
3
for (airportCode,airportName) in airport {
prinln("\(airportCode):\(airportName)")
}

也可以访问它的keysvalues属性检索一个字典的键或者值

1
2
3
4
for airportCode in airports.keys {
println("Airport code: \(airportCode)")
}
let airportNames = Array(airports.values)

创建字典

创建空字典

1
2
var nameOfIntegers = Dictionary<Int,String>()
var nameOfIntegers = [:]

集合的可变性

如果数组或字典设置为变量,那么它的数据项是可变的,设置为常量,那么它的大小是不可变的,数据在首次设定之后便不能改变。不同的是,数组的大小不能改变,但是可以改变它的值。
控制流

For循环

for循环用作按照指定的次数多次执行一系列语句。Swift提供了两种for循环形式:

  • for-in用来遍历一个区间(range),序列(sequence),集合(collection),系列(progression)里面多有的元素
  • for条件递增(for-condition-increment)语句,用来重复执行一系列语句知道达到特定条件,一般通过在每次循环完成后增加计数器的值来实现。

    For-In

    1
    2
    3
    4
    // index不需要声明
    for index in 1...5{
    println("\(index)");
    }

如果不需要知道区间每一项的值,可以使用下划线(_)替代变量名忽略对值的访问

1
2
3
for _ in 1...9{
//
}

For条件递增(for-condition-increment)

1
2
3
for var index = 0; index < 3;++index{
//
}

下面是一般情况下这种循环方式的格式:

for initialization; condition; increment {
statements
}

While循环

while循环运行一系列语句直到条件变成false。这类循环适合使用在第一次迭代前迭代次数未知的情况下。Swift 提供两种while循环形式:

  • while循环,每次在循环开始时计算条件是否符合;
  • do-while循环,每次在循环结束时计算条件是否符合。
    一般格式如下:
    1
    2
    3
    4
    5
    6
    while condition{
    statements
    }
    do {
    statements
    } while condition

条件语句

Swift提供两种类型的条件语句:if语句和switch语句。通常,当条件较为简单且可能的情况很少时,使用if语句。而switch语句更适用于条件较复杂、可能情况较多且需要用到模式匹配(pattern-matching)的情境。

If

if语句最简单的形式就是只包含一个条件,当且仅当该条件为true时,才执行相关代码:

1
2
3
if true {
//
}

Switch

switch语句会尝试把某个值与若干个模式(pattern)进行匹配。根据第一个匹配成功的模式,switch语句会执行对应的代码。

1
2
3
4
5
6
7
8
9
witch some value to consider {
case value 1:
respond to value 1
case value 2,
value 3:
respond to value 2 or 3
default:
otherwise, do something else
}

swift不存在隐藏的贯穿,即不需要break,执行完也会推出,同时swift还支持区间匹配

1
2
3
4
5
6
switch count{
case 0:
//
case 1...3:
//
}

元组匹配

1
2
3
4
5
var count = (1,3)
switch count{
case (0,0):
//
}

值绑定

case分支的模式允许将匹配的值绑定到一个临时的常量或变量,这些变量或常量在该case分支里就可以被引用————这种行为被称为值绑定

1
2
3
4
5
6
7
8
9
let point = (2,0)
switch point {
case (let x,0):
//
case (0, let y):
//
case let (x,y):
//
}

Where

case分支模式可以使用where语句来判断额外的条件

1
2
3
4
5
let point = (1,0)
switch point {
case let (x,y) where x == y:
//
}

控制转移语句(Control Transfer Statements)

控制转移语句改变你代码的执行顺序,通过它你可以实现代码的跳转。Swift有四种控制转移语句。- continue

  • break
  • fallthrough
  • return

    Continue

    continue语句告诉一个循环体立刻停止本次循环迭代,重新开始下次循环迭代。

    Break

    break语句会立刻结束整个控制流的执行。

    贯穿(Fallthrough)

    Swift不存在C语言中switch语句的贯穿行为,要实现需要使用fallthrouth
    1
    2
    3
    4
    5
    6
    7
    switch count{
    case 1:
    //
    fallthrough
    default:
    // 这里会被执行
    }

带标签的语句(Labeled Statements)

环体和switch代码块两者都可以使用break语句来提前结束整个方法体,通过带标签的语句可以指定想要终止哪个循环或者switch代码块,如果有许多嵌套的循环体,也可以实现continue指定跳转

1
2
3
label name:while condition{
statenments
}

1
2
3
4
5
6
7
8
9
loop:while count != 100{
if count < 100 {count += 10}
switch count {
case 1..20 :
break loop
case 21...99:
//
}
}

函数(Functions)

函数的定义与调用(Defining and Calling Functions)

1
2
3
4
5
func sayHello(personName:String) -> String{
let greeting = "Hello," + personName + "!"
return greeting
}
println("\(sayHeloo("zoe"))")

函数参数与返回值(Function Parameters and Return Values)

  • 多个参数

    1
    2
    3
    func fun(start: Int,end: Int) -> Int {
    return end - start
    }
  • 无参

    1
    2
    3
    func say() -> String{
    return "hello"
    }
  • 无返回值

    1
    2
    3
    func sayGoodbye(personName: String){
    println("Goodbye",\(personName)!)
    }
  • 多返回值
    // 计算一个字符串中元音、辅音和其他字母的个数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    func count(str:String) -> (vowels: Int, consonants: Int,others: Int) {
    var vowels = 0, consonants = 0, others = 0
    for character in string {
    switch String(character).lowercaseString {
    case "a", "e", "i", "o", "u":
    ++vowels
    case "b", "c", "d", "f", "g", "h", "j", "k", "l", "m",
    "n", "p", "q", "r", "s", "t", "v", "w", "x", "y", "z":
    ++consonants
    default:
    ++others
    }
    }
    return (vowels, consonants, others)
    }
    let total = count("some arbitrary string!")
    println("\(total.vowels) vowels and \(total.consonants) consonants")

函数参数名称

在函数定义时定义的参数为局部参数名,只能在函数体中使用

外部函数名

类似于Objective-C的函数命名

1
2
3
4
func join(string s1:String,toString s2:String) -> String{
return s1 + s2
}
join(string:"hello",toString:"world")

简写外部参数名

上面的方法需要为参数提供外部参数名和内部参数名,可以通过#简写,将外部参数名和内部参数名等同起来

1
2
3
4
func join(# string:String,# toString:String) -> String{
return string + toString
}
join(string:"hello",toString:"world")

默认参数值

为参数提供一个初始值,调用时,缺省可以忽略并使用默认值

1
2
3
func join(str1 String,str2 String = "world") -> String{
return str1 + str2
}

可变参数

1
2
3
4
5
6
7
func add (numbers: Double...) -> Double {
var total: Double = 0;
for number in numbers {
total += number
}
return total / Double(numbers.cout)
}

常量参数和变量参数

函数参数默认是常量,在函数体中更改参数值将会导致编译错误,如果参数定义为变量就可以当做参数的副本来使用

1
2
3
4
5
6
7
8
// 用来右对齐输入的字符串到一个长的输出字符串中。左侧空余的地方用指定的填充字符填充
func alignRight(var string: String, count: Int, pad: Character) -> String {
let amountToPad = count - countElements(string)
for _ in 1...amountToPad {
string = pad + string
}
return string
}

输入输出参数

一个输入输出参数时,在参数定义前加inout关键字。一个输入输出参数有传入函数的值,这个值被函数修改,然后被传出函数,替换原来的值。当传入的参数作为输入输出参数时,需要在参数前加&符,表示这个值可以被函数修改。

1
2
3
4
5
6
7
func swap (inout a:Int,inout b:Int){
let temp = a
a = b
b = temp
}
var a = 3,b = 4
swap(&a,&b)

函数类型

每个函数都有种特定的函数类型,由函数的参数类型和返回类型组成。
() -> ()表示没用参数,返回Void,在Swift中,Void与空元组是一样的

使用函数类型

使用函数名来给另一个函数赋值

1
var fun2:(Int,Int) -> Int = fun1

使用函数类型

1
2
var mathFunction:(Int,Int) -> Int = addTwoInts
// 只要matchFunction和addTwoInts的类型相同,该赋值就是合法的

函数类型作为参数类型

1
2
3
4
func printMathResult(matchFunction:(Int,Int) -> Int,a:Int,b:Int){
println("Result: \(mathFunction(a,b))")
}
printMathResult(addTwoInts,3,5)

函数类型作为返回类型

1
2
3
func chooseStepFunction(backwards:Bool) -> (Int) -> Int {
return backwards ? stepBackward : stepForward
}

闭包
闭包之自包含的函数代码块,可以在代码中传递和使用,与Objective-C的blocks相似
闭包可以捕获和存储其所在上下文任意常量和变量的引用。
全局和嵌套函数也是特殊的闭包,闭包采取如下三种形式:

  • 全局函数是一个有名字但不会捕获任何值的闭包
  • 嵌套函数是一个有名字并可以捕获其封闭函数内值的闭包
  • 闭包表达式是一个利用轻量级语法所写的可以捕获其上下文中变量或常量值的匿名闭包

闭包表达式(Closure Expreessions)

闭包表达式是一种利用简洁语法构建内联闭包的方式,闭包表达式提供了一些语法优化,使得撰写闭包变得简单明了。

闭包表达式语法

闭包表带式语法一般形式:

1
2
3
{ (parameters) -> returnType in
statemnts
}

Swift标准库提供了sort函数,会根据基于输出类型排序的闭包函数将已知类型数组的值进行排序,返回一个与原数组大小相同的新数组,并排序完成。

`

Swift可以再类型后面加一个?来将变量声明为optional(随意的)。如果不是Optional的变量,那么它就必须有值,而没有值发话,我们使用Optional并且将它设置为nil来表示没有值。

1
2
3
var num:Int?
num = nil
num = 3

Optional Value就像一个盒子,盒子可能装着实际的值,可能声明都没装。

1
2
3
4
5
6
var num:Int?=3	// 声明一个Int的Optianal,并将其设为3
if let n = num {
// hava a num
} else {
// no num
}

使用场景

1
foo?.somemethod()

数据持久化的方法有:

  • 保存在硬盘或闪存上
  • 保存在计算机内存中
  • 保存在数据库中
  • 保存在cookie或会话中

将数据写入文件

1
2
3
4
5
6
7
8
9
10
// weite.js
var fs = require('fs'),
data = "Some data I want to write to a file.";
fs.writeFile('file.txt',data,function(err){
if (!err) {
console.log("Wrote data to file.txt.");
} else {
throw err;
}
});

注意:

  1. 如果文件不存在,writeFile方法也创建文件
  2. 文件默认写入脚本运行位置,也可以指定完整路径
  3. 可能的错误包括文件不存在或者没有读取文件的权限

从文件读取数据

1
2
3
4
5
6
7
8
9
// read.js
var fs = require('fs');
fs.readFile('file.txt','utf8',function(err,data){
if (!err) {
console.log(data);
}else{
throw err;
}
});

环境变量

设置环境变量
windows:

1
SET SOMETHING='12345678'

UNIX:

1
var something = process.env.SOMETHING

可以再node中访问process.env.SOMETHING

使用数据库

NoSQL数据库

  • Cassandra
  • Redis
  • Memcached
  • MongoDB
  • Hadoop

在Node.js中使用MongoDB

MongoDB是面向文档的数据库,它不遵照将关系数据连接在一起的关系模型,它可执行关系数据库的大多数功能并且旨在提供高度的可用性和可扩展性。
CRUD指Create、Read、Update、Delete

安装MongiDB

可以到MongoDB官网获得对应平台的安装文件。
MongoDB有很多GUI支持,OSX可以使用MongoHub;Windows,可以使用Monogo VUE,它是功能受限的免费版,同事需要.NET Framework支持;Linux上可以使用JMongoBrowser,它是基于Java客户端,需要Java运行环境。

连接MongoDB

Mongoose是Node.js中一个功能齐备的第三方模块,用于处理MongoDB。将Mongoose模块加入到项目中,并在package.json文件中将其作为依赖模块包含进来:

1
2
3
4
5
6
7
{
"name":"your-application",
"version":"0.0.1",
"dependcies":{
"mongoose":">= 2.3.1"
}
}

运行npm install
并在应用程序文件中请求它var mongoose = require('mongoose');
之后连接并使用它mongoose.connect('mongodb://localhost/your_database');.

定义文档

MongoDB中,没有关系数据库中表的概念,M哦你goDB围绕着文档的思想来组织数据。
通过Mongoose模块在MongoDB中定义一个文档:通过Mongoose提供的Schema接口定义,然后声明属性。Mongoose可声明的类型如下:

  • String(字符串)
  • Number(数值)
  • Date(日期)
  • Boolean(布尔值)
  • Buffer(缓存)
  • ObjectID(对象ID)
  • Mixed(混合)
  • Array(数组)
    对于task模型而言,只需要一个属性
    1
    2
    3
    4
    5
    6
    var Schema = mongoose.Schema,
    ObjectId = Shema.ObjectId;
    var Task = new Schema({
    task:String
    });
    var Task = mongoose.model('Task',Task);

将Twitter Bootstrap包含进来

views/layout.jade包含如下内容

1
2
3
4
5
6
html 
head
title=title
link(rel='stylesheet',href='http://twitter.github.com/bootstrap/1.4.0/bootstrap.min.css')
body
section.container!=body

数据类

  • Bimp
    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
    public class Bimp {
    public static int max = 0;
    public static boolean act_bool = true;
    public static List<Bitmap> bmp = new ArrayList<Bitmap>();
    // 图片的存放地址
    public static List<String> drr = new ArrayList<String>();
    // 用于压缩图片
    public static Bitmap revitionImageSize(String path)throws IOException {
    BufferedInputStream in = new BufferedInputStream(new FileInputStream(
    new File(path)));
    BitmapFactory.Options options = new BitmapFactory.Options();
    options.inJustDecodeBounds = true;
    BitmapFactory.decodeStream(in, null, options);
    in.close();
    int i = 0;
    Bitmap bitmap = null;
    while (true) {
    if ((options.outWidth >> i <= 1000)
    && (options.outHeight >> i <= 1000)) {
    in = new BufferedInputStream(
    new FileInputStream(new File(path)));
    options.inSampleSize = (int) Math.pow(2.0D, i);
    options.inJustDecodeBounds = false;
    bitmap = BitmapFactory.decodeStream(in, null, options);
    break;
    }
    i++;
    }
    return bitmap;
    }
    }

工具类

  • BitmapCache类

    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
    public class BitmapCache {
    public final String TAG = getClass().getSimpleName();
    public Handle h = new Handle();
    private HashMap<String,SoftReference<Bitmap>> imageCache = new HashMap<String,SoftReference<Bitmap>>();
    public void put(String path,Bitmap bmp) {
    if(!TextUtils.isEmpty(path) && bmp != null) {
    imageCache.put(path, new SoftReference<Bitmap>(bmp));
    }
    }
    public void displayBmp(final ImageView iv, final String thumbPath, final String sourcePath, final ImageCallback call ballback) {
    if (TextUtils.isEmpty(thumbPath) && TextUtils.isEmpty(sourcePath)) {
    Log.e(TAG, "no paths pass in");
    return;
    }
    final String path;
    final boolean isThumbPath;
    if (!TextUtils.isEmpty(thumbPath)) {
    path = thumbPath;
    isThumbPath = true;
    }else if (!TextUtils.isEmpty(sourcePath)) {
    path = sourcePath;
    isThumbPath = false;
    }else {
    return;
    }
    // 缓存中是否有该图片
    if(imageCache.containsKey(path)){
    SoftReference<Bitmap> reference = imageCache.get(path);
    if (bmp != null) {
    if (callback != null) {
    callback.imageLoad(iv, bmp ,sourcePath);
    }
    iv.setImageBitmap(bmp);
    Log.d(TAG,"hit cache");
    return;
    }
    }
    iv.setImageBitmap(null);
    new Thread() {
    Bitmap thumb;
    public void run(){
    try {
    if (isThumbPath) {
    thumb = BitmapFactory.decodeFile(thumbPath);
    if (thumb == null) {
    thumb = revitionImageSize(sourcePath);
    } else {
    thumb = revitionImageSize(thumbPath);
    }
    } catch (Exception e){

    }
    if (thumb == null) {
    thumb = revitionImageSize(sourcePath);
    }
    put(path,thumb);
    if(callback != null){
    h.post(new Runnable(){
    @Override
    public void run() {
    callback.imageLoad(iv, thumb, sourcePath);
    }
    });
    }
    }
    }.strat();
    }

    public Bitmap revitionImageSize(String path) throws IOException {
    BufferedInputStream in = new BufferedInputStream(new FileInputStream(
    new File(path)));
    BitmapFactory.Options options = new BitmapFactory.Options();
    options.inJustDecodeBounds = true;
    BitmapFactory.decodeStream(in, null, options);
    in.close();
    int i = 0;
    Bitmap bitmap = null;
    while (true) {
    if ((options.outWidth >> i <= 256)
    && (options.outHeight >> i <= 256)) {
    in = new BufferedInputStream(
    new FileInputStream(new File(path)));
    options.inSampleSize = (int) Math.pow(2.0D, i);
    options.inJustDecodeBounds = false;
    bitmap = BitmapFactory.decodeStream(in, null, options);
    break;
    }
    i += 1;
    }
    return bitmap;
    }

    public interface ImageCallback {
    public void imageLoad(ImageView imageView, Bitmap bitmap,
    Object... params);
    }
    }
  • AlbumHelper

    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
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    228
    229
    230
    231
    232
    233
    234
    235
    236
    237
    238
    239
    240
    241
    242
    243
    244
    245
    246
    247
    248
    249
    250
    251
    252
    public class AlbumHelper {
    private final String TAG = getClass().getSimpleName();
    private Context context;
    private ContentResolver cr;

    // 缩略图列表
    public HashMap<String, String> thumbnailList = new HashMap<String, String>();
    // 专辑列表
    private List<HashMap<String, String>> albumList = new ArrayList<HashMap<String, String>>();
    private HashMap<String, ImageBucket> bucketList = new HashMap<String, ImageBucket>();

    private static AlbumHelper instance;

    private AlbumHelper() {
    }

    public static AlbumHelper getHelper() {
    if (instance == null) {
    instance = new AlbumHelper();
    }
    return instance;
    }

    /**
    * 初始化
    *
    * @param context
    */
    public void init(Context context) {
    if (this.context == null) {
    this.context = context;
    cr = context.getContentResolver();
    }
    }

    /**
    * 得到缩略图
    */
    private void getThumbnail() {
    String[] projection = { Thumbnails._ID, Thumbnails.IMAGE_ID,
    Thumbnails.DATA };
    Cursor cursor = cr.query(Thumbnails.EXTERNAL_CONTENT_URI, projection,
    null, null, null);
    getThumbnailColumnData(cursor);
    }

    /**
    * 从数据库中得到缩略图
    *
    * @param cur
    */
    private void getThumbnailColumnData(Cursor cur) {
    if (cur.moveToFirst()) {
    int _id;
    int image_id;
    String image_path;
    int _idColumn = cur.getColumnIndex(Thumbnails._ID);
    int image_idColumn = cur.getColumnIndex(Thumbnails.IMAGE_ID);
    int dataColumn = cur.getColumnIndex(Thumbnails.DATA);

    do {
    _id = cur.getInt(_idColumn);
    image_id = cur.getInt(image_idColumn);
    image_path = cur.getString(dataColumn);
    thumbnailList.put("" + image_id, image_path);
    } while (cur.moveToNext());
    }
    }

    /**
    * 得到原图
    */
    private void getAlbum() {
    String[] projection = { Albums._ID, Albums.ALBUM, Albums.ALBUM_ART,
    Albums.ALBUM_KEY, Albums.ARTIST, Albums.NUMBER_OF_SONGS };
    Cursor cursor = cr.query(Albums.EXTERNAL_CONTENT_URI, projection, null,
    null, null);
    getAlbumColumnData(cursor);

    }

    /**
    * 从本地数据库中得到原图
    *
    * @param cur
    */
    private void getAlbumColumnData(Cursor cur) {
    if (cur.moveToFirst()) {
    int _id;
    String album;
    String albumArt;
    String albumKey;
    String artist;
    int numOfSongs;

    int _idColumn = cur.getColumnIndex(Albums._ID);
    int albumColumn = cur.getColumnIndex(Albums.ALBUM);
    int albumArtColumn = cur.getColumnIndex(Albums.ALBUM_ART);
    int albumKeyColumn = cur.getColumnIndex(Albums.ALBUM_KEY);
    int artistColumn = cur.getColumnIndex(Albums.ARTIST);
    int numOfSongsColumn = cur.getColumnIndex(Albums.NUMBER_OF_SONGS);

    do {
    _id = cur.getInt(_idColumn);
    album = cur.getString(albumColumn);
    albumArt = cur.getString(albumArtColumn);
    albumKey = cur.getString(albumKeyColumn);
    artist = cur.getString(artistColumn);
    numOfSongs = cur.getInt(numOfSongsColumn);

    Log.i(TAG, _id + " album:" + album + " albumArt:" + albumArt
    + "albumKey: " + albumKey + " artist: " + artist
    + " numOfSongs: " + numOfSongs + "---");
    HashMap<String, String> hash = new HashMap<String, String>();
    hash.put("_id", _id + "");
    hash.put("album", album);
    hash.put("albumArt", albumArt);
    hash.put("albumKey", albumKey);
    hash.put("artist", artist);
    hash.put("numOfSongs", numOfSongs + "");
    albumList.add(hash);
    } while (cur.moveToNext());
    }
    }

    /**
    * 是否创建了图片集
    */
    private boolean hasBuildImagesBucketList = false;

    /**
    * 得到图片集
    */
    private void buildImagesBucketList() {
    long startTime = System.currentTimeMillis();

    // 构造缩略图索引
    getThumbnail();

    // 构造相册索引
    String columns[] = new String[] { Media._ID, Media.BUCKET_ID,
    Media.PICASA_ID, Media.DATA, Media.DISPLAY_NAME, Media.TITLE,
    Media.SIZE, Media.BUCKET_DISPLAY_NAME };
    // 得到一个游标
    Cursor cur = cr.query(Media.EXTERNAL_CONTENT_URI, columns, null, null,
    null);
    if (cur.moveToFirst()) {
    // 获取指定列的索引
    int photoIDIndex = cur.getColumnIndexOrThrow(Media._ID);
    int photoPathIndex = cur.getColumnIndexOrThrow(Media.DATA);
    int photoNameIndex = cur.getColumnIndexOrThrow(Media.DISPLAY_NAME);
    int photoTitleIndex = cur.getColumnIndexOrThrow(Media.TITLE);
    int photoSizeIndex = cur.getColumnIndexOrThrow(Media.SIZE);
    int bucketDisplayNameIndex = cur
    .getColumnIndexOrThrow(Media.BUCKET_DISPLAY_NAME);
    int bucketIdIndex = cur.getColumnIndexOrThrow(Media.BUCKET_ID);
    int picasaIdIndex = cur.getColumnIndexOrThrow(Media.PICASA_ID);
    // 获取图片总数
    int totalNum = cur.getCount();

    do {
    String _id = cur.getString(photoIDIndex);
    String name = cur.getString(photoNameIndex);
    String path = cur.getString(photoPathIndex);
    String title = cur.getString(photoTitleIndex);
    String size = cur.getString(photoSizeIndex);
    String bucketName = cur.getString(bucketDisplayNameIndex);
    String bucketId = cur.getString(bucketIdIndex);
    String picasaId = cur.getString(picasaIdIndex);

    Log.i(TAG, _id + ", bucketId: " + bucketId + ", picasaId: "
    + picasaId + " name:" + name + " path:" + path
    + " title: " + title + " size: " + size + " bucket: "
    + bucketName + "---");

    ImageBucket bucket = bucketList.get(bucketId);
    if (bucket == null) {
    bucket = new ImageBucket();
    bucketList.put(bucketId, bucket);
    bucket.imageList = new ArrayList<ImageItem>();
    bucket.bucketName = bucketName;
    }
    bucket.count++;
    ImageItem imageItem = new ImageItem();
    imageItem.imageId = _id;
    imageItem.imagePath = path;
    imageItem.thumbnailPath = thumbnailList.get(_id);
    bucket.imageList.add(imageItem);

    } while (cur.moveToNext());
    }
    // Interator为迭代器,Entry为Map接口
    // 遍历所有图片
    Iterator<Entry<String, ImageBucket>> itr = bucketList.entrySet()
    .iterator();
    while (itr.hasNext()) {
    Map.Entry<String, ImageBucket> entry = (Map.Entry<String, ImageBucket>) itr
    .next();
    ImageBucket bucket = entry.getValue();
    Log.d(TAG, entry.getKey() + ", " + bucket.bucketName + ", "
    + bucket.count + " ---------- ");
    for (int i = 0; i < bucket.imageList.size(); ++i) {
    ImageItem image = bucket.imageList.get(i);
    Log.d(TAG, "----- " + image.imageId + ", " + image.imagePath
    + ", " + image.thumbnailPath);
    }
    }
    hasBuildImagesBucketList = true;
    long endTime = System.currentTimeMillis();
    Log.d(TAG, "use time: " + (endTime - startTime) + " ms");
    }

    /**
    * 得到图片集
    *
    * @param refresh
    * @return
    */
    public List<ImageBucket> getImagesBucketList(boolean refresh) {
    if (refresh || (!refresh && !hasBuildImagesBucketList)) {
    buildImagesBucketList();
    }
    List<ImageBucket> tmpList = new ArrayList<ImageBucket>();
    Iterator<Entry<String, ImageBucket>> itr = bucketList.entrySet()
    .iterator();
    while (itr.hasNext()) {
    Map.Entry<String, ImageBucket> entry = (Map.Entry<String, ImageBucket>) itr
    .next();
    tmpList.add(entry.getValue());
    }
    return tmpList;
    }

    /**
    * 根据图片的_ID得到原始图像路径
    *
    * @param image_id
    * @return
    */
    public String getOriginalImagePath(String image_id) {
    String path = null;
    Log.i(TAG, "---(^o^)----" + image_id);
    String[] projection = { Media._ID, Media.DATA };
    Cursor cursor = cr.query(Media.EXTERNAL_CONTENT_URI, projection,
    Media._ID + "=" + image_id, null, null);
    if (cursor != null) {
    cursor.moveToFirst();
    path = cursor.getString(cursor.getColumnIndex(Media.DATA));
    }
    return path;
    }
    }

layout

相册选取layout

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
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="# f1eff5"
android:orientation="vertical" >
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="60dp"
android:background="# ff495a" >
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="相册"
android:textColor="# ffffff"
android:textSize="20sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="40dp"
android:gravity="center"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:layout_marginRight="8dp"
android:textColor="@drawable/text_while"
android:text="取消"
android:textSize="20sp" />
</RelativeLayout>
<GridView
android:id="@+id/gridview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="10dp"
android:horizontalSpacing="20dp"
android:numColumns="2"
android:scrollbars="none"
android:verticalSpacing="20dp" >
</GridView>
</LinearLayout>

  • 图片选取layout

    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
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="# f1eff5"
    android:orientation="vertical" >
    <RelativeLayout
    android:layout_width="match_parent"
    android:layout_height="60dp"
    android:background="# ff495a" >
    <TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_centerInParent="true"
    android:text="相册"
    android:textColor="# ffffff"
    android:textSize="20sp" />
    <TextView
    android:layout_width="wrap_content"
    android:layout_height="40dp"
    android:gravity="center"
    android:layout_alignParentRight="true"
    android:layout_centerVertical="true"
    android:layout_marginRight="8dp"
    android:textColor="@drawable/text_while"
    android:text="取消"
    android:textSize="20sp" />
    </RelativeLayout>
    <GridView
    android:id="@+id/gridview"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_margin="10dp"
    android:horizontalSpacing="20dp"
    android:numColumns="2"
    android:scrollbars="none"
    android:verticalSpacing="20dp" >
    </GridView>
    </LinearLayout>
  • 图片预览layout

    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
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >
    <android.support.v4.view.ViewPager
    android:id="@+id/viewpager"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >
    </android.support.v4.view.ViewPager>
    <RelativeLayout
    android:id="@+id/photo_relativeLayout"
    android:layout_width="match_parent"
    android:layout_height="60dp"
    android:layout_alignParentBottom="true" >
    <Button
    android:id="@+id/photo_bt_exit"
    android:layout_width="50dp"
    android:layout_height="50dp"
    android:layout_alignParentLeft="true"
    android:layout_centerVertical="true"
    android:background="@drawable/bt_quxiao" />
    <Button
    android:id="@+id/photo_bt_del"
    android:layout_width="50dp"
    android:layout_height="50dp"
    android:layout_centerInParent="true"
    android:background="@drawable/bt_shanchu" />
    <Button
    android:id="@+id/photo_bt_enter"
    android:layout_width="50dp"
    android:layout_height="50dp"
    android:layout_alignParentRight="true"
    android:layout_centerVertical="true"
    android:background="@drawable/bt_queding" />
    </RelativeLayout>
    </RelativeLayout>