The old driver will take you to enjoy the effect of Alipay Ant Forest

The old driver will take you to enjoy the effect of Alipay Ant Forest

The old driver, it doesn't exist, in fact, I'm not quite there, haha. . .

The old driver will take you to enjoy the effect of Alipay Ant Forest

Recently, the company's product suddenly has a function similar to Alipay Ant Forest. The general function is similar to Alipay Ant Forest. After looking at the effect of Alipay Ant Forest, it is best to use RN to implement this kind of thing, but since the product has decided to choose Native, we can't kill the product, right? Who told us to move bricks? Before the renderings come out, pull out a control first, and after the company renderings come out, you can put them on it and use them directly.

First of all, let's take a general look at Alipay's Ant Forest renderings:

The old driver will take you to enjoy the effect of Alipay Ant Forest

This is the rendering of my current implementation:

The old driver will take you to enjoy the effect of Alipay Ant Forest

When we get this demand, we will analyze it first, and don't start it when we are busy, otherwise it will be easy to roll over on the ground. . The functions that need to be implemented are:

1. Customize the small ball, the text in the ball, floating up and down, disappearing animation;

2. Dynamically add small balls according to the data, and the positions are randomly distributed around the small tree and cannot overlap. This point is the most important and involves the design of a random position generation algorithm.

Well, when we have determined the functions we want to implement, we can start coding step by step.

custom ball

This is relatively easy to implement, draw a circle, and then draw text in the garden. The animation implementation is uniformly realized by attribute animation. The code is as follows, and the comments are written in detail and will not be explained one by one, lazy...

/** * @author: xiaohaibin. * @time: 2018/1/5 * @mail:[email protected] * @github:https://github.com/xiaohaibin * @describe: Custom imitation Alipay Ant Forest Waterdrop View */ public class WaterView extends View { private Paint paint; private ObjectAnimator mAnimator; /** * Text color*/ private int textColor = Color.parseColor("#69c78e"); /** * Water drop fill color*/ private int waterColor = Color.parseColor("#c3f593"); /** * Ball stroke color*/ private int storkeColor = Color.parseColor("#69c78e"); /** * stroke line width*/ private float strokeWidth = 0.5f; /** * Text font size*/ private float textSize = 36; /** * Calculated according to the difference between the distance and the distance to the radius ratio*/ private float proportion; /** * The radius of the water drop ball* / private int mRadius = 30; /** * Ball text content*/ private String textContent="3g"; public WaterView(Context context) { super(context); init(); } public WaterView(Context context,@Nullable AttributeSet attrs) { super(context, attrs); init(); } public WaterView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } private void init() { paint = new Paint(); paint.setAntiAlias(true); } @Override public void draw(Canvas canvas) { super.draw(canvas); drawCircleView(canvas); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); setMeasuredDimension(Utils.dp2px(getContext(), (int) (2 * (mRadius+strokeWidth))),Utils.dp2px(getContext(), (int) (2 * (mRadius+strokeWidth)))); } @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); start(); } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); stop();} @Override protected void onVisibilityChanged(@NonNull View changedView, int visibility) { super.onVisibilityChanged(changedView, visibility); if (visibility == VISIBLE) { start(); } else { stop(); } } private void drawCircleView(Canvas canvas){ //圆球 paint.setColor(waterColor); paint.setStyle(Paint.Style.FILL); canvas.drawCircle(Utils.dp2px(getContext(), mRadius), Utils.dp2px(getContext(), mRadius), Utils.dp2px(getContext(), mRadius), paint); //描边 paint.setColor(storkeColor); paint.setStyle(Paint.Style.STROKE); paint.setStrokeWidth(Utils.dp2px(getContext(), (int) strokeWidth)); canvas.drawCircle(Utils.dp2px(getContext(), mRadius), Utils.dp2px(getContext(), mRadius), Utils.dp2px(getContext(), (int) (mRadius+strokeWidth)) , paint); //圆球文字 paint.setTextSize(textSize); paint.setColor(textColor); paint.setStyle(Paint.Style.FILL);drawVerticalText(canvas, Utils.dp2px(getContext(), mRadius), Utils.dp2px(getContext(), mRadius), textContent); } private void drawVerticalText(Canvas canvas, float centerX, float centerY, String text) { Paint.FontMetrics fontMetrics = paint.getFontMetrics(); float baseLine = -(fontMetrics.ascent + fontMetrics.descent) / 2; float textWidth = paint.measureText(text); float startX = centerX - textWidth / 2; float endY = centerY + baseLine; canvas.drawText(text, startX, endY, paint); } public void start() { if (mAnimator == null) { mAnimator = ObjectAnimator.ofFloat(this, "translationY", -6.0f, 6.0f, -6.0f); mAnimator.setDuration(3500); mAnimator.setInterpolator(new LinearInterpolator()); mAnimator.setRepeatMode(ValueAnimator.RESTART); mAnimator.setRepeatCount(ValueAnimator.INFINITE); mAnimator.start(); } else if (!mAnimator.isStarted()) { mAnimator.start(); } } public void stop() { if (mAnimator != null) { mAnimator.cancel(); mAnimator = null; } } public float getProportion() { return proportion; } public void setProportion(float proportion) { this.proportion = proportion; } }

Dynamic random addition of small balls

Here I use the integrated FrameLayout to dynamically add the balls by setting the ball data. By carefully observing the effect of Alipay Ant Forest, we can find that the general small balls are randomly distributed directly above the tree. So I want to take the root of the small tree as the center, the height of the small tree is a fan shape, and randomly place small balls above this fan shape.   

Formula: Coordinate = rotation angle * radius * the ratio of the radius that should be calculated according to the distance

The formula for calculating the coordinates of any point (x1, y1) on the circle:

x1 = x0 + r * cos (at * 3.14 / 180)

y1 = y0 + r * sin (ao * 3.14 / 180)

The old driver will take you to enjoy the effect of Alipay Ant Forest

JAVA learning exchange QQ group: 288351179

The specific implementation code is as follows:

/** * @author: xiaohaibin. * @time: 2018/1/5 * @mail:[email protected] * @github:https://github.com/xiaohaibin * @describe: Alipay Ant Forest Water Droplet Energy* / public class WaterFlake extends FrameLayout { private OnWaterItemListener mOnWaterItemListener; /** * Small tree coordinate X */ private float treeCenterX = 0; /** * Small tree coordinate Y */ private float treeCenterY = 0; /** * Small tree height */ private int radius = 80; /** * start angle*/ private double mStartAngle = 0; /** * whether energy is being collected or not*/ private boolean isCollect = false; public WaterFlake(@NonNull Context context) { super(context ); } public WaterFlake(@NonNull Context context, @Nullable AttributeSet attrs) { super(context, attrs); } public WaterFlake(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr) ; } @Override public boolean onTouchEvent(MotionEvent event) { if (event.getAction() == MotionEvent.ACTION_DOWN) { int x = (int) event.getX(); int y = (int) event.getY(); Rect rect = new Rect(); for (int i = 0; i < getChildCount(); i++) { getChildAt(i).getHitRect(rect); if (rect.contains(x, y)) { if (mOnWaterItemListener != null) { getChildAt(i).performClick(); mOnWaterItemListener.onItemClick(i); startAnimator(getChildAt(i)); return true; } } } } return super.onTouchEvent(event); } @Override public boolean performClick() { return super.performClick(); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); measureChildren(widthMeasureSpec, heightMeasureSpec); } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { int childCount = getChildCount(); if (childCount==0){ return; } int left, top;// Calculate the angle according to the number of items float angleDelay = -180 / childCount; for (int i = 0; i < childCount; i++) { WaterView child = (WaterView) getChildAt(i); mStartAngle %= 180; // Set the coordinate information of the CircleView small dots//coordinates = rotation angle * radius * the ratio of the radius that should be calculated according to the difference between the distance and distance// Then any point on the circle is: (x1, y1) // x1 = x0 + r * cos(ao * 3.14 /180 ) // y1 = y0 + r * sin(ao * 3.14 /180 ) if (child.getVisibility() != GONE) { left = (int) (getTreeCenterX() + radius * Math .cos(mStartAngle * 3.14 / 180) * (child.getProportion() / radius * 2)); top = (int) (getTreeCenterY() + radius * Math.sin(mStartAngle * 3.14 / 180) * (child.getProportion () / radius * 2)); child.layout(left, top, left + child.getMeasuredWidth(), top + child.getMeasuredWidth()); } mStartAngle += angleDelay; } } /** * Set the ball data , create the number of balls according to the data collection* * @param modelList data collection*/ public void setModelList(List<WaterModel> modelList, float treeCenterX,float treeCenterY) { this.treeCenterX = treeCenterX; this.treeCenterY = treeCenterY; for (int i = 0; i < modelList.size(); i++) { WaterView waterView = new WaterView(getContext(),(i+1)+"g"); waterView.setProportion(Utils.getRandom(radius, radius + 80)); addView(waterView); } } /** * 设置小球点击事件 * * @param onWaterItemListener */ public void setOnWaterItemListener(OnWaterItemListener onWaterItemListener) { mOnWaterItemListener = onWaterItemListener; } public interface OnWaterItemListener { void onItemClick(int pos); } private void startAnimator(final View view) { if (isCollect) { return; } isCollect = true; ObjectAnimator translatAnimatorY = ObjectAnimator.ofFloat(view, "translationY", getTreeCenterY()); translatAnimatorY.start(); ObjectAnimator alphaAnimator = ObjectAnimator.ofFloat(view, "alpha", 1f, 0f);alphaAnimator.start(); AnimatorSet animatorSet = new AnimatorSet(); animatorSet.play(translatAnimatorY).with(alphaAnimator); animatorSet.setDuration(3000); animatorSet.start(); animatorSet.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { removeViewInLayout(view); isCollect = false; } }); } public float getTreeCenterX() { return treeCenterX; } public float getTreeCenterY() { return treeCenterY; } }} public float getTreeCenterY() { return treeCenterY; } }} public float getTreeCenterY() { return treeCenterY; } }

There are many ways to implement the random algorithm for placing the balls. This is just one of them. If it is not well written, I hope you will correct me. If you want to change jobs but have insufficient skills or encounter bottlenecks in your work, I have one here. JAVA's free live course is about high-end knowledge points. If you have a bad foundation, please do not enter. As long as you have 1-5 years of development experience, you can join the group and ask me for a class link. Note: it is free without development experience. Do not enter) Welcome Let's share and learn together.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325443293&siteId=291194637