Override of View and LinearLayout in android (implement background bubbles and ripple effects)
Two days ago, I read two blogs that copy the ripple effect in android L.
Android L Implementation of Water ripple click Effect
Android custom component series [14] -- Android5.0 button ripple effect implementation
The first article implements a ripple layout. After clicking all the controls in it, the ripple effect will appear.
The second article is to implement a ripple view. After clicking it, the page will show a ripple effect.
Based on my understanding of the two blogs, I implemented a similar thing myself. I didn't find a suitable screen recording software, so I had to record the ripple speed much faster, I can see what it means. If you don't adjust the speed, it's still elegant.
Just like the above control, the background inside is a rewritten TextView, and the background is always a bubble that keeps breathing.
Here, we will contact the previous two blogs (we suggest you first look at it) to introduce how to implement background animation in View and ViewGroup, and record your understanding of the knowledge points in it.
For the moment, this effect is named as a breathing bubble. (I later found that this figure does not move. Well, I don't know why it's not a waste of time. I just want to imagine that the entire background has a circle, which is constantly zoomed, and randomly centered, this is just random to the upper left corner)
First, ViewGroup
Take the first blog as an example to record your understanding.
1. Obtain coordinates. This is an important step. You need to use the click Control and draw ripple.
@Overrideprotected void onLayout(boolean changed, int l, int t, int r, int b) {super.onLayout(changed, l, t, r, b);this.getLocationOnScreen(mLocationInScreen);}
The coordinates in the upper left corner of the current layout on the screen are obtained.
2. obtain the clicked control. The obtained result must be determined based on the coordinates. In the dispatchTouchEvent method, the current Click Event MotionEvent will be passed in, which will bring the coordinates of the current click, there are two methods to obtain the data: getX and getRawX. The difference between the two methods is described in the view coordinate system. To calculate click events, here we need to obtain the coordinates of the relative screen. In fact, the whole part of the layout is actually the coordinates of the relative screen, because the layout will appear nested, and the relative coordinates after nesting are incorrect, therefore, the coordinates of the screen are used for calculation. After obtaining the coordinates of the click event, you can use the coordinates to find the clicked control. The whole process blog is very detailed.
3. After you get the clicked control, You need to draw a ripple on the control. Here the code will be pasted.
// View painting process: first draw the background, then draw yourself (onDraw), then draw the child element (dispatchDraw), and finally draw some decorations, such as the scroll bar (onDrawScrollBars) // to prevent the ripple from being blocked by the drawn sub-element, click "Draw" and then draw the ripple @ Overrideprotected void dispatchDraw (Canvas canvas) {super. dispatchDraw (canvas); if (! MShouldDoAnimation | mTargetWidth <0 | mTouchTarget = null) {return;} if (mRevealRadius> mMinBetweenWidthAndHeight/2) {// when the radius exceeds the shorter side, increase the diffusion speed to complete the diffusion as soon as possible. mRevealRadius + = mRevealRadiusGap * 4;} else {// ripple spreads mRevealRadius + = mRevealRadiusGap when the radius increments.} this. getLocationOnScreen (mLocationInScreen); // obtain the coordinates of the current layout --- 1 ?? Int [] location = new int [2]; mTouchTarget. getLocationOnScreen (location); // obtain the coordinate of the click control --- 2 ?? Int left = location [0]-mLocationInScreen [0]; // --- 3 ?? Int top = location [1]-mLocationInScreen [1]; int right = left + mTouchTarget. getMeasuredWidth (); int bottom = top + mTouchTarget. getMeasuredHeight (); canvas. save (); canvas. clipRect (left, top, right, bottom); // --- 4 ?? Canvas. drawCircle (mCenterX, mCenterY, mRevealRadius, mPaint); canvas. restore (); if (mRevealRadius <= mMaxRevealRadius) {postInvalidateDelayed (INVALIDATE_DURATION, left, top, right, bottom); // --- 5 ??} Else if (! MIsPressed) {mShouldDoAnimation = false; postInvalidateDelayed (INVALIDATE_DURATION, left, top, right, bottom );}}
The whole painting process is like the above. We should focus on the cutting of the canvas and the calculation of coordinates, the calculation of the Drawing Process and the radius, and so on.
1 ?? The coordinates in the upper left corner of the layout are obtained.
2 ?? The coordinates in the upper left corner of the control are obtained.
3 ?? The coordinates of the control minus the coordinates of the layout are the relative coordinates of the layout on the control. The relative coordinates here are actually the concepts of getLeft and getTop, but they cannot be used in this way, it is possible that there are nested la s between controls and la S.
4 ?? In 3 ?? You have obtained the coordinates of the control relative to the layout. Here, we cut down the corresponding position of the control on the canvas of the layout and draw a circle on it to improve the performance.
5 ?? After the circle is drawn, You need to draw a circle with a larger radius to achieve the diffusion effect. Therefore, you need to refresh postInvalidateDelayed, and only refresh the area corresponding to the control when refreshing, to improve performance
Basically, this is the case. The original blog has already explained in detail. Here I will only record my understanding.
Then View
Let's take a look at several steps.
1. Obtain the width and height information of the current control (used to initialize the bubble radius and other information)
2. Obtain the Click Event (this event is used as the center of the bubble. When there is no click event, it uses a random coordinate as the center of the bubble. Click it to move to the click position)
3. Draw bubbles
The following is the rewritten BreathTextView code.
Public class extends TextView {private extends breathCircle; public JasonBreathTextView (Context context) {super (context) ;}public JasonBreathTextView (Context context, AttributeSet attrs) {super (context, attrs ); breathCircle = new JasonBreathCircle (context) ;}@ Overrideprotected void onSizeChanged (int w, int h, int oldw, int oldh) {// TODO Auto-generated method stubsuper. onSizeChanged (w, h, oldw, oldh); breathCircle. initParameters (this) ;}@ Overridepublic boolean onTouchEvent (MotionEvent event) {breathCircle. setCircleCenter (int) event. getX (), (int) event. getY (); return super. onTouchEvent (event) ;}@ Overrideprotected void onDraw (Canvas canvas) {breathCircle. draw (canvas); super. onDraw (canvas);}/*** start watermark effect */public void startReveal () {breathCircle. start ();}/*** stop watermark effect */public void stopReveal () {breathCircle. stop ();}}
This is the control used in the figure to inherit from TextView.
For ease of use, I separated the bubble implementation from the control implementation. In this way, no matter which View is implemented, the separated bubble class can be directly used. The usage is like the above, you only need to pass the view control to the bubble, and the bubble will be drawn on the control.
The bubble BreathCircle code is as follows:
Public class JasonBreathCircle {private static int DIFFUSE_GAP = 2; // increment of the diffusion radius private static final int INVALIDATE_DURATION = 10; // interval of each refresh private Context mContext; private boolean needToDrawReveal = false; // painting flag bit // attributes of the circle itself private boolean isLargerMode = true; // breathing mode private Paint mPaintReveal; // brush private int mCircleCenterX; // center xprivate int mCircleCenterY; // center yprivate int mCurRadius; // current RADIUS priv Ate int mMaxRadius; // maximum radius // attributes of the attached control. The height and width are used to calculate the position of the current touch point private View mParentView; // The attached control private int mParentHeight; // control height private int mParentWidth; // control width // ======================== initialization method (required) ===================/ *** instantiate a circle, and then call initParameters to initialize the attributes of the circle, then, you can draw ** @ param context */public JasonBreathCircle (Context context) {mContext = context; initPaint () ;}/ *** to input a view for initializing coordinates, radius. Circle starts from the center by default ** @ param view */Public void initParameters (View view) {this. mParentView = view; // obtain the attribute mParentHeight = mParentView of the currently attached control. getHeight (); mParentWidth = mParentView. getWidth (); // initialize the circle property mMaxRadius = (int) Math. hypot (view. getHeight (), view. getWidth ()/2; // obtain the initial center mCircleCenterX = mParentWidth/2; mCircleCenterY = mParentHeight/2 ;} /*** input canvas ** @ param Canvas */public void draw (canvas) {if (needToDra WReveal) {canvas. save (); canvas. drawCircle (mCircleCenterX, mCircleCenterY, mCurRadius, mPaintReveal); canvas. restore (); if (isLargerMode & mCurRadius <mMaxRadius) {mCurRadius + = DIFFUSE_GAP; // ripple increments postRevealInvalidate ();} else if (mCurRadius> 0 &&! IsLargerMode) {// draw mCurRadius-= DIFFUSE_GAP from the beginning after a cycle is completed; // ripple increments postRevealInvalidate ();} else {// switch mode isLargerMode =! IsLargerMode; // randomly selects coordinates as the center of the circle, and obtains x from 0 to the rightmost center, and ysetCircleCenter (JasonRandomUtil from 0 to the end side. nextInt (0, mParentWidth), JasonRandomUtil. nextInt (0, mParentHeight); // After the center is changed, set the current radius to the maximum before narrowing down to prevent blank overwrites on the edge if (! IsLargerMode) {mCurRadius = mMaxRadius;} postRevealInvalidate ();}}} // ======================== external interface ==============================/*** start breathing */public void start () {if (needToDrawReveal) {return;} needToDrawReveal = true; postRevealInvalidate ();}/*** stop breathing */public void stop () {if (! NeedToDrawReveal) {return;} needToDrawReveal = false; reset (); postRevealInvalidate ();} /*** set the center ** @ param x * @ param y */public void setCircleCenter (int x, int y) {mCircleCenterX = x; mCircleCenterY = y; mMaxRadius = JasonRadiusUtil. getMaxRadius (mCircleCenterX, mCircleCenterY, mParentWidth, mParentHeight);}/*** sets whether the circle is hollow or solid. The default value is solid ** @ param isHollow */public void setHollow (boolean isHollow) {mPaintReve Al. setStyle (isHollow? Paint. style. STROKE: Paint. style. FILL );} // ======================== internal implementation ================================/**** Reset */private void reset () {mCurRadius = 0; isLargerMode = true;}/*** initialize Paint brush */private void initPaint () {mPaintReveal = new Paint (); mPaintReveal. setColor (mContext. getResources (). getColor (R. color. jason_bg_common_green_light); mPaintReveal. setAntiAlias (true);}/*** redraw */private void postRevealInvalidate () {mParentView. postInvalidateDelayed (INVALIDATE_DURATION );}}
The onSizeChanged method is used to obtain the width and height information of the control.
Click events are obtained in onTouchEvent.
Finally, the onDraw method is selected.
If you have read the previous two blogs, you can record them here:
1 ?? There are several ways to use the rendering method. Let's take a look at the view rendering process: first draw the background, then draw yourself (onDraw), and then draw the child element (dispatchDraw ), finally, draw some decorations, such as the scroll bar (onDrawScrollBars)
This is because the bubble to be drawn is used as the background, so you need to complete the bubble painting to draw some of the view's own things, so it is most appropriate in onDraw
2 ?? About coordinates, due to the influence of layout rewriting on coordinate processing in the first blog, it is a waste of time here. In fact, in view rewriting, coordinates are used during repainting, you only need to know the width and height of the view, because the canvas passed in by onDraw is the size of the control,
Therefore, you do not need to crop the canvas as in the layout. You just need to draw the canvas directly on it. The coordinate system can be directly built based on the width and height, and the origin is in the upper left corner.