http://blog.csdn.NET/tianjian4592/article/details/44222565
在實際的開發中,很多時候還會遇到相對比較複雜的需求,比如產品妹紙或UI妹紙在哪看了個讓人興奮的效果,興致高昂的來找你,看了之後目的很明確,當然就是希望你能給她;
在這樣的關鍵時候,身子板就一定得硬了,可千萬別說不行,爺們兒怎麼能說不行呢;
好了,爲了讓大家都能給妹紙們想要的,後面會逐漸分享一些比較比較不錯的效果,目的只有一個,通過自定義view實現我們所能實現的動效;
今天主要分享水波紋效果:
1.標準正餘弦水波紋;
2.非標準圓形液柱水波紋;
雖說都是水波紋,但兩者在實現上差異是比較大的,一個通過正餘弦函數模擬水波紋效果,另外一個會運用到圖像的混合模式(PorterDuffXfermode);
先看效果:
自定義View根據實際情況可以選擇繼承自View、TextView、ImageView或其他,我們先只需要瞭解如何利用Android給我們提供好的利刃去滿足UI妹紙;
這次的實現我們都選擇繼承view,在實現的過程中我們需要關注如下幾個方法:
1.onMeasure():最先回調,用於控件的測量;
2.onSizeChanged():在onMeasure後面回調,可以拿到view的寬高等數據,在橫豎屏切換時也會回調;
3.onDraw():真正的繪製部分,繪製的代碼都寫到這裏面;
既然如此,我們先複寫這三個方法,然後來實現如上兩個效果;
一:標準正餘弦水波紋
這種水波紋可以用具體函數模擬出具體的軌跡,所以思路基本如下:
1.確定水波函數方程
2.根據函數方程得出每一個波紋上點的座標;
3.將水波進行平移,即將水波上的點不斷的移動;
4.不斷的重新繪製,生成動態水波紋;
有了上面的思路,我們一步一步進行實現:
正餘弦函數方程爲:
y = Asin(wx+b)+h ,這個公式裏:w影響週期,A影響振幅,h影響y位置,b爲初相;
根據上面的方程選取自己覺得中意的波紋效果,確定對應參數的取值;
然後根據確定好的方程得出所有的方程上y的數值,並將所有y值保存在數組裏:
- // 將週期定爲view總寬度
- mCycleFactorW = (float) (2 * Math.PI / mTotalWidth);
- // 根據view總寬度得出所有對應的y值
- for (int i = 0; i < mTotalWidth; i++) {
- mYPositions[i] = (float) (STRETCH_FACTOR_A * Math.sin(mCycleFactorW * i) + OFFSET_Y);
- }
- for (int i = 0; i < mTotalWidth; i++) {
- // 減400只是爲了控制波紋繪製的y的在屏幕的位置,大家可以改成一個變量,然後動態改變這個變量,從而形成波紋上升下降效果
- // 繪製第一條水波紋
- canvas.drawLine(i, mTotalHeight - mResetOneYPositions[i] - 400, i,
- mTotalHeight,
- mWavePaint);
- // 繪製第二條水波紋
- canvas.drawLine(i, mTotalHeight - mResetTwoYPositions[i] - 400, i,
- mTotalHeight,
- mWavePaint);
- }
這種方式類似於數學裏面的細分法,一條波紋,如果橫向以一個像素點爲單位進行細分,則形成view總寬度條直線,並且每條直線的起點和終點我們都能知道,在此基礎上我們只需要循環繪製出所有細分出來的直線(直線都是縱向的),則形成了一條靜態的水波紋;
接下來我們讓水波紋動起來,之前用了一個數組保存了所有的y值點,有兩條水波紋,再利用兩個同樣大小的數組來保存兩條波紋的y值數據,並不斷的去改變這兩個數組中的數據:
- private void resetPositonY() {
- // mXOneOffset代表當前第一條水波紋要移動的距離
- int yOneInterval = mYPositions.length - mXOneOffset;
- // 使用System.arraycopy方式重新填充第一條波紋的數據
- System.arraycopy(mYPositions, mXOneOffset, mResetOneYPositions, 0, yOneInterval);
- System.arraycopy(mYPositions, 0, mResetOneYPositions, yOneInterval, mXOneOffset);
- int yTwoInterval = mYPositions.length - mXTwoOffset;
- System.arraycopy(mYPositions, mXTwoOffset, mResetTwoYPositions, 0,
- yTwoInterval);
- System.arraycopy(mYPositions, 0, mResetTwoYPositions, yTwoInterval, mXTwoOffset);
- }
如此下來只要不斷的改變這兩個數組的數據,然後不斷刷新,即可生成動態水波紋了;
刷新可以調用invalidate()或postInvalidate(),區別在於後者可以在子線程中更新UI
整體代碼如下:
- public class DynamicWave 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 = 7;
- // 第二條水波移動速度
- private static final int TRANSLATE_X_SPEED_TWO = 5;
- private float mCycleFactorW;
- private int mTotalWidth, mTotalHeight;
- private float[] mYPositions;
- private float[] mResetOneYPositions;
- private float[] mResetTwoYPositions;
- private int mXOffsetSpeedOne;
- private int mXOffsetSpeedTwo;
- private int mXOneOffset;
- private int mXTwoOffset;
- private Paint mWavePaint;
- private DrawFilter mDrawFilter;
- public DynamicWave(Context context, AttributeSet attrs) {
- super(context, attrs);
- // 將dp轉化爲px,用於控制不同分辨率上移動速度基本一致
- mXOffsetSpeedOne = UIUtils.dipToPx(context, TRANSLATE_X_SPEED_ONE);
- mXOffsetSpeedTwo = UIUtils.dipToPx(context, TRANSLATE_X_SPEED_TWO);
- // 初始繪製波紋的畫筆
- mWavePaint = new Paint();
- // 去除畫筆鋸齒
- mWavePaint.setAntiAlias(true);
- // 設置風格爲實線
- mWavePaint.setStyle(Style.FILL);
- // 設置畫筆顏色
- mWavePaint.setColor(WAVE_PAINT_COLOR);
- mDrawFilter = new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
- }
- @Override
- protected void onDraw(Canvas canvas) {
- super.onDraw(canvas);
- // 從canvas層面去除繪製時鋸齒
- canvas.setDrawFilter(mDrawFilter);
- resetPositonY();
- for (int i = 0; i < mTotalWidth; i++) {
- // 減400只是爲了控制波紋繪製的y的在屏幕的位置,大家可以改成一個變量,然後動態改變這個變量,從而形成波紋上升下降效果
- // 繪製第一條水波紋
- canvas.drawLine(i, mTotalHeight - mResetOneYPositions[i] - 400, i,
- mTotalHeight,
- mWavePaint);
- // 繪製第二條水波紋
- canvas.drawLine(i, mTotalHeight - mResetTwoYPositions[i] - 400, i,
- mTotalHeight,
- mWavePaint);
- }
- // 改變兩條波紋的移動點
- mXOneOffset += mXOffsetSpeedOne;
- mXTwoOffset += mXOffsetSpeedTwo;
- // 如果已經移動到結尾處,則重頭記錄
- if (mXOneOffset >= mTotalWidth) {
- mXOneOffset = 0;
- }
- if (mXTwoOffset > mTotalWidth) {
- mXTwoOffset = 0;
- }
- // 引發view重繪,一般可以考慮延遲20-30ms重繪,空出時間片
- postInvalidate();
- }
- private void resetPositonY() {
- // mXOneOffset代表當前第一條水波紋要移動的距離
- int yOneInterval = mYPositions.length - mXOneOffset;
- // 使用System.arraycopy方式重新填充第一條波紋的數據
- System.arraycopy(mYPositions, mXOneOffset, mResetOneYPositions, 0, yOneInterval);
- System.arraycopy(mYPositions, 0, mResetOneYPositions, yOneInterval, mXOneOffset);
- int yTwoInterval = mYPositions.length - mXTwoOffset;
- System.arraycopy(mYPositions, mXTwoOffset, mResetTwoYPositions, 0,
- yTwoInterval);
- System.arraycopy(mYPositions, 0, mResetTwoYPositions, yTwoInterval, mXTwoOffset);
- }
- @Override
- protected void onSizeChanged(int w, int h, int oldw, int oldh) {
- super.onSizeChanged(w, h, oldw, oldh);
- // 記錄下view的寬高
- mTotalWidth = w;
- mTotalHeight = h;
- // 用於保存原始波紋的y值
- mYPositions = new float[mTotalWidth];
- // 用於保存波紋一的y值
- mResetOneYPositions = new float[mTotalWidth];
- // 用於保存波紋二的y值
- mResetTwoYPositions = new float[mTotalWidth];
- // 將週期定爲view總寬度
- mCycleFactorW = (float) (2 * Math.PI / mTotalWidth);
- // 根據view總寬度得出所有對應的y值
- for (int i = 0; i < mTotalWidth; i++) {
- mYPositions[i] = (float) (STRETCH_FACTOR_A * Math.sin(mCycleFactorW * i) + OFFSET_Y);
- }
- }
二:非標準圓形液柱水波紋
前面的波形使用函數模擬,這個我們換種方式,採用圖進行實現,先用PS整張不像波紋的波紋圖;
爲了銜接緊密,首尾都比較平,並高度一致;
思路:
1.使用一個圓形圖作爲遮罩過濾波形圖;
2.平移波紋圖,即不斷改變繪製的波紋圖的區域,即srcRect;
3.當一個週期繪製完,則從波紋圖的最前面重新計算;
首先初始化bitmap:
- private void initBitmap() {
- mSrcBitmap = ((BitmapDrawable) getResources().getDrawable(R.drawable.wave_2000))
- .getBitmap();
- mMaskBitmap = ((BitmapDrawable) getResources().getDrawable(
- R.drawable.circle_500))
- .getBitmap();
- }
使用drawable獲取的方式,全局只會生成一份,並且系統會進行管理,而BitmapFactory.decode()出來的則decode多少次生成多少張,務必自己進行recycle;
然後繪製波浪和遮罩圖,繪製時設置對應的混合模式:
- /*
- * 將繪製操作保存到新的圖層
- */
- int sc = canvas.saveLayer(0, 0, mTotalWidth, mTotalHeight, null, Canvas.ALL_SAVE_FLAG);
- // 設定要繪製的波紋部分
- mSrcRect.set(mCurrentPosition, 0, mCurrentPosition + mCenterX, mTotalHeight);
- // 繪製波紋部分
- canvas.drawBitmap(mSrcBitmap, mSrcRect, mDestRect, mBitmapPaint);
- // 設置圖像的混合模式
- mBitmapPaint.setXfermode(mPorterDuffXfermode);
- // 繪製遮罩圓
- canvas.drawBitmap(mMaskBitmap, mMaskSrcRect, mMaskDestRect,
- mBitmapPaint);
- mBitmapPaint.setXfermode(null);
- canvas.restoreToCount(sc);
爲了形成動態的波浪效果,單開一個線程動態更新要繪製的波浪的位置:
- new Thread() {
- public void run() {
- while (true) {
- // 不斷改變繪製的波浪的位置
- mCurrentPosition += mSpeed;
- if (mCurrentPosition >= mSrcBitmap.getWidth()) {
- mCurrentPosition = 0;
- }
- try {
- // 爲了保證效果的同時,儘可能將cpu空出來,供其他部分使用
- Thread.sleep(30);
- } catch (InterruptedException e) {
- }
- postInvalidate();
- }
- };
- }.start();
主要過程就以上這些,全部代碼如下:
- public class PorterDuffXfermodeView extends View {
- private static final int WAVE_TRANS_SPEED = 4;
- private Paint mBitmapPaint, mPicPaint;
- private int mTotalWidth, mTotalHeight;
- private int mCenterX, mCenterY;
- private int mSpeed;
- private Bitmap mSrcBitmap;
- private Rect mSrcRect, mDestRect;
- private PorterDuffXfermode mPorterDuffXfermode;
- private Bitmap mMaskBitmap;
- private Rect mMaskSrcRect, mMaskDestRect;
- private PaintFlagsDrawFilter mDrawFilter;
- private int mCurrentPosition;
- public PorterDuffXfermodeView(Context context, AttributeSet attrs) {
- super(context, attrs);
- initPaint();
- initBitmap();
- mPorterDuffXfermode = new PorterDuffXfermode(PorterDuff.Mode.DST_IN);
- mSpeed = UIUtils.dipToPx(mContext, WAVE_TRANS_SPEED);
- mDrawFilter = new PaintFlagsDrawFilter(Paint.ANTI_ALIAS_FLAG, Paint.DITHER_FLAG);
- new Thread() {
- public void run() {
- while (true) {
- // 不斷改變繪製的波浪的位置
- mCurrentPosition += mSpeed;
- if (mCurrentPosition >= mSrcBitmap.getWidth()) {
- mCurrentPosition = 0;
- }
- try {
- // 爲了保證效果的同時,儘可能將cpu空出來,供其他部分使用
- Thread.sleep(30);
- } catch (InterruptedException e) {
- }
- postInvalidate();
- }
- };
- }.start();
- }
- @Override
- protected void onDraw(Canvas canvas) {
- super.onDraw(canvas);
- // 從canvas層面去除鋸齒
- canvas.setDrawFilter(mDrawFilter);
- canvas.drawColor(Color.TRANSPARENT);
- /*
- * 將繪製操作保存到新的圖層
- */
- int sc = canvas.saveLayer(0, 0, mTotalWidth, mTotalHeight, null, Canvas.ALL_SAVE_FLAG);
- // 設定要繪製的波紋部分
- mSrcRect.set(mCurrentPosition, 0, mCurrentPosition + mCenterX, mTotalHeight);
- // 繪製波紋部分
- canvas.drawBitmap(mSrcBitmap, mSrcRect, mDestRect, mBitmapPaint);
- // 設置圖像的混合模式
- mBitmapPaint.setXfermode(mPorterDuffXfermode);
- // 繪製遮罩圓
- canvas.drawBitmap(mMaskBitmap, mMaskSrcRect, mMaskDestRect,
- mBitmapPaint);
- mBitmapPaint.setXfermode(null);
- canvas.restoreToCount(sc);
- }
- // 初始化bitmap
- private void initBitmap() {
- mSrcBitmap = ((BitmapDrawable) getResources().getDrawable(R.drawable.wave_2000))
- .getBitmap();
- mMaskBitmap = ((BitmapDrawable) getResources().getDrawable(
- R.drawable.circle_500))
- .getBitmap();
- }
- // 初始化畫筆paint
- private void initPaint() {
- mBitmapPaint = new Paint();
- // 防抖動
- mBitmapPaint.setDither(true);
- // 開啓圖像過濾
- mBitmapPaint.setFilterBitmap(true);
- mPicPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
- mPicPaint.setDither(true);
- mPicPaint.setColor(Color.RED);
- }
- @Override
- protected void onSizeChanged(int w, int h, int oldw, int oldh) {
- super.onSizeChanged(w, h, oldw, oldh);
- mTotalWidth = w;
- mTotalHeight = h;
- mCenterX = mTotalWidth / 2;
- mCenterY = mTotalHeight / 2;
- mSrcRect = new Rect();
- mDestRect = new Rect(0, 0, mTotalWidth, mTotalHeight);
- int maskWidth = mMaskBitmap.getWidth();
- int maskHeight = mMaskBitmap.getHeight();
- mMaskSrcRect = new Rect(0, 0, maskWidth, maskHeight);
- mMaskDestRect = new Rect(0, 0, mTotalWidth, mTotalHeight);
- }
- }
源碼CSDN下載地址:http://download.csdn.net/download/tianjian4592/8496385
轉載自http://blog.csdn.net/jdsjlzx/article/details/44601239