前言
Android 开发中自定义View的重要性不言而喻,这里就结合github上一个自定义view的代码来进行分析,代码中提供了动态效果,使用两种方式来生成了自定义View。
效果图:
以下是介绍。
自定义View
在安卓开发艺术探索中,自定义View被分为4类
- 继承View重写onDraw方法
- 继承ViewGroup派生特殊的Layout
- 继承特定的View
- 继承特定的ViewGroup
这里就使用上述1和4来实现效果。
继承View重写onDraw方法
这种方法主要用于实现一些不规则的效果,需要自己重写onDraw方法,同时需要自己支持wrap_content,并且padding也需要自己处理,这里因为实现的两个View会添加到一个重写的ViewGroup里,所以没有实现。
首先是简单的Solid:
class Solid extends View {
// This is a Rect to cover the main view
private Paint aboveWavePaint;
private Paint blowWavePaint;
public Solid(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public Solid(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
// 这里为1则在LinearLayout中会填满剩余空间
params.weight = 1;
setLayoutParams(params);
}
public void setAboveWavePaint(Paint aboveWavePaint) {
this.aboveWavePaint = aboveWavePaint;
}
public void setBlowWavePaint(Paint blowWavePaint) {
this.blowWavePaint = blowWavePaint;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawRect(getLeft(), 0, getRight(), getBottom(), blowWavePaint);
canvas.drawRect(getLeft(), 0, getRight(), getBottom(), aboveWavePaint);
}
}
Solid实现的效果很简单,从onDraw中可以看到,Solid画了两个正方形,效果是实现了WaveView的下面部分,也就是波浪线的下方。
接下来是Wave,波浪线,不重要的代码被去除了,只关注最重要的部分:
// y=Asin(ωx+φ)+k
class Wave extends View {
......
// ω
private double omega;
public Wave(Context context, AttributeSet attrs) {
this(context, attrs, R.attr.waveViewStyle);
}
public Wave(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawPath(mBlowWavePath, mBlowWavePaint);
canvas.drawPath(mAboveWavePath, mAboveWavePaint);
}
......
/**
* calculate wave track
* this is why is can wave
* y=Asin(ωx+φ)+k
*/
private void calculatePath() {
mAboveWavePath.reset();
mBlowWavePath.reset();
getWaveOffset();
float y;
mAboveWavePath.moveTo(left, bottom);
// calculate the path by sin
for (float x = 0; x <= mMaxRight; x += X_SPACE) {
y = (float) (mWaveHeight * Math.sin(omega * x + mAboveOffset) + mWaveHeight);
mAboveWavePath.lineTo(x, y);
}
mAboveWavePath.lineTo(right, bottom);
mBlowWavePath.moveTo(left, bottom);
for (float x = 0; x <= mMaxRight; x += X_SPACE) {
y = (float) (mWaveHeight * Math.sin(omega * x + mBlowOffset) + mWaveHeight);
mBlowWavePath.lineTo(x, y);
}
mBlowWavePath.lineTo(right, bottom);
}
@Override
protected void onWindowVisibilityChanged(int visibility) {
super.onWindowVisibilityChanged(visibility);
if (View.GONE == visibility) {
removeCallbacks(mRefreshProgressRunnable);
} else {
removeCallbacks(mRefreshProgressRunnable);
mRefreshProgressRunnable = new RefreshProgressRunnable();
post(mRefreshProgressRunnable);
}
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
}
@Override
public void onWindowFocusChanged(boolean hasWindowFocus) {
super.onWindowFocusChanged(hasWindowFocus);
if (hasWindowFocus) {
if (mWaveLength == 0) {
startWave();
}
}
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
if (mWaveLength==0){
startWave();
}
}
private void startWave() {
if (getWidth() != 0) {
int width = getWidth();
mWaveLength = width * mWaveMultiple;
left = getLeft();
right = getRight();
bottom = getBottom() + 2;
mMaxRight = right + X_SPACE;
omega = PI2 / mWaveLength;
}
}
private void getWaveOffset() {
if (mBlowOffset > Float.MAX_VALUE - 100) {
mBlowOffset = 0;
} else {
mBlowOffset += mWaveHz;
}
if (mAboveOffset > Float.MAX_VALUE - 100) {
mAboveOffset = 0;
} else {
mAboveOffset += mWaveHz;
}
}
private class RefreshProgressRunnable implements Runnable {
public void run() {
synchronized (Wave.this) {
long start = System.currentTimeMillis();
calculatePath();
invalidate();
// update every 16ms
long gap = 16 - (System.currentTimeMillis() - start);
postDelayed(this, gap < 0 ? 0 : gap);
}
}
}
}
Wave的逻辑是这样的:
当窗口可视时,如果View不是GONE则调用post方法后台运行一个RefreshProgressRunnable对象
RefreshProgressRunnable对象则调用calculatePath方法计算要画的路径,然后调用postDelayed方法,在16ms内再次计算路径
calculatePath方法中则根据y=Asin(ωx+φ)+k,x以一定的间隔增大,从左到右的计算函数值并调用path的lineTo方法勾画路径,k则为设置的高度值,是水波的最高值
调用invalidate方法更新视图
onDraw方法中根据路径重画
继承特定的ViewGroup实现自定义View
这里是WaveView,它继承了LinearLayout,里面添加了Wave和Solid两个View,Wave负责水波纹效果,Solid则是水面下。
public class WaveView extends LinearLayout {
......
public WaveView(Context context, AttributeSet attrs) {
super(context, attrs);
setOrientation(VERTICAL);
......
addView(mWave);
addView(mSolid);
setProgress(mProgress);
}
// The height of Wave
public void setProgress(int progress) {
this.mProgress = progress > 100 ? 100 : progress;
computeWaveToTop();
}
@Override
public void onWindowFocusChanged(boolean hasWindowFocus) {
super.onWindowFocusChanged(hasWindowFocus);
if (hasWindowFocus) {
computeWaveToTop();
}
}
private void computeWaveToTop() {
mWaveToTop = (int) (getHeight() * (1f - mProgress / 100f));
ViewGroup.LayoutParams params = mWave.getLayoutParams();
if (params != null) {
((LayoutParams) params).topMargin = mWaveToTop;
}
mWave.setLayoutParams(params);
}
......
}
这个就比较简单了,computeWaveToTop计算wave视图到顶部的距离,以此实现WaveView progress的变化。
总结
上面这个例子实现了自定义View,继承View 重写了onDraw方法,同时继承LinearLayout,通过组合加上多线程的更新,达到了一个良好的效果。这也同时说明了自定义View对我们开发者有多重要,它通过特殊的排列组合能够完成令人眼前一亮的效果,大大拓展了安卓开发界面的效果。
参考资料
《Android开发艺术探索》
WaveView的github链接
个人思考
有时候会觉得遇到了瓶颈,有时候会不知道做什么,这时候就学点新东西吧,看看书吧,这种时候就是我们能够大进步的时候,有瓶颈说明我们有不足,即使无法认清我们的不足之处,我们也可以学点新的东西,把旧知识弄的深入。会有进步的。