前一段時間去百度面試,面試官讓實現(xiàn)如下效果的自定義控件的思路,這里整理一下,并且通過代碼實現(xiàn)這個效果的自定義View.
先說一下思路:
思路:1.該view是一個標(biāo)轉(zhuǎn)的正余弦函數(shù) Y=Asin(Wi+b)+h A表示幅度 W影響周期 b影響初始化位置 h影響高度
2.創(chuàng)建一個長度為控件width的數(shù)組,通過正余弦函數(shù)計算出各個像素上的Y坐標(biāo)的位置
3.指定一個平移的速度,根據(jù)這個速度不斷的改變數(shù)組
4.通過System.arraycopy來對數(shù)組進(jìn)行元素進(jìn)行平移的操作.(也可以通過不斷設(shè)置b初相的值來改變數(shù)組的元素,不過這種操作顯然更耗時間)
5.在ondraw中不斷的通過drawPoint,drawLine或者的Path的LineTo方法繪制(具體使用哪種都可以)
6.在一次繪制完成以后通過postInvalidate來刷新.
public class WaveView extends View {
// 波紋顏色
private static final int WAVE_PAINT_COLOR = 0x880000aa;
// y = Asin(wx+b)+h
private static final float STRETCH_FACTOR_A = 20;
private static final int OFFSET_Y = 0;
// 第一條水波移動速度
private static final int TRANSLATE_X_SPEED_ONE = 20;
// 第二條水波移動速度
private static final int TRANSLATE_X_SPEED_TWO = 40;
private float mCycleFactorW;
private int mWith, mHeight;
private float[] mYPositions;
private float[] mResetOneYPositions;
private float[] mResetTwoYPositions;
private int mXOffsetSpeed1;
private int mXOffsetSpeed2;
private int mX1Offset;
private int mX2Offset;
private Paint mWavePaint;
private DrawFilter mDrawFilter;
public WaveView(Context context, AttributeSet attrs) {
super(context, attrs);
// 將dp轉(zhuǎn)化為px躏精,用于控制不同分辨率上移動速度基本一致
mXOffsetSpeed1 = UIUtils.dipToPx(context, TRANSLATE_X_SPEED_ONE);
mXOffsetSpeed2 = UIUtils.dipToPx(context, TRANSLATE_X_SPEED_TWO);
// 初始繪制波紋的畫筆
mWavePaint = new Paint();
// 去除畫筆鋸齒
mWavePaint.setAntiAlias(true);
// 設(shè)置風(fēng)格為實線
mWavePaint.setStyle(Style.FILL);
// 設(shè)置畫筆顏色
mWavePaint.setColor(WAVE_PAINT_COLOR);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 從canvas層面去除繪制時鋸齒
canvas.setDrawFilter(mDrawFilter);
resetPositonY();
for (int i = 0; i < mWith; i++) {
// 減400只是為了控制波紋繪制的y的在屏幕的位置识虚,大家可以改成一個變量,然后動態(tài)改變這個變量瓶摆,從而形成波紋上升下降效果
// canvas.drawLine(i, mHeight - mResetOneYPositions[i] - 400, i,
// mHeight,
// mWavePaint);
// canvas.drawLine(i, mHeight - mResetTwoYPositions[i] - 400, i,
// mHeight,
// mWavePaint);
canvas.drawPoint(i,mHeight - mResetOneYPositions[i] - 400,mWavePaint);
canvas.drawPoint(i,mHeight - mResetTwoYPositions[i] - 400,mWavePaint);
}
// 改變兩條波紋的移動點
mX1Offset += mXOffsetSpeed1;
mX2Offset += mXOffsetSpeed2;
// 如果已經(jīng)移動到結(jié)尾處衷佃,則重頭記錄
if (mX1Offset >= mWith) {
mX1Offset = mXOffsetSpeed1;
}
if (mX2Offset > mWith) {
mX2Offset = mXOffsetSpeed2;
}
// 引發(fā)view重繪,一般可以考慮延遲20-30ms重繪序无,空出時間片
postInvalidateDelayed(20);
}
private void resetPositonY() {
// mX1Offset代表當(dāng)前第一條水波紋要移動的距離
int yOneInterval = mYPositions.length - mX1Offset;
// 使用System.arraycopy方式重新填充第一條波紋的數(shù)據(jù)
System.arraycopy(mYPositions, mX1Offset, mResetOneYPositions, 0, yOneInterval);
System.arraycopy(mYPositions, 0, mResetOneYPositions, yOneInterval, mX1Offset);
int yTwoInterval = mYPositions.length - mX2Offset;
System.arraycopy(mYPositions, mX2Offset, mResetTwoYPositions, 0,
yTwoInterval);
System.arraycopy(mYPositions, 0, mResetTwoYPositions, yTwoInterval, mX2Offset);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
// 記錄下view的寬高
mWith = w;
mHeight = h;
// 用于保存原始波紋的y值
mYPositions = new float[mWith];
// 用于保存波紋一的y值
mResetOneYPositions = new float[mWith];
// 用于保存波紋二的y值
mResetTwoYPositions = new float[mWith];
// 將周期定為view總寬度
mCycleFactorW = (float) (2 * Math.PI / mWith);
// 根據(jù)view總寬度得出所有對應(yīng)的y值
for (int i = 0; i < mWith; i++) {
mYPositions[i] = (float) (STRETCH_FACTOR_A * Math.sin(mCycleFactorW * i) + OFFSET_Y);
}
}
}
分別介紹一下
1.在構(gòu)造方法中初始化幾個重要的參數(shù)mXOffsetSpeed:余弦的的推進(jìn)速度,每一次繪制將每個像素上的Y增加mXOffsetSpeed個數(shù)值,初始化paint對象.
public WaveView(Context context, AttributeSet attrs) {
super(context, attrs);
// 將dp轉(zhuǎn)化為px蒸健,用于控制不同分辨率上移動速度基本一致
mXOffsetSpeed1 = UIUtils.dipToPx(context, TRANSLATE_X_SPEED_ONE);
mXOffsetSpeed2 = UIUtils.dipToPx(context, TRANSLATE_X_SPEED_TWO);
// 初始繪制波紋的畫筆
mWavePaint = new Paint();
// 去除畫筆鋸齒
mWavePaint.setAntiAlias(true);
// 設(shè)置風(fēng)格為實線
mWavePaint.setStyle(Style.FILL);
// 設(shè)置畫筆顏色
mWavePaint.setColor(WAVE_PAINT_COLOR);
}
2.onSizeChange方法在確認(rèn)view的width和heigh時調(diào)用,此時傳遞,在這個方法中初始化幾個數(shù)組,數(shù)組的大小跟view的寬度相等,這些數(shù)組用于存放每個像素上的Y軸值,并通過Math.sin方法生成每個像素對應(yīng)的值.存放到數(shù)組中.
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
// 記錄下view的寬高
mWith = w;
mHeight = h;
// 用于保存原始波紋的y值
mYPositions = new float[mWith];
// 用于保存波紋一的y值
mResetOneYPositions = new float[mWith];
// 用于保存波紋二的y值
mResetTwoYPositions = new float[mWith];
// 將周期定為view總寬度
mCycleFactorW = (float) (2 * Math.PI / mWith);
// 根據(jù)view總寬度得出所有對應(yīng)的y值
for (int i = 0; i < mWith; i++) {
mYPositions[i] = (float) (STRETCH_FACTOR_A * Math.sin(mCycleFactorW * i) + OFFSET_Y);
}
}
3.ondraw方法:當(dāng)view開始繪制時,根據(jù)數(shù)組每個像素的值,并通過canvas.drawLine來對x軸上的每個點畫線,這樣就繪制出了一個靜態(tài)的余弦曲線,如果僅僅只需要一個曲線而不需要曲線下方的顏色時用canvas.drawPoint來繪制,這兩種在代碼里都存在,可以通過自己的需求來使用特定的樣式.你也可以通過path的LineTo來繪制,這樣也是可行的
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 從canvas層面去除繪制時鋸齒
canvas.setDrawFilter(mDrawFilter);
resetPositonY();
for (int i = 0; i < mWith; i++) {
//通過drawline繪制波浪
// canvas.drawLine(i, mHeight - mResetOneYPositions[i] - 400, i,
// mHeight,
// mWavePaint);
// canvas.drawLine(i, mHeight - mResetTwoYPositions[i] - 400, i,
// mHeight,
// mWavePaint);
//通過drawPoint來畫曲線
// 減400只是為了控制波紋繪制的y的在屏幕的位置,大家可以改成一個變量媒怯,然后動態(tài)改變這個變量订讼,從而形成波紋上升下降效果
canvas.drawPoint(i,mHeight - mResetOneYPositions[i] - 400,mWavePaint);
canvas.drawPoint(i,mHeight - mResetTwoYPositions[i] - 400,mWavePaint);
}
4.在繪制一次只得到一個靜態(tài)的曲線,而如何將這個曲線推動起來呢?通過則需要在繪制完成以后調(diào)用postInvalidate來進(jìn)行下一次的繪制,而在繪制之前需要將x軸上每個點的高度改變,改變高度我們通過System.copyArrary來實現(xiàn)數(shù)組中元素的平移
public static void arraycopy(Object src, int srcPos. Object dest,int destPos,int length)
src:源數(shù)組; srcPos:源數(shù)組要復(fù)制的起始位置扇苞;
dest:目的數(shù)組; destPos:目的數(shù)組放置的起始位置寄纵; length:復(fù)制的長度鳖敷。
注意:src and dest都必須是同類型或者可以進(jìn)行轉(zhuǎn)換類型的數(shù)組.
你也可以通過sin(wx+b)的方式通過改變b來重新獲取一組坐標(biāo),但是這樣的是很消耗性能的.所以建議使用copyArray來實現(xiàn)
private void resetPositonY() {
// mX1Offset代表當(dāng)前第一條水波紋要移動的距離
int yOneInterval = mYPositions.length - mX1Offset;
// 使用System.arraycopy方式重新填充第一條波紋的數(shù)據(jù)
System.arraycopy(mYPositions, mX1Offset, mResetOneYPositions, 0, yOneInterval);
System.arraycopy(mYPositions, 0, mResetOneYPositions, yOneInterval, mX1Offset);
int yTwoInterval = mYPositions.length - mX2Offset;
System.arraycopy(mYPositions, mX2Offset, mResetTwoYPositions, 0,
yTwoInterval);
System.arraycopy(mYPositions, 0, mResetTwoYPositions, yTwoInterval, mX2Offset);
}
以上就是自定義view的全部代碼.