自定義控件--水波紋效果

前一段時間去百度面試,面試官讓實現(xiàn)如下效果的自定義控件的思路,這里整理一下,并且通過代碼實現(xiàn)這個效果的自定義View.

Wave.gif

先說一下思路:
思路: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的全部代碼.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市程拭,隨后出現(xiàn)的幾起案子定踱,更是在濱河造成了極大的恐慌,老刑警劉巖恃鞋,帶你破解...
    沈念sama閱讀 206,839評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件崖媚,死亡現(xiàn)場離奇詭異,居然都是意外死亡恤浪,警方通過查閱死者的電腦和手機(jī)畅哑,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來水由,“玉大人荠呐,你說我怎么就攤上這事。” “怎么了泥张?”我有些...
    開封第一講書人閱讀 153,116評論 0 344
  • 文/不壞的土叔 我叫張陵呵恢,是天一觀的道長。 經(jīng)常有香客問我媚创,道長渗钉,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,371評論 1 279
  • 正文 為了忘掉前任钞钙,我火速辦了婚禮鳄橘,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘歇竟。我一直安慰自己挥唠,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,384評論 5 374
  • 文/花漫 我一把揭開白布焕议。 她就那樣靜靜地躺著宝磨,像睡著了一般。 火紅的嫁衣襯著肌膚如雪盅安。 梳的紋絲不亂的頭發(fā)上唤锉,一...
    開封第一講書人閱讀 49,111評論 1 285
  • 那天,我揣著相機(jī)與錄音别瞭,去河邊找鬼窿祥。 笑死,一個胖子當(dāng)著我的面吹牛蝙寨,可吹牛的內(nèi)容都是我干的晒衩。 我是一名探鬼主播,決...
    沈念sama閱讀 38,416評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼墙歪,長吁一口氣:“原來是場噩夢啊……” “哼听系!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起虹菲,我...
    開封第一講書人閱讀 37,053評論 0 259
  • 序言:老撾萬榮一對情侶失蹤靠胜,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后毕源,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體浪漠,經(jīng)...
    沈念sama閱讀 43,558評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,007評論 2 325
  • 正文 我和宋清朗相戀三年霎褐,在試婚紗的時候發(fā)現(xiàn)自己被綠了址愿。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,117評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡瘩欺,死狀恐怖必盖,靈堂內(nèi)的尸體忽然破棺而出拌牲,到底是詐尸還是另有隱情,我是刑警寧澤歌粥,帶...
    沈念sama閱讀 33,756評論 4 324
  • 正文 年R本政府宣布塌忽,位于F島的核電站,受9級特大地震影響失驶,放射性物質(zhì)發(fā)生泄漏土居。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,324評論 3 307
  • 文/蒙蒙 一嬉探、第九天 我趴在偏房一處隱蔽的房頂上張望擦耀。 院中可真熱鬧,春花似錦涩堤、人聲如沸眷蜓。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽吁系。三九已至,卻和暖如春白魂,著一層夾襖步出監(jiān)牢的瞬間汽纤,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評論 1 262
  • 我被黑心中介騙來泰國打工福荸, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留蕴坪,地道東北人。 一個月前我還...
    沈念sama閱讀 45,578評論 2 355
  • 正文 我出身青樓敬锐,卻偏偏與公主長得像背传,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子台夺,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,877評論 2 345

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,519評論 25 707
  • 一续室、Android開發(fā)初體驗 二、Android與MVC設(shè)計模式模型對象存儲著應(yīng)用的數(shù)據(jù)和業(yè)務(wù)邏輯谒养。模型類通常用來...
    為夢想戰(zhàn)斗閱讀 875評論 0 3
  • 青竹成篾十道工, 劈削抽刮技不同明郭。 十指即是傳感器买窟, 上挑下壓多復(fù)重。 編織縛滕有花樣薯定, 方梭圓尖顯其功始绍。 篾匠都...
    南竹山閱讀 205評論 0 1
  • 昨夜寤寐不寧,今天早早就瞌睡了话侄,于是乎睡了個早午覺亏推。午飯時想起睡中和植物神經(jīng)的一番爭執(zhí)一時陷入了凝思学赛。 事情是這樣...
    西子若然閱讀 226評論 0 0
  • 連續(xù)十次的沙盤小組已過半。十個女人拋開最擅長的言語在沙盤的小小方寸間上演一場場恩怨情仇吞杭。讀懂自己盏浇,看到他人,似乎并...
    秘宓閱讀 197評論 0 0