In daily development, often will encounter all sorts of visual effect, some effect may in a glance can let a person feel very complicated, but we must be clear: all complex dynamic effect is the basis of can be decomposed into a single action, such as scaling, translation and rotation these basic units, and then all the basic unit are combined, can produce a let a person shine at the moment of visual effect.

First take a look at the following effect:

Break it down as we mentioned above:

  1. The Logo name LitePlayer is split into a single text
  2. All the text is randomly scattered around the screen
  3. The middle Logo is hidden
  4. Logo text moves from a random position to a fixed position on the page
  5. The Logo image in the middle is gradually displayed with a small shift from bottom to top
  6. The Logo is combined with the broken text to form a name
  7. After the Logo is combined into the name, a gradual halo effect moves from left to right
  8. End of the animation

Once we disassemble the animation, we can construct an implementation for each disassembly unit.

  • First of all, let’s get rightlogoText animation to achieve:
  1. First for the data source, we expect to pass in onelogoInternally disassemble the string into a single literal array:
    // fill the text to array
    private void fillLogoTextArray(String logoName) {
        if (TextUtils.isEmpty(logoName)) {
            return;
        }
        if (mLogoTexts.size() > 0) {
            mLogoTexts.clear();
        }
        for (int i = 0; i < logoName.length(); i++) {
            charc = logoName.charAt(i); mLogoTexts.put(i, String.valueOf(c)); }}Copy the code
  1. All the text needs to be randomly scattered around the screen, because when it comes to coordinates, we canonSizeChangedIn thelogoInitialization of random position of text, at the same time, we build two sets to store the coordinate state of each text after being broken up and combined:
    // Coordinates of the final synthesized logo
    private SparseArray<PointF> mQuietPoints = new SparseArray<>();
    // The logo is randomly scattered coordinates
    private SparseArray<PointF> mRadonPoints = new SparseArray<>();

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mWidth = w;
        mHeight = h;
        initLogoCoordinate();
    }

    private void initLogoCoordinate(a) {
        float centerY = mHeight / 2f + mPaint.getTextSize() / 2 + mLogoOffset;
        // calculate the final xy of the text
        float totalLength = 0;
        for (int i = 0; i < mLogoTexts.size(); i++) {
            String str = mLogoTexts.get(i);
            float currentLength = mPaint.measureText(str);
            if(i ! = mLogoTexts.size() -1) {
                totalLength += currentLength + mTextPadding;
            } else{ totalLength += currentLength; }}// the draw width of the logo must small than the width of this AnimLogoView
        if (totalLength > mWidth) {
            throw new IllegalStateException("This view can not display all text of logoName, please change text size.");
        }
        float startX = (mWidth - totalLength) / 2;
        if (mQuietPoints.size() > 0) {
            mQuietPoints.clear();
        }
        for (int i = 0; i < mLogoTexts.size(); i++) {
            String str = mLogoTexts.get(i);
            float currentLength = mPaint.measureText(str);
            mQuietPoints.put(i, new PointF(startX, centerY));
            startX += currentLength + mTextPadding;
        }
        // generate random start xy of the text
        if (mRadonPoints.size() > 0) {
            mRadonPoints.clear();
        }
        // Build random initial coordinates
        for (int i = 0; i < mLogoTexts.size(); i++) {
            mRadonPoints.put(i, new PointF((float) Math.random() * mWidth, (float) Math.random() * mHeight)); }}Copy the code
  1. Construct the animation process, define an attribute animation to calculate the progress from 0-1, and redraw the text to move from the messy scattered coordinates to the final combined coordinates during the animation process:
    // init the translation animation
    private void initOffsetAnimation(a) {
        mOffsetAnimator = ValueAnimator.ofFloat(0.1);
        mOffsetAnimator.setDuration(mOffsetDuration);
        mOffsetAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
        mOffsetAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                if (mQuietPoints.size() <= 0 || mRadonPoints.size() <= 0) {
                    return;
                }
                mOffsetAnimProgress = (float) animation.getAnimatedValue(); invalidate(); }}); }@Override
    protected void onDraw(Canvas canvas) {
        if(! isOffsetAnimEnd) {// offset animation
            mPaint.setAlpha((int) Math.min(255.255 * mOffsetAnimProgress + 100));
            for (int i = 0; i < mQuietPoints.size(); i++) {
                PointF quietP = mQuietPoints.get(i);
                PointF radonP = mRadonPoints.get(i);
                float x = radonP.x + (quietP.x - radonP.x) * mOffsetAnimProgress;
                floaty = radonP.y + (quietP.y - radonP.y) * mOffsetAnimProgress; canvas.drawText(mLogoTexts.get(i), x, y, mPaint); }}}Copy the code
  1. At this point we’ve got thelogoNow that the text animation is done, let’s look at step 7 of the disassembly, and the lighting effect. For this lighting effect, the preferred solution is throughGradient+ShaderThe implementation. Since drawing gradients also involves coordinates, initialization of the animation is also includedonSizeChangedTo:
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mWidth = w;
        mHeight = h;
        initLogoCoordinate();// Initialize the coordinate animation
        initGradientAnimation(w);// Initialize the gradient animation
    }

    // init the gradient animation
    private void initGradientAnimation(int width) {
        mGradientAnimator = ValueAnimator.ofInt(0.2 * width);
        if(mGradientListener ! =null) {
            mGradientAnimator.addListener(mGradientListener);
        }
        mGradientAnimator.setDuration(mGradientDuration);
        mGradientAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                mMatrixTranslate = (int) animation.getAnimatedValue(); invalidate(); }}); mLinearGradient =new LinearGradient(-width, 0.0.0.new int[]{mTextColor, mGradientColor, mTextColor},
                new float[] {0.0.5 f.1}, Shader.TileMode.CLAMP);
        mGradientMatrix = new Matrix();
    }
Copy the code
  1. The gradient animation automatically plays after the text move animation ends, so we can initialize the text move animation to listen for the end of the animation and draw at the same timeonDrawTo draw text in:
    // init the translation animation
    private void initOffsetAnimation(a) {...// Initialize the movement animation. mOffsetAnimator.addListener(new AnimatorListenerAdapter() {

            @Override
            public void onAnimationEnd(Animator animation) {
                if(mGradientAnimator ! =null && isShowGradient) {
                    isOffsetAnimEnd = true; mPaint.setShader(mLinearGradient); mGradientAnimator.start(); }}}); }@Override
    protected void onDraw(Canvas canvas) {
        if(! isOffsetAnimEnd) {// offset animation.// Text moving animation. }else {// gradient animation
            for (int i = 0; i < mQuietPoints.size(); i++) {
                PointF quietP = mQuietPoints.get(i);
                canvas.drawText(mLogoTexts.get(i), quietP.x, quietP.y, mPaint);
            }
            mGradientMatrix.setTranslate(mMatrixTranslate, 0); mLinearGradient.setLocalMatrix(mGradientMatrix); }}Copy the code
  1. At this point, text animation has been implemented. What’s left is the definition of some custom properties, providing some external propertiessetterandgetterAt the same time, you need to consider resource release of the animation during the page life cycle. Ok, let’s see what we achieved:

  1. For the above Logo picture animation can be a separateImageViewPan + transparency animation implementation, I don’t need the space to describe it here.

I believe that most of the students have mastered the custom View, but for complex animation, whether can use these skilled ability on the blade, maybe some students will be at a loss to see a gorgeous effect. This paper does not carry out in-depth analysis of animation, nor involve complex data operation. It just illustrates a general method of dynamic effect analysis through a simple example. Through this way of thinking, you can clearly understand the realization and goals of each step.

Finally, to sum up, for custom motion effects, we can first let THE UI provide the final visual effect, analyze the single frame through the tool, observe the action relationship between each frame, and break it down into each basic unit. Each unit step is then implemented, and finally integrated to achieve a coherent effect. This is the idea, and once you’re comfortable with this idea, you need to have some knowledge of mathematics, trigonometric functions, matrix operations, and so on. As long as these two aspects of ability are well cultivated, any complex dynamic effect in daily development is not to be feared.

Attached project source code address: github.com/seagazer/an…