First on the renderings
The effect of sprinkling beans, a whim, thought the animation was very interesting, so I took the time to write a play
Drawing process:
Define 6 "beans". Each bean has its own attributes, size, throwing speed, etc. then control the direction and state of each bean. The rebound effect uses the differencing device BounceInterpolator
package com.fragmentapp.view.beans; import android.animation.Animator; import android.animation.ValueAnimator; import android.content.Context; import android.graphics.Canvas; import android.graphics.Paint; import android.util.AttributeSet; import android.util.Log; import android.view.View; import android.view.animation.BounceInterpolator; import com.fragmentapp.R; import com.fragmentapp.helper.RandomUtil; /** * Created by liuzhen on 2017/1/17. */ public class BeansView extends View { private Paint paint; private int mWidth; private int mHeight; private int top; private ValueAnimator va; private Beans beans1,beans2,beans3,beans4,beans5,beans6; public BeansView(Context context) { this(context, null); } public BeansView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public BeansView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } private void init() { Log.e("tag","init"); setWillNotDraw(false); paint = new Paint(); paint.setAntiAlias(true); paint.setStyle(Paint.Style.FILL); paint.setColor(getResources().getColor(R.color.color_ff9c19)); //Randomly generate the size of the sphere beans1 = new Beans(RandomUtil.random(5,15)); beans2 = new Beans(RandomUtil.random(5,15)); beans3 = new Beans(RandomUtil.random(5,15)); beans4 = new Beans(RandomUtil.random(5,15)); beans5 = new Beans(RandomUtil.random(5,15)); beans6 = new Beans(RandomUtil.random(5,15)); } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); if (changed) { mWidth = getWidth(); mHeight = getHeight(); this.top = top; startAnim(); } } @Override protected void onDraw(Canvas canvas) { //Drop to the right normally. Here, you can also use randomly generated directions. Here, you can fix three on the left and three on the right canvas.drawCircle(beans1.getCx(), beans1.getCy(), beans1.getRadius(), paint); canvas.drawCircle(beans2.getCx(), beans2.getCy(), beans2.getRadius(), paint); canvas.drawCircle(beans3.getCx(), beans3.getCy(), beans3.getRadius(), paint); //Let the ball fall to the left canvas.drawCircle(-beans4.getCx()+mWidth, beans4.getCy(), beans4.getRadius(), paint); canvas.drawCircle(-beans5.getCx()+mWidth, beans5.getCy(), beans5.getRadius(), paint); canvas.drawCircle(-beans6.getCx()+mWidth, beans6.getCy(), beans6.getRadius(), paint); } public void startAnim() { if (mWidth == 0) return; beans1.setState(0); beans1.setOff(0); beans1.setRand(RandomUtil.random(20));//Speed value of randomly generated throw beans2.setState(0); beans2.setOff(0); beans2.setRand(RandomUtil.random(20)); beans3.setState(0); beans3.setOff(0); beans3.setRand(RandomUtil.random(20)); beans4.setState(0); beans4.setOff(0); beans4.setRand(RandomUtil.random(20)); beans5.setState(0); beans5.setOff(0); beans5.setRand(RandomUtil.random(20)); beans6.setState(0); beans6.setOff(0); beans6.setRand(RandomUtil.random(20)); va = ValueAnimator.ofFloat(top, mHeight - top); va.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { float val = (float)animation.getAnimatedValue(); beans1.setCy(val); beans1.move(mWidth);//Move the coordinates first. It's actually changed off Offset value beans1.setCx(mWidth / 2 + beans1.getOff());//Refresh X Axis coordinates beans2.setCy(val); beans2.move(mWidth); beans2.setCx(mWidth / 2 + beans2.getOff()); beans3.setCy(val); beans3.move(mWidth); beans3.setCx(mWidth / 2 + beans3.getOff()); beans4.setCy(val); beans4.move(mWidth); beans4.setCx(mWidth / 2 + beans4.getOff()); beans5.setCy(val); beans5.move(mWidth); beans5.setCx(mWidth / 2 + beans5.getOff()); beans6.setCy(val); beans6.move(mWidth); beans6.setCx(mWidth / 2 + beans6.getOff()); invalidate(); } }); va.addListener(new Animator.AnimatorListener() { @Override public void onAnimationStart(Animator animator) { } @Override public void onAnimationEnd(Animator animator) { //To prevent the sphere from falling to the ground at different levels due to different radius after stopping, unify the horizontal line beans1.setCy(mHeight - beans1.getRadius()); beans2.setCy(mHeight - beans2.getRadius()); beans3.setCy(mHeight - beans3.getRadius()); beans4.setCy(mHeight - beans4.getRadius()); beans5.setCy(mHeight - beans5.getRadius()); beans6.setCy(mHeight - beans6.getRadius()); invalidate(); } @Override public void onAnimationCancel(Animator animator) { } @Override public void onAnimationRepeat(Animator animator) { } }); va.setInterpolator(new BounceInterpolator());//Gravimeter va.setDuration(3000); va.setRepeatMode(ValueAnimator.RESTART); va.start(); } public void stopAnim() { va.cancel(); va = null; } }
Here, we mainly encapsulate the logic into individual objects, so the view class looks fresh
Here are the beans
package com.fragmentapp.view.beans; import android.util.Log; /** * Created by liuzhen on 2018/1/18. */ public class Beans { public Beans(){ } public Beans(int radius){ this.radius = radius; } /**X coordinate*/ private float cx; /**Y coordinate*/ private float cy; /**Offset*/ private float off; /**Randomly generated speed value*/ private float rand; /**Touch edge or not*/ private int state; /**The size of the ball*/ private float radius; /**Move the X-coordinate, and rebound after touching the boundary*/ public void move(int width){ if (cx < 0 || state == 1) {//Hit the left edge state = 1; off += rand; } else if (cx >= width || state == 2) {//Touch the right edge state = 2; off -= rand; }else if(state == 0) { state = 0; off += rand; } // Log.e("tag","-- cx "+(int)cx + " width "+width + " state "+state); } public float getCx() { return cx; } public void setCx(float cx) { this.cx = cx; } public float getOff() { return off; } public void setOff(float off) { this.off = off; } public float getRand() { return rand; } public void setRand(float rand) { this.rand = rand; } public int getState() { return state; } public void setState(int state) { this.state = state; } public float getCy() { return cy; } public void setCy(float cy) { this.cy = cy; } public float getRadius() { return radius; } public void setRadius(float radius) { this.radius = radius; } }
The main logic is concentrated in the move method
The default is to throw it normally, and then bounce back after hitting the edge
Just focus on two methods
This is to put the control in a dialog, which I like. Obviously, dialog is not very suitable, or it can be added to the header of the pull-down library. The effect should be good
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@drawable/shape_dialog_bg" android:padding="@dimen/d20.0" android:orientation="vertical" android:id="@+id/root"> <com.fragmentapp.view.beans.BeansView android:id="@+id/beans" android:layout_width="@dimen/d350.0" android:layout_height="@dimen/d300.0" android:layout_gravity="center_horizontal" /> <!--<View--> <!--android:layout_width="match_parent"--> <!--android:layout_height="@dimen/d1.0"--> <!--android:background="@color/white"/>--> <TextView android:id="@+id/tv_val" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:layout_marginTop="@dimen/d20.0" android:text="Loading..." android:textColor="@color/color_cccccc" android:textSize="@dimen/d43.0" /> </LinearLayout>