Android L water ripple click effect implementation, android water ripple

Source: Internet
Author: User

Android L water ripple click effect implementation, android water ripple

The blogger participated in the 2014 CSDN blog Star Selection. please vote for me.

Click to vote for me


Preface

Some time ago, android L (android 5.0) came out and made some changes on the interface, mainly by adding some animations and some new controls, I believe you are very impressed with the click Effect of view-water ripple. Click a view and then a water ripple will spread from the click. This article will analyze the implementation of this effect. First, let's talk about the implementation on L. This ripple effect provides an animation called Reveal effect, the underlying layer of the view is achieved by obtaining the canvas of the view and refreshing the view continuously. This effect requires the support of the view, but does not support the view in earlier versions. Therefore, the Reveal effect cannot be directly run in earlier versions. However, after learning about its effect and its principles, we can still simulate it to achieve this effect. In all fairness, it is not hard or easy to write a custom view with ripple effects. However, there are many child classes of view. If we want to implement buttons, edit, and other controls one by one, this is complicated. So, do we want to find a simpler way? Actually, we can write a custom layout and make all the clickable elements in layout ripple. This greatly simplifies the entire process. Next, this article will analyze the implementation of layout. Before that, let's take a look at the effect.


Implementation idea

First, we will customize a layout. Here we select LinearLayout. As for the reason, we will analyze it below. When a user clicks a clickable element, such as a button, we need to obtain information about the elements clicked by the user, including: the width, height, and location information of the clicked element. After obtaining the button information, I can determine the water ripple range, and then re-draw the water ripple through layout, so that the water ripple effect will be realized. Of course, this is only a rough step, some details need to be processed in the middle.

Layout Selection

Since we plan to implement a custom layout, which layout should we select? LinearLayout, RelativeLayout, and FrameLayout? Here I use LinearLayout. Why? Some may ask, shouldn't RelativeLayout be used? Because RelativeLayout is powerful, Complex layout can be implemented, but LinearLayout and FrameLayout won't work. Yes, RelativeLayout is powerful, but considering that the water wave effect is implemented by refreshing layout frequently, due to frequent re-painting, we need to consider performance issues, relativeLayout has the worst performance (because there are many things to do), because we chose LinearLayout for the sake of performance. As for FrameLayout, it is too simple and not suitable for use. When implementing a Complex layout, we can wrap LinearLayout outside the elements with ripple effects, so that there will be no excessive tasks during re-painting.

According to the above analysis, we define the following layout:

Public class RevealLayout extends LinearLayout implements Runnable

Implementation Process

The implementation process mainly solves the following problems:

1. How to know which element the user clicked

2. How to obtain the information of the clicked Element

3. How to redraw water ripple through layout

4. Delayed event delivery

The following is an analysis.

How to know which element the user clicks

In order to know which element the user clicks (this element can be clicked in general, otherwise it is meaningless), we need to intercept all click events in advance. Therefore, we should rewrite the dispatchTouchEvent method in layout. Note that onInterceptTouchEvent is not recommended here, because onInterceptTouchEvent is not always called back. For details, refer to the view system parsing series I wrote earlier. Then, when a user clicks, there will be a series of down, move, and up events. We need to determine the element on which the event falls, and the down element is the element clicked by the user, of course, for the sake of rigor, we also need to determine whether the group falls on the same element, because the judgment rules of the system click event are: both down and up fall on the same clickable element.

    @Override    public boolean dispatchTouchEvent(MotionEvent event) {        int x = (int) event.getRawX();        int y = (int) event.getRawY();        int action = event.getAction();        if (action == MotionEvent.ACTION_DOWN) {            View touchTarget = getTouchTarget(this, x, y);            if (touchTarget.isClickable() && touchTarget.isEnabled()) {                mTouchTarget = touchTarget;                initParametersForChild(event, touchTarget);                postInvalidateDelayed(INVALIDATE_DURATION);            }        } else if (action == MotionEvent.ACTION_UP) {            mIsPressed = false;            postInvalidateDelayed(INVALIDATE_DURATION);            mDispatchUpTouchEventRunnable.event = event;            postDelayed(mDispatchUpTouchEventRunnable, 400);            return true;        } else if (action == MotionEvent.ACTION_CANCEL) {            mIsPressed = false;            postInvalidateDelayed(INVALIDATE_DURATION);        }        return super.dispatchTouchEvent(event);    }
Through the code above, we can know that when we go down, we take out the screen coordinates of the Click Event and traverse the view tree to find the view that the user clicked. The Code is as follows, it is to judge whether the event coordinates fall within the view range. This is not to be said, so it is easy to understand. Note that we cannot use getX and getY for event coordinates, but getRawX and getRawY for event coordinates. The difference between the two is that the former is relative to the coordinate of the clicked view, the latter is relative to the screen coordinates, and we cannot know which layer of the target view is located in layout. Therefore, the absolute coordinates of the screen must be used for calculation. With the event coordinates, based on the absolute coordinates of the view on the screen, you only need to determine whether the event xy falls within the top, bottom, and left corners of the view, and whether the event falls on The view, in this way, the view clicked by the user is taken out.

    private View getTouchTarget(View view, int x, int y) {        View target = null;        ArrayList<View> TouchableViews = view.getTouchables();        for (View child : TouchableViews) {            if (isTouchPointInView(child, x, y)) {                target = child;                break;            }        }        return target;    }    private boolean isTouchPointInView(View view, int x, int y) {        int[] location = new int[2];        view.getLocationOnScreen(location);        int left = location[0];        int top = location[1];        int right = left + view.getMeasuredWidth();        int bottom = top + view.getMeasuredHeight();        if (view.isClickable() && y >= top && y <= bottom                && x >= left && x <= right) {            return true;        }        return false;    }
It is relatively simple to obtain the information of the clicked elements. The information of the clicked elements include width, height, left, top, right, and bottom. The code for obtaining these elements is as follows:

        int[] location = new int[2];        mTouchTarget.getLocationOnScreen(location);        int left = location[0] - mLocationInScreen[0];        int top = location[1] - mLocationInScreen[1];        int right = left + mTouchTarget.getMeasuredWidth();        int bottom = top + mTouchTarget.getMeasuredHeight();
Note: mTouchTarget refers to the view clicked by the user.

How to redraw water ripple through layout

This will make water ripple relatively simple, as long as you use drawCircle to draw a translucent ring, here we mainly talk about the time to draw. In general, we will choose to draw in onDraw. This is correct, but it is not suitable for the effect in L. We will understand the view painting process, to draw a view, follow the following procedure: 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). Therefore, if we draw ripple in onDraw, the child element will cover the ring we have drawn because it is drawn after onDraw. In this way, the ring may not be completely complete, this is because it is important to set the time for drawing. Based on the view painting process, we select dispatchDraw. When all the child elements are drawn, the ripple is drawn. After reading this, you will understand why we should choose LinearLayout and why we do not recommend that the nested view level be too deep, because if the view itself is heavy or the nested level is too deep, this will increase the execution time of dispatchDraw, so that the drawing of water waves will have some influence. Therefore, smooth performance is also important in the Code and needs to be considered. At the same time, to prevent the drawn ring from exceeding the range of the clicked element, we need to clip the canvas. In order to make the ripple effect, we need to redraw layout frequently and change the radius of the ring during the redrawing process, so that a dynamic water ripple will come out. Still, for performance consideration, we chose to use postInvalidateDelayed (long delayMilliseconds, int left, int top, int right, int bottom) for partial repainting of the view, because, other regions do not need to be re-painted. Only the area where the clicked element is located needs to be re-painted. The reason why we need to use the Delayed method is that we cannot refresh it all the time and there must be a little interval. The advantage of doing so is: avoid overprovisioning time slices by redrawing the view, resulting in potential indirect stack overflow, because invalidate will directly lead to draw calls.

The Code is as follows:

    protected void dispatchDraw(Canvas canvas) {        super.dispatchDraw(canvas);        if (!mShouldDoAnimation || mTargetWidth <= 0 || mTouchTarget == null) {            return;        }        if (mRevealRadius > mMinBetweenWidthAndHeight / 2) {            mRevealRadius += mRevealRadiusGap * 4;        } else {            mRevealRadius += mRevealRadiusGap;        }        int[] location = new int[2];        mTouchTarget.getLocationOnScreen(location);        int left = location[0] - mLocationInScreen[0];        int top = location[1] - mLocationInScreen[1];        int right = left + mTouchTarget.getMeasuredWidth();        int bottom = top + mTouchTarget.getMeasuredHeight();        canvas.save();        canvas.clipRect(left, top, right, bottom);        canvas.drawCircle(mCenterX, mCenterY, mRevealRadius, mPaint);        canvas.restore();        if (mRevealRadius <= mMaxRevealRadius) {            postInvalidateDelayed(INVALIDATE_DURATION, left, top, right, bottom);        } else if (!mIsPressed) {            mShouldDoAnimation = false;            postInvalidateDelayed(INVALIDATE_DURATION, left, top, right, bottom);        }    }
So far, this layout has been implemented, but you will surely find something wrong. For example, you can add a click event to the button and initiate an activity when the button is clicked. Soon you will find the problem: the water wave has not been broadcast yet, activity gets up, leading to a big reduction in the Water Wave Effect. After carefully observing the effect of android L, we find that L always waits until the water wave effect is played to complete the next step. Therefore, the last problem to be solved will come out. Please refer to the following analysis.

How to delay the delivery of up Events

To solve the problem mentioned above, if we can delay the delivery of up time, such as a delay of 400 ms, the water wave will have enough time to finish playing and then distribute up events, this will solve the problem. At the very beginning, I did. Let's look at the following code first:

 else if (action == MotionEvent.ACTION_UP) {            mIsPressed = false;            postInvalidateDelayed(INVALIDATE_DURATION);            mDispatchUpTouchEventRunnable.event = event;            postDelayed(mDispatchUpTouchEventRunnable, 400);            return true;        } 
It can be found that when the group is up, I did not directly go through the distribution process of the system, but forced consumption of the group event and then delayed delivery. Please refer to the Code:

    private class DispatchUpTouchEventRunnable implements Runnable {        public MotionEvent event;        @Override        public void run() {            if (mTouchTarget == null || !mTouchTarget.isEnabled()) {                return;            }            if (isTouchPointInView(mTouchTarget, (int)event.getRawX(), (int)event.getRawY())) {                mTouchTarget.dispatchTouchEvent(event);            }        }    };

So far, the above issues have been analyzed, and we can easily achieve the click Effect of Water ripple.

Source code download

The demo source code in this article is not currently available on the Internet. Add a group215680213To download the source code from group sharing.

Related Article

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.