Custom view to achieve water ripple effect, custom view water ripple

Source: Internet
Author: User
Tags asin image filter

Custom view to achieve water ripple effect, custom view water ripple


In actual development, we often encounter relatively complex requirements, such as where the product paper or UI paper looks at exciting results and comes to you with high spirits, after reading it, the goal is clear. Of course, you want to give it to her;

At this critical time, the body must be hard, but never say no. How can ye say no;


Well, in order for everyone to give their sisters what they want, we will gradually share some of the better results, with only one purpose. We can achieve the dynamic effects through custom view;


Today, we will mainly share the effect of water ripple:

1. Standard positive cosine water ripple;

2. Non-Standard Circular Liquid Column water ripple;

Although they are all water ripple, there is a big difference in implementation between the two. One uses the positive cosine function to simulate the water ripple effect, and the other uses the mixed image mode (porterduxfermode );


First look at the effect:




You can choose to inherit the custom View, TextView, ImageView, or others based on the actual situation. First, we only need to know how to use android to provide us with a good edge to satisfy the UI sister paper;

In this implementation, we choose to inherit the view. in the implementation process, we need to pay attention to the following methods:

1. onMeasure (): The first callback for control measurement;

2. onSizeChanged (): callback after onMeasure. You can obtain the width and height of the view data, which will also be called back during screen switching;

3. onDraw (): The actual drawing part, and the drawn code is written here;

In this case, we will first repeat these three methods and then implement the above two effects;

I. Standard positive cosine water ripple

This kind of water ripple can be simulated with a specific function, so the idea is basically as follows:

1. Determine the Water Wave Function Equation

2. Draw the coordinates of each point on the ripple according to the function equation;

3. Translate the water waves, that is, the points on the water waves are constantly moving;

4. Continuous re-drawing to generate dynamic water ripple;

With the above ideas, we will implement them step by step:

The positive cosine function equation is:

Y = Asin (wx + B) + h. In this formula, w affects the cycle, A affects the amplitude, h affects the y position, and B indicates the initial phase;

Select the desired ripple effect based on the above equation and determine the value of the corresponding parameter;

Then, the values of y on all equations are obtained based on the determined equation, and all y values are saved in the array:

// Set the cycle to the total view width mCycleFactorW = (float) (2 * Math. PI/mTotalWidth); // obtain all corresponding y values for (int I = 0; I <mTotalWidth; I ++) based on the total view width) {mYPositions [I] = (float) (STRETCH_FACTOR_A * Math. sin (mCycleFactorW * I) + OFFSET_Y );}


Based on all the obtained y values, two static ripples can be drawn in onDraw using the following code:

For (int I = 0; I <mTotalWidth; I ++) {// only to control the position of y drawn by ripple on the screen, you can change it to a variable, then dynamically change this variable to form a ripple rise and fall effect // draw the first water ripple canvas. drawLine (I, mTotalHeight-mResetOneYPositions [I]-400, I, mTotalHeight, mWavePaint); // draw the second ripple canvas. drawLine (I, mTotalHeight-mResetTwoYPositions [I]-400, I, mTotalHeight, mWavePaint );}

This method is similar to the subdivision method in mathematics. If a ripple is segmented horizontally by a pixel, the total width of the view is formed in a straight line, in addition, we can know the starting and ending points of each line. On this basis, we only need to draw all the subdivided straight lines cyclically (the straight lines are vertical), forming a static water ripple;

Next we let the water ripple move up. We used an array to save all the y value points, there were two water ripple, and two arrays of the same size were used to save the y value data of two ripple, and constantly change the data in these two Arrays:

Private void resetPositonY () {// mXOneOffset indicates the distance from the first ripple to be moved. int yOneInterval = mYPositions. length-mXOneOffset; // use System. in arraycopy mode, refill the first corrugated data 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 );}

In this case, as long as the data of these two arrays is constantly changed and then refreshed, dynamic water ripples can be generated;

Refresh can call invalidate () or postInvalidate (). The difference is that the latter can update the UI in the Child thread.

The overall code is as follows:

Public class DynamicWave extends View {// ripple color private static final int WAVE_PAINT_COLOR = 0x8820.aa; // y = Asin (wx + B) + h private static final float STRETCH_FACTOR_A = 20; private static final int OFFSET_Y = 0; // The first wave moving speed private static final int TRANSLATE_X_SPEED_ONE = 7; // The second wave moving speed private static final int TRANSLATE_X_SPEED_TWO = 5; private float mCycleFactorW; private int mTotalWidth, mTotalHeight; private float [] mYPositions; private float [] attributes; private float [] attributes; 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); // convert dp to px, used to control the movement speed of different resolutions. mXOffsetSpeedOne = UIUtils. dipToPx (context, TRANSLATE_X_SPEED_ONE); mXOffsetSpeedTwo = UIUtils. dipToPx (context, TRANSLATE_X_SPEED_TWO); // Paint brush mWavePaint = new Paint (); // remove the brush sawtooth mWavePaint. setAntiAlias (true); // set the style to Real-line mWavePaint. setStyle (Style. FILL); // set the paint brush color 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); // remove the canvas at the canvas level. setDrawFilter (mDrawFilter); resetPositonY (); for (int I = 0; I <mTotalWidth; I ++) {// minus 400 is only used to control the position of y drawn by ripple on the screen. You can change it to a variable and then change it dynamically, in this way, the ripple rises and drops. // draw the first ripple canvas. drawLine (I, mTotalHeight-mResetOneYPositions [I]-400, I, mTotalHeight, mWavePaint); // draw the second ripple canvas. drawLine (I, mTotalHeight-interval [I]-400, I, mTotalHeight, mWavePaint);} // change the two ripple moving points mXOneOffset + = mXOffsetSpeedOne; mXTwoOffset + = mXOffsetSpeedTwo; // if it has been moved to the end, record it again if (mXOneOffset >=mtotalwidth) {mXOneOffset = 0 ;}if (mXTwoOffset> mTotalWidth) {mXTwoOffset = 0 ;} // trigger view re-painting. Generally, you can consider the delay of 20-30 ms re-painting. The blank time slice is postInvalidate ();} private void resetPositonY () {// mXOneOffset indicates the distance from the first ripple to be moved. int yOneInterval = mYPositions. length-mXOneOffset; // use System. in arraycopy mode, refill the first corrugated data 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); // record the width and height of the view mTotalWidth = w; mTotalHeight = h; // The y value for saving the original ripple mYPositions = new float [mTotalWidth]; // The y value mResetOneYPositions = new float [mTotalWidth] for saving the ripple; // The y value mResetTwoYPositions = new float [mTotalWidth] For saving ripple 2; // set the period to the total width of the view mCycleFactorW = (float) (2 * Math. PI/mTotalWidth); // obtain all corresponding y values for (int I = 0; I <mTotalWidth; I ++) based on the total view width) {mYPositions [I] = (float) (STRETCH_FACTOR_A * Math. sin (mCycleFactorW * I) + OFFSET_Y );}}

Ii. Non-Standard Circular Liquid Column water ripple

The preceding waveform uses function simulation. In this case, we use graphs for implementation. First, we use PS as a ripple chart;


In order to connect closely, both the beginning and end are relatively flat and highly consistent;

Ideas:

1. Use a circular chart as a mask to filter the waveform chart;

2. Translate the ripple, that is, the area of the drawn ripple, that is, srcRect;

3. When a cycle is drawn, it is re-calculated from the beginning of the ripple;


First initialize bitmap:

private void initBitmap() {        mSrcBitmap = ((BitmapDrawable) getResources().getDrawable(R.drawable.wave_2000))                .getBitmap();        mMaskBitmap = ((BitmapDrawable) getResources().getDrawable(                R.drawable.circle_500))                .getBitmap();    }
When drawable is used, only one copy is generated globally, and the system manages it. If BitmapFactory. decode () is used, the number of times decode is generated, and you must recycle it yourself;

Then draw the wave and mask graphs, and set the corresponding mixed mode when drawing:

/** Save the painting operation to the new layer */int SC = canvas. saveLayer (0, 0, mTotalWidth, mTotalHeight, null, Canvas. ALL_SAVE_FLAG); // set mSrcRect of the ripple to be drawn. set (mCurrentPosition, 0, mCurrentPosition + mCenterX, mTotalHeight); // draw the ripple canvas. drawBitmap (mSrcBitmap, mSrcRect, mDestRect, mBitmapPaint); // you can specify the image Mixing Mode mBitmapPaint. setXfermode (mporterduxfermode); // draw the canvas of the mask circle. drawBitmap (mMaskBitmap, mMaskSrcRect, mMaskDestRect, mBitmapPaint); mBitmapPaint. setXfermode (null); canvas. restoreToCount (SC );

To form a dynamic wave effect, a single thread dynamically updates the position of the wave to be drawn:

New Thread () {public void run () {while (true) {// changing the position of the drawn wave mCurrentPosition + = mSpeed; if (mCurrentPosition> = mSrcBitmap. getWidth () {mCurrentPosition = 0;} try {// to ensure the effect, try to empty the cpu for other parts to use Thread. sleep (30);} catch (InterruptedException e) {} postInvalidate ();}};}. start ();

The main process is the above. All the code is as follows:

Public class 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 porterduxfermode comment; private Bitmap mMaskBitmap; private Rect comment, mMaskDestRect; private comment mDrawFilter; private int mCurrentPosition; public comment (Context context, AttributeSet attrs) {super (context, attrs); initPaint (); initBitmap (); mporterduxfermode = new porterduxfermode (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) {// changing the position of the drawn wave mCurrentPosition + = mSpeed; if (mCurrentPosition> = mSrcBitmap. getWidth () {mCurrentPosition = 0;} try {// to ensure the effect, try to empty the cpu for other parts to use Thread. sleep (30);} catch (InterruptedException e) {} postInvalidate ();}};}. start () ;}@ Override protected void onDraw (Canvas canvas) {super. onDraw (canvas); // remove the Sawtooth canvas from the canvas layer. setDrawFilter (mDrawFilter); canvas. drawColor (Color. TRANSPARENT);/** Save the painting operation to the new layer */int SC = canvas. saveLayer (0, 0, mTotalWidth, mTotalHeight, null, Canvas. ALL_SAVE_FLAG); // set mSrcRect of the ripple to be drawn. set (mCurrentPosition, 0, mCurrentPosition + mCenterX, mTotalHeight); // draw the ripple canvas. drawBitmap (mSrcBitmap, mSrcRect, mDestRect, mBitmapPaint); // you can specify the image Mixing Mode mBitmapPaint. setXfermode (mporterduxfermode); // draw the canvas of the mask circle. drawBitmap (mMaskBitmap, mMaskSrcRect, mMaskDestRect, mBitmapPaint); mBitmapPaint. setXfermode (null); canvas. restoreToCount (SC);} // initialize bitmap private void initBitmap () {mSrcBitmap = (BitmapDrawable) getResources().getDrawable(R.drawable.wav e_2000 )). getBitmap (); mMaskBitmap = (BitmapDrawable) getResources (). getDrawable (R. drawable. circle_500 )). getBitmap () ;}// initialize paint private void initPaint () {mBitmapPaint = new Paint (); // anti-jitter mBitmapPaint. setDither (true); // enable image filter 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 );}}


Source code CSDN: http://download.csdn.net/download/tianjian4592/8496385







Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.