上篇声音识别动画(上)—–矩形声音识别动画中分析了矩形声音识别动画,
这一篇来分析线形(曲线)声音识别动画
再看一遍我们的效果图(曲线):
1.分析:
这个图看上去是曲线,这个曲线非常类似正余弦图像,安卓中画出正余弦
可以使用画贝塞尔曲线的方法.
简单介绍下贝塞尔曲线:
简单的说就是通过三个点,不断地取三点连线的中点,最后近似出一条光滑的曲线。
对贝塞尔曲线不太了解的可以先去看看 Android 绘图基础:Path(绘制三角形、
贝塞尔曲线、正余弦)这篇文章.
我们还需要清楚既然每个光滑的曲线至少需要三个点来确定,那么这段光滑的曲线就是1/2T(半个周期)
的正余弦曲线,那么要想画出一个周期的正余弦曲线就至少需要五个点(前半个周期的最后一个点是最后
一个周期的起点).而我们图中的曲线不止一个周期,所要确定的就要根据实际情况去确定,我们可以水平
方向每隔一段距离取一个点.
正弦曲线:
y=Asin(ωx+φ)+b
正余弦曲线和余弦曲线之间相差的就是初始值φ,正弦曲线向左右或者右平移变化的也就是φ.
每隔5个像素取一个点,根据屏幕的长度我们就知道总共有几个点,根据点的个数进行for循环,
求出每的点的坐标.
这个自定义控件我们需要这么几个值:
- 周期(T): ω=2π/T 我取的是400px 那么ω=2π/400
- 初始偏移量:φ 我取的初始偏移量为π
振幅A b明显等于1/2A 振幅我取的是35dp(注意转化成像素)
因此y=1/2*35+35*sin(2π/400*n*5+π)
好了,这样就可以画出一条完整的曲线,而且从最左边到最右边,下变看看
怎么让这条曲线在怎么动起来:
刚才有个初始偏移量:φ 如果我每隔100毫秒让这个φ增加1/4π,就会发现这条曲线动起来了
postDelayed(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
i = 0;
path1.reset();
startAngle1 += (Math.PI / 4);
path2.reset();
startAngle2 += (Math.PI / 4);
path3.reset();
startAngle3 += (Math.PI / 4);
postInvalidate();
}
}, 150);
2.下边贴出所有代码
自定义属性
<declare-styleable name="VoiceLine">
<attr name="amplitude_big" format="dimension|reference" />
<attr name="amplitude_small" format="dimension|reference" />
<attr name="lineColor" format="color|reference" />
<attr name="backColor" format="color|reference" />
<attr name="frequency" format="float" />
</declare-styleable>
布局文件
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:VoiceRect="http://schemas.android.com/apk/res-auto"
xmlns:VoiceLine="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#ffffff"
android:orientation="vertical"
tools:context="com.gelitenight.waveview.sample.TestVoiceLineActivity">
<com.yzhx.testvoiceani.VoiceLine
android:layout_width="match_parent"
android:layout_height="80dp"
VoiceLine:amplitude_big="35dp"
VoiceLine:amplitude_small="25dp"
VoiceLine:backColor="#ffffff"
VoiceLine:frequency="400"
VoiceLine:lineColor="#ff6600"
/>
</LinearLayout>
自定义控件类
public class VoiceLine extends View {
private int height;
private int width;
private Path path1;//第一条线
private Path path2;//第二条线
private Path path3;//第三条线
private Paint paint;
private float waveHeight_big;//大真毒
private float waveHeight_small;//小高度
private int lineColor;//线的颜色
private float amplitude_big;//大振幅
private float amplitude_small;//小振幅
private float frequency;//频率
private float frequency_small;//频率
/**
* 三条线的偏移
*/
private float startAngle1 = (float) (Math.PI / 4);
private float startAngle2 = (float) (Math.PI / 4 * 3);
private float startAngle3 = (float) (Math.PI);
private int i = 0;
public VoiceLine(Context context) {
this(context, null);
}
public VoiceLine(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initView(context, attrs);
}
public VoiceLine(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
private void initView(Context context, AttributeSet attrs) {
TypedArray ta = context.obtainStyledAttributes(attrs,
R.styleable.VoiceLine);
amplitude_big = ta.getDimension(R.styleable.VoiceLine_amplitude_big, 100);
amplitude_small = ta.getDimension(R.styleable.VoiceLine_amplitude_small, 50);
frequency = ta.getFloat(R.styleable.VoiceLine_frequency, 100);
lineColor = ta.getColor(R.styleable.VoiceLine_lineColor, Color.GREEN);
paint = new Paint();
paint.setColor(lineColor);
paint.setStrokeWidth(2);
paint.setStyle(Paint.Style.STROKE);
paint.setAntiAlias(true);
path1 = new Path();
path2 = new Path();
path3 = new Path();
ta.recycle();
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
// TODO Auto-generated method stub
super.onSizeChanged(w, h, oldw, oldh);
if (height == 0) {
height = getMeasuredHeight();
waveHeight_big = Math.min(height, amplitude_big) - 20;
waveHeight_small = Math.min(height, amplitude_small) - 20;
Log.e("KFJKFJ", "高度" + waveHeight_big + "getMeasuredHeight()" + getMeasuredHeight() + "amplitude_big" + amplitude_big);
}
if (width == 0) {
width = getMeasuredWidth();
}
}
@Override
protected void onDraw(Canvas canvas) {
// TODO Auto-generated method stub
super.onDraw(canvas);
// 每隔5个像素获取一个点
for (i = 0; i < width; i += 5) {
float y = (float) (waveHeight_big / 2 + waveHeight_big / 2 * Math.sin(i * (2 * Math.PI / frequency) + startAngle1)) + 10;
if (i == 0) {
//设置path的起点
path1.moveTo(0, y);
} else {
//连线
path1.lineTo(i, y);
}
y = (float) (waveHeight_big / 2 + waveHeight_big / 2 * Math.sin(i * (2 * Math.PI / frequency) + startAngle2)) + 10;
if (i == 0) {
//设置path的起点
path2.moveTo(0, y);
} else {
//连线
path2.lineTo(i, y);
}
y = (float) (waveHeight_small / 2 + waveHeight_small / 2 * Math.sin(i * (2 * Math.PI / frequency) + startAngle3)) + 10;
if (i == 0) {
//设置path的起点
path3.moveTo(0, y);
} else {
//连线
path3.lineTo(i, y);
}
}
//每隔150毫秒刷新一次界面 将所有的起点增加1/4π 让曲线动起来
postDelayed(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
i = 0;
path1.reset();
startAngle1 += (Math.PI / 4);
path2.reset();
startAngle2 += (Math.PI / 4);
path3.reset();
startAngle3 += (Math.PI / 4);
postInvalidate();
}
}, 150);
canvas.drawPath(path1, paint);
canvas.drawPath(path2, paint);
canvas.drawPath(path3, paint);
}
}