月度归档:2015年03月

SVG动画——Android 5.0新特性介绍(2)

先放点motivation

(GIF图点开,看动画)

test要实现这个布局很简单,用FrameLayout加一个ImageView并设置一个图片,再在FrameLayout上放一个RelativeLayout,背景用t这个矢量图。布局文件如下:

<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:padding="20dp"
                tools:context=".MainActivity">
    <FrameLayout
        android:id="@+id/card_view"
        android:layout_centerInParent="true"
        android:layout_width="260dp"
        android:layout_height="430dp"
        >

        <ImageView
            android:id="@+id/img"
            android:src="@drawable/p1"
            android:scaleType="centerCrop"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
        <RelativeLayout
            android:id="@+id/card_content"
            android:background="@drawable/animate_rect"
            android:layout_width="match_parent"
            android:layout_height="320dp"
            android:padding="15dp">
            <TextView
                android:id="@+id/title"
                android:textColor="#03A9F4"
                android:textSize="25sp"
                android:layout_marginTop="6dp"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="Vector card"
                android:layout_alignParentTop="true"
                android:layout_centerHorizontal="true"/>
            <TextView
                android:id="@+id/tv_content"
                android:gravity="center"
                android:layout_marginTop="18dp"
                android:layout_below="@id/title"
                android:text="Lorem ipsum dolor sit amet, Duis aute irure dolor in reprehenderit sunt in culpa quilaborum. in voluptate velit esse cillum dolore eu fugiat nulla ."
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                />


        </RelativeLayout>

    </FrameLayout>
</RelativeLayout>

怎么让它动起来呢?其实很简单,只要修改SVG图中的pathData就行,也就是从梯形变成一个长方形。原来的pathData的坐标和要变化的新坐标如图所示,t

 

SVG文件内容:sharp_rect.xml

注意,我们把path加了一个name=”sharp_rect”,这是做动画效果是要指定这部分,所以需要给个独一无二的name作为id.

<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
        android:width="180dp"
        android:height="320dp"
        android:viewportWidth="180"
        android:viewportHeight="400">

    <path
        android:name="sharp_rect"
        android:fillColor="#000000"
        android:pathData="M 180,230 L 0,320 0,0 180,0 z" />
</vector>

下面做animate-vector drawable:

<?xml version="1.0" encoding="utf-8"?>
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:drawable="@drawable/sharp_rect">
    <target
        android:animation="@animator/to_rect"
        android:name="sharp_rect"/>
</animated-vector>

在animated-vector里填入android:drawable=”@drawable/sharp_rect”,这是指定要产生动画的SVG。target里是需要动画的对象,这里我们的对象就是前面那个叫sharp_rect的梯形,所以name里填上它,然后animation填的是对应的动画文件。

动画用的是ObjectAnimator做的,内容如下:

<?xml version="1.0" encoding="utf-8"?>
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
                android:duration="330"
                android:interpolator="@android:interpolator/decelerate_cubic"
                android:propertyName="pathData"
                android:valueType="pathType"
                android:valueFrom="M 180,230 L 0,320 0,0 180,0 z"
                android:valueTo="M 180,75 L 0,75 0,0 180,0 z" />

值得注意的是,我们要从梯形变成矩形,就是对pathData进行修改,也就是propertyName要填入pathData,valueType是pathType,valueFrom是原来的路径(也就是梯形的路径),最终变成的效果是valueTo(矩形的路径)。这里有个要注意的地方,如果要进行path的变换,里面的点数必须要一样!

 

Ok了大功告成,最后只要把ImageView的src改成animate_rect就行了,并设置触发函数就行了

 final ImageView play = (ImageView)findViewById(R.id.icon_play);
        play.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Drawable drawable = play.getDrawable();
                if ( drawable instanceof Animatable){
                    ((Animatable) drawable).start();
                }
            }
        });

注意,上面我这种方法不是Android SVG推荐的用法,SVG动画尺寸应该尽可能的小,和简单,因为每次动画都会先把这些path先计算绘成Bitmap,然后上传texture到GPU,如果SVG太大意味着生成更大的Bitmap,占更多内存,消耗更多时间.Google的推荐是把SVG用于图标(icon)和按钮(Button),只有需要的时候才修改Vector的属性(比如alpha,width,height),因为如果SVG不用于动画,android会把这个图生成一个Cache来节省时间,如果SVG动画这个Cache就没有用了。如果要用SVG动画,请确保它“短小精悍”(Short and sweet)。

下面还有一些SVG动画的例子:

(GIF图,点开看)

35ed6362d41a2a3e5a64d3bb8ed16033

Demo源代码: https://github.com/qianlvable/VectorCard

参考:

DevBytes: Android Vector Graphics (youtube视频,需翻墙)

VectorDrawables – Styling Android 系列

Transitioning to Infinity (一个很厉害的法国人,不过他的程序都是用C#写的,本文的BlueTooth的动画来源于这个博客)

SVG图像——Android 5.0新特性介绍(1)

本文将介绍什么是SVG图像,及如何在Android中使用SVG及一些简单的SVG动画。

什么是SVG?

SVG的全称是Scalable Vector Graphics,叫可缩放矢量图形。它和位图(Bitmap)相对,SVG不会像位图一样因为缩放而让图片质量下降。下面这个图片展示了SVG和位图的区别,左边的位图在放大后出现了锯齿,而右边的SVG任然清晰。svg_cp

 

为什么要使用SVG?

  • 节约空间,使用方便

因为SVG可以任意缩放而图像任保持清晰的特点,且在android里SVG是在运行时才进行渲染的,它会根据屏幕的dpi自动缩放到合适的大小,所以我们仅仅用一个SVG文件就可以代替多个不同dpi的drawable了!下面给出一个用SVG的android logo代替多种dpi的drawable的占用空间大小对比.svg_size_cp

  • 用XML就可以写出一些简单的动画。下面会给例子

如何在Android中使用?

  • 把普通SVG图片转成Android可用格式:

有两种方法,手动改一下原始SVG的格式,或者用一个自动转换的工具(Android SVG to VectorDrawable)

下面展示一下,这个图像的SVG文件内容t

<svg xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" version="1.0" viewBox="0 0 180 400" width="180px" height="320" preserveAspectRatio="none">
    <path d="M 180,230 L 0,320 0,0 180,0 z" fill="#000000"/>
</svg>

path是画出这个矢量图所需的笔画路径,这里就是移植到android所需要的关键部分。下面来看转换成android能用SVG图内容,这里我将重点讲解

sharp_rect.xml

<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
        android:width="180dp"
        android:height="320dp"
        android:viewportWidth="180"
        android:viewportHeight="400">

    <path
        android:name="sharp_rect"
        android:fillColor="#000000"
        android:pathData="M 180,230 L 0,320 0,0 180,0 z" />
</vector>

注意到和普通SVG图内容不同地方在与viewBox那变成了viewportWidth,viewportHeight,viewport就相当于画这个SVG的画布大小。width和height是规定这个SVG图像最终的显示大小的,一般用dp表示。第二个不同是有一个普通SVG里的fill到android里要变成fillColor,这里就是SVG图像填充的颜色。第三点不同是,普通SVG的path的数据是d开头的标签,在android里要写成pathData。综上所述,只要把viewBox的大小改成viewport的大小,把填充颜色的fill改成fillColor,把Path中的d,改成pathData就行了。

还有最后一个问题pathData中的那些奇怪的东西是啥?数字是在viewport中的坐标点,M代表move to(把画笔移动到),L是Line(划线),Z是封闭path.(更多参考

我们可以通过上面的变动手动把普通SVG转成android用的VectorDrawable,或者我们可以用这个网站自动完成转换 http://inloop.github.io/svg2android/

 

  • 显示

这个SVG就直接就是Drawable直接用就好:

<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:padding="20dp"
                tools:context=".MainActivity">
         <ImageView
            android:src="@drawable/sharp_rect"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />        
</RelativeLayout>

 

下一篇文章将介绍一个使用SVG图像动画的例子: )

QQ截图20150324172339

控制随机——产生不同概率的事件

本文将对产生不同概率的方法和高斯分布的使用做总结。

在实际情况中我们有时候不光只是要随机,有时候还要不同概率大小情况的下的随机事件,比如发生A事件的几率是30%,B事件是40%,等等.实现它有多种方法,最只觉得想法的就是在一个数组里填充几种元素,通过控制各元素的个数来控制概率,比如我要A事件发生的概率为40%,B事件发生的概率为60%.可以造一个长度为10的boolean数组,里面4个设置为true,另外6个位false,然后随机从里面抽一个出来,true就发生事件A,false就发生事件B。代码如下:

boolean array = new boolean[10];
array[0] = true;
...
array[3] = true;

if (array[random(0,10)]){
    triggerA();
}else{
    triggerB();
}

这个方法的弊端是当有多样元素的填充很麻烦,并且浪费不必要的空间。

另一个办法,这个办法更简单:

比如我想要一个概率为60%的A事件,发生概率为20%的B事件,20%的C事件代码如下:

float p = 0.6;
float rand = random(1); // return a number betwenn 0~1
if( rand < p){
    triggerA();
}else if ( p < rand < 0.8){
    triggerB();
} else {
    triggerC();
}

 

有时候我们需要概率随着一定规律变化,比如从1~100中选择数字,要求实现数字越大选中的概率越大,可以用二次随机的方法(这种方法也叫Monte Carlo method)。

下面的例子返回一个0~1的数,概率大小的变化和数字大小成正比 p = value

float montecarlo(){
    while(true){
        float r1 = random(1);
        float p = r1;
        float r2 = random(1);
        if( r2 < p){
            return r1;
        }
    }
}

可以通过修改上面代码很容易得出概率和数字成正比的随机函数的方法.

高斯分布又叫正态分布(本文后续全部使用正态分布这个叫法),这是生活中出现很多的随机分布现象,比如人的身高,财富情况都是正太分布.下面我介绍一下如何把正态分布用于随机中.

要使用正态分布,首先要明白两个参数,mean 和 deviation:

 正态分布mean是中值,也就是数据最集中的那里.deviation是偏差的意思.也就是和中值偏差的大小.有了这两个值就可以用来对高斯分布进行操纵了,偏差越小意味着数据越集中在中值处,偏差越大意味着数据越分散.那么到底在程序中要怎么用正态分布呢?下面用Java里的Random.nextGaussian()为例,它会返回一个mean为0,deviation为1的随机数字.我们做一个在窗口中大小为600的画布上用正态分布画彩色球小的程序:

import java.util.Random;
Random gen;

int r = 0;
int g = 0;
int b = 0;
float x = 0;
float y = 0;

void setup(){
    // 设置屏幕大小为600
  size(600,600);
  gen = new Random();
}
// 这里是循环执行的部分
void draw(){
  float num = (float) gen.nextGaussian();
  float standardDeviation = 40;  // 偏差设置为80
  float mean = 300;             // 中间值设为300,屏幕的中中心
  
  // 随机产生小球的颜色
  r = int(random(255));
  g = int(random(255));
  b = int(random(255));
  fill(r,g,b);
  
  // 这里就是使用状态分布的地方了 偏差 * 正太分布产生的随机数 + 偏差
  // 产生小球的坐标
  x = standardDeviation * num + mean;
  
  num = (float) gen.nextGaussian();
  y = standardDeviation * num + mean;
  // 画球
  ellipse(x,y,15,15);
  
}

ball1

 

这里是偏差为40时的运行截图,注意到因为偏差比较小小球都集中在屏幕中心,也就是中值附近.

当偏差为80时运行截图为:

ball2

 

注意到偏差较大的时候,求不在是特别集中于中值附近,成较分散状.