这一篇文章主要讲到的是那个循环动画,好了先把动画的样子奉上,请各位大佬轻喷:
关于这个动画,与遇上一个动画的联系就是,使用同样的方法去绘制的小方块,为了避免大家翻看过于麻烦,在这里再给大家展示出来,Been类:
/**
* @author: jjf
* @date: 2019/5/10
* @describe:View 参数
*/
public class BlockBeen {
//初始View位置
private int viewAddressX = 100;
private int viewAddressY = 200;
//View 大小
private int viewWide = 100;
private int viewHeight = 100;
//画view的画笔仓库
private Paint viewPain ;
//view仓库
private RectF rectF;
//地址状态 0:第一排 1:牧默认位置(中间一排) 2:下边一排
private int addressState;
public int getAddressState() {
return addressState;
}
public void setAddressState(int addressState) {
this.addressState = addressState;
}
public int getViewAddressX() {
return viewAddressX;
}
public void setViewAddressX(int viewAddressX) {
this.viewAddressX = viewAddressX;
}
public int getViewAddressY() {
return viewAddressY;
}
public void setViewAddressY(int viewAddressY) {
this.viewAddressY = viewAddressY;
}
String TAG="MoveBlockView";
public int getViewWide() {
return viewWide;
}
public void setViewWide(int viewWide) {
this.viewWide = viewWide;
}
public int getViewHeight() {
return viewHeight;
}
public void setViewHeight(int viewHeight) {
this.viewHeight = viewHeight;
}
public Paint getViewPain() {
return viewPain;
}
public void setViewPain(Paint viewPain) {
this.viewPain = viewPain;
}
public RectF getRectF() {
return rectF;
}
public void setRectF(RectF rectF) {
this.rectF = rectF;
}
}
还有一个填充数据并绘制的方法:
/**
* 画View
*
* @param canvas
* @param position 第几个view
*/
public void drawBlock(Canvas canvas, int position) {//viewAddressY+ position * (viewWide+padding)+viewHeight
BlockBeen blockBeen;
if (position >= blockBeens.size()) {
blockBeen = new BlockBeen();
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);//创建画笔
paint.setColor(getResources().getColor(R.color.colorPrimaryDark));//添加画笔颜色
blockBeen.setViewPain(paint);
} else {
blockBeen = blockBeens.get(position);
}
if (position == 0) {
blockBeen.setViewAddressX(viewAddressX);
blockBeen.setViewAddressY(viewAddressY - padding - blockBeen.getViewHeight());
} else {
blockBeen.setViewAddressX(viewAddressX + (position - 1) * (viewWide + padding));
blockBeen.setViewAddressY(viewAddressY);
}
blockBeen.setViewWide(viewWide);
blockBeen.setViewHeight(viewHeight);
RectF rectF;
if (blockBeen.getRectF() == null) {
rectF = new RectF(blockBeen.getViewAddressX(), blockBeen.getViewAddressY(),
blockBeen.getViewAddressX() + blockBeen.getViewWide(),
blockBeen.getViewAddressY() + blockBeen.getViewHeight());//先画一个矩形
blockBeen.setRectF(rectF);
} else {
rectF = blockBeen.getRectF();
rectF.set(blockBeens.get(position).getViewAddressX()
, blockBeens.get(position).getViewAddressY(), blockBeens.get(position).getViewAddressX() + blockBeens.get(position).getViewWide()
, blockBeens.get(position).getViewAddressY() + blockBeens.get(position).getViewHeight());
}
if (!blockBeens.contains(blockBeen)) {
blockBeens.add(blockBeen);
}
canvas.drawRoundRect(rectF, 30, 30, blockBeen.getViewPain());//根据提供的矩形为四个角画弧线,(其中的数字:第一个表示X轴方向大小,第二个Y轴方向大小。可以改成其他的,你可以自己体验),最后添加画笔。
//绘制View
invalidate();
}
为了与上一篇文章的代码混淆,在这里画一条分割线表示以上代码与上一篇文章相同:
-------------------------------------------我是分割线---------哦略略-------------------------------------------------------
然后循环动画的的核心代码,让他动起来:
public void moveRectf(Canvas canvas, BlockBeen blockBeen) {
blockBeen.getRectF().set(blockBeen.getViewAddressX()
, blockBeen.getViewAddressY(), blockBeen.getViewAddressX() + blockBeen.getViewWide()
, blockBeen.getViewHeight() + blockBeen.getViewAddressY() + mSpeedX);
//根据提供的矩形为四个角画弧线,(其中的数字:第一个表示X轴方向大小,第二个Y轴方向大小。可以改成其他的,你可以自己体验),最后添加画笔。
canvas.drawRoundRect(blockBeen.getRectF(), 30, 30, blockBeen.getViewPain());
setViewState(index);
setViewState(nextIndex);
}
看到这里 是不是有点儿怀疑,则呢吗可能这么少,嗯 是的。不可能这么少,只是这部分代码是关键代码,多次引用,我就把他摘出来单独放一起,然后下面的代码将会是这个动画的数据操作部分,
我先给大家说一下我的实现思路,这样对于代码会更好地理解:
首先我们把这个动画看成三行区域,上、中、下三行,小方块默认是在第二行,也就是中间行,向下移动,,当Y轴坐标超过中间行向下移动开始,默认这个小方块的状态在下行,向上移动时也是一样的道理,路过中间行开始向上移动时,说明小方块的移动状态是在上行,这是区分小方块移动位置的一个方法;
还有一个问题是 小方块是循环移动的,也就是有想做一定的时候,也有向右移动的时候,对于处理数据来说,无非就是在X坐标上的 加减,但是如何知道应该去加还是应该去减呢? 大家可以自己想想这块的逻辑,说不定有更好的方法,如果不想动脑子的话,就直接看看我的方法喽,,让我们先看单个方块的半个循环步骤中做了什么:
我在这里是使用标记法来计算它的方向的,index是当前小方块的角标。 nextIndex 是下一个小方块的脚标,向左移动时,前面的角标比后面的角标要小 ,向右移动时 前面的要比后面的脚标要大
boolean directionToRight = index < nextIndex;//判断运动方向
代码中。View的起步,也就是第一个方块,初始位置是在第一行,也就是上行,第一个动作是与第二个小方块同时向下移动,第一个小方块移动到中行时,第二个正好也会移动到下行底部,然后第二个小方块向左移动,移动到第三个小方块的下面时(X轴位置相同的地方),开始对index、nextIndex重新赋值
index = nextIndex;
nextIndex = directionToRight ? (nextIndex + 1) : (nextIndex - 1);
关于第二行代码nextIndex的赋值,大家暂时可以当作nextIndex = directionToRight ? (nextIndex + 1) : (nextIndex - 1);
nextIndex = nextIndex + 1;
因为是向右移动 所以 nextIndex+=1;
以上是半个步骤的操作思路,如果以上理解了,那么下面的代码看起来会很轻松,如果上面的不理解,可能。。。。。。
上核心代码:
//循环移动
private void moveCirclePath(Canvas canvas) {
if(mSpeedY>padding||mSpeedX>padding){
Toast.makeText(context,"移动单位 mSpeedY、mSpeedX 不可以大与每个View之间的间距padding",Toast.LENGTH_SHORT).show();
return ;
}
boolean directionToRight = index < nextIndex;//判断运动方向
//根据运行方向判断,当前角标是否在当前方向上的边界(是否第一位或者最后一位脚标)
switch (setViewState(index)) {
case UP:
Log.i(TAG, "UP");
blockBeens.get(index).setViewAddressY(blockBeens.get(index).getViewAddressY() + mSpeedY);
blockBeens.get(nextIndex).setViewAddressY(blockBeens.get(nextIndex).getViewAddressY() + mSpeedY);
for (int i = 0; i < blockBeens.size(); i++) {
moveRectf(canvas, blockBeens.get(i));
}
invalidate();
break;
case CENTER:
Log.i(TAG, "CENTER");
boolean isInIndexBoundary = directionToRight ? blockBeens.size() > nextIndex + 1 : 0 < nextIndex;
if (isInIndexBoundary) {
//根据移动方向,判断当前移动是否到达预期位置
boolean IsMoveToCriticalPoint = directionToRight ?
(blockBeens.get(nextIndex).getViewAddressX() < blockBeens.get(nextIndex + 1).getViewAddressX())
: (blockBeens.get(nextIndex).getViewAddressX() > blockBeens.get(nextIndex - 1).getViewAddressX());
if (IsMoveToCriticalPoint) {
int addressX = directionToRight ? blockBeens.get(nextIndex).getViewAddressX() + mSpeedX : blockBeens.get(nextIndex).getViewAddressX() - mSpeedX;
blockBeens.get(nextIndex).setViewAddressX(addressX);
for (int i = 0; i < blockBeens.size(); i++) {
moveRectf(canvas, blockBeens.get(i));
}
invalidate();
} else {
index = nextIndex;
nextIndex = directionToRight ? (nextIndex + 1) : (nextIndex - 1);
moveCirclePath(canvas);
}
} else {
BlockBeen indexBlockBeen = blockBeens.get(index);
blockBeens.remove(indexBlockBeen);
int insertIndex = directionToRight ? blockBeens.size() : 0;//当数据运行到集合的结尾或者头部的时候,需要按照图形位置,进行数据交换位置
blockBeens.add(insertIndex, indexBlockBeen);
index = nextIndex;
nextIndex = directionToRight ? (nextIndex -= 1) : (nextIndex += 1);
moveCirclePath(canvas);
}
break;
case BELOW:
Log.i(TAG, "BELOW");
blockBeens.get(index).setViewAddressY(blockBeens.get(index).getViewAddressY() - mSpeedY);
blockBeens.get(nextIndex).setViewAddressY(blockBeens.get(nextIndex).getViewAddressY() - mSpeedY);
for (int i = 0; i < blockBeens.size(); i++) {
moveRectf(canvas, blockBeens.get(i));
}
invalidate();
break;
default:
Toast.makeText(context, "View 位置状态错误", Toast.LENGTH_SHORT).show();
}
}
上面的代码主要是对数据的操作部分,运行状态的核心是这个。上中下三行。针对正在运行的小方块,分成上中下三部分去处理,这样的话, 一切的逻辑都将明朗。
好了,在这里我把完整的代码粘贴出来,里面包括上一个动画的代码:
package com.jjf.blockmoveforcirclepath.customview;
import android.app.Activity;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.RectF;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Display;
import android.view.View;
import android.widget.Toast;
import com.jjf.blockmoveforcirclepath.BlockBeen;
import com.jjf.blockmoveforcirclepath.R;
import java.util.ArrayList;
/**
* @author: jjf
* @date: 2019/5/10
* @describe: 移动动画
*/
public class MoveBlockView extends View {
//每个方块间距
private int padding = 20;
//初始View位置
private int viewAddressX = 100;
private int viewAddressY = 200;
//View 大小
private int viewWide = 100;
private int viewHeight = 100;
private int count = 8;//view数量(小方块的数量)
//view仓库
private ArrayList blockBeens = new ArrayList<>();
public static final int MAX_SIZE = 140;
private int mCoordX = 0;
private int mCoordY = 0;
private int mRealSize = 140;
//移动间距必须小于padding
private final int mSpeedX = 5;// View每次位移的距离单位(值越大 速度越快)
private int mSpeedY = 5;
private Context context;
//三种状态
private final int UP = 0;
private final int CENTER = 1;
private final int BELOW = 2;
/**
* moveto 动画
*/
private boolean goRight = true;
private boolean goDown = true;
int parentHeight = 0;
int parentWight = 0;
public MoveBlockView(Context context) {
super(context);
this.context = context;
}
public MoveBlockView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
this.context = context;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
parentHeight = bottom - top;
parentWight = right - left;
}
boolean bo = true;
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//初始化
if (bo) {
for (int i = 0; i < count; i++) {
drawBlock(canvas, i);
//单词初始化核心代码
if (i == 0) {
blockBeens.get(i).setAddressState(0);
} else {
blockBeens.get(i).setAddressState(1);
}
}
bo = false;
}
moveCirclePath(canvas);
// moveTo(0, 5, 5, canvas);
}
//设置View状态
public int setViewState(int index) {
if (blockBeens.get(index).getViewAddressY() == viewAddressY) {
blockBeens.get(index).setAddressState(CENTER);
return CENTER;
} else if (blockBeens.get(index).getViewAddressY() <= viewAddressY + blockBeens.get(index).getViewHeight() + padding
&& blockBeens.get(index).getViewAddressY() > viewAddressY) {
blockBeens.get(index).setAddressState(BELOW);
return BELOW;
} else if (blockBeens.get(index).getViewAddressY() < viewAddressY
&& blockBeens.get(index).getViewAddressY() >= viewAddressY - padding - blockBeens.get(index).getViewHeight()) {
blockBeens.get(index).setAddressState(UP);
return UP;
} else {
return -1;
}
}
String TAG = "MoveBlockView";
/**
* 移动小方块
* @param position 第几个View移动
* @param goX 在X轴上移动的幅度
* @param goY 在Y轴上移动的幅度
* @param canvas 画布
*/
private void moveTo(int position, int goX, int goY, Canvas canvas) {
// check the borders, and set the direction if a border has reached
if (blockBeens.get(position).getViewAddressX() > (parentWight - MAX_SIZE)) {
goRight = false;
}
if (blockBeens.get(position).getViewAddressX() <= 0) {
goRight = true;
}
if (blockBeens.get(position).getViewAddressY() > (parentHeight - MAX_SIZE)) {
goDown = false;
}
if (blockBeens.get(position).getViewAddressY() <= 0) {
goDown = true;
}
// move the x and y
if (goRight) {
blockBeens.get(position).setViewAddressX(blockBeens.get(position).getViewAddressX() + goX);
} else {
blockBeens.get(position).setViewAddressX(blockBeens.get(position).getViewAddressX() - goX);
}
if (goDown) {
blockBeens.get(position).setViewAddressY(blockBeens.get(position).getViewAddressY() + goY);
} else {
blockBeens.get(position).setViewAddressY(blockBeens.get(position).getViewAddressY() - goY);
}
blockBeens.get(position).getRectF().set(blockBeens.get(position).getViewAddressX()
, blockBeens.get(position).getViewAddressY(), blockBeens.get(position).getViewAddressX() + blockBeens.get(position).getViewWide()
, blockBeens.get(position).getViewAddressY() + blockBeens.get(position).getViewHeight());
canvas.drawRoundRect(blockBeens.get(position).getRectF(), 30, 30, blockBeens.get(position).getViewPain());
invalidate();
}
int index = 0;//当前角标
int nextIndex = 1;//下一个View角标
public void moveRectf(Canvas canvas, BlockBeen blockBeen) {
blockBeen.getRectF().set(blockBeen.getViewAddressX()
, blockBeen.getViewAddressY(), blockBeen.getViewAddressX() + blockBeen.getViewWide()
, blockBeen.getViewHeight() + blockBeen.getViewAddressY() + mSpeedX);
//根据提供的矩形为四个角画弧线,(其中的数字:第一个表示X轴方向大小,第二个Y轴方向大小。可以改成其他的,你可以自己体验),最后添加画笔。
canvas.drawRoundRect(blockBeen.getRectF(), 30, 30, blockBeen.getViewPain());
setViewState(index);
setViewState(nextIndex);
}
//循环移动
private void moveCirclePath(Canvas canvas) {
if(mSpeedY>padding||mSpeedX>padding){
Toast.makeText(context,"移动单位 mSpeedY、mSpeedX 不可以大与每个View之间的间距padding",Toast.LENGTH_SHORT).show();
return ;
}
boolean directionToRight = index < nextIndex;//判断运动方向
switch (setViewState(index)) {
case UP:
Log.i(TAG, "UP");
blockBeens.get(index).setViewAddressY(blockBeens.get(index).getViewAddressY() + mSpeedY);
blockBeens.get(nextIndex).setViewAddressY(blockBeens.get(nextIndex).getViewAddressY() + mSpeedY);
for (int i = 0; i < blockBeens.size(); i++) {
moveRectf(canvas, blockBeens.get(i));
}
invalidate();
break;
case CENTER:
Log.i(TAG, "CENTER");
//根据运行方向判断,当前角标是否在当前方向上的边界(是否第一位或者最后一位脚标)
boolean isInIndexBoundary = directionToRight ? blockBeens.size() > nextIndex + 1 : 0 < nextIndex;
if (isInIndexBoundary) {
//根据移动方向,判断当前移动是否到达预期位置
boolean IsMoveToCriticalPoint = directionToRight ?
(blockBeens.get(nextIndex).getViewAddressX() < blockBeens.get(nextIndex + 1).getViewAddressX())
: (blockBeens.get(nextIndex).getViewAddressX() > blockBeens.get(nextIndex - 1).getViewAddressX());
if (IsMoveToCriticalPoint) {
int addressX = directionToRight ? blockBeens.get(nextIndex).getViewAddressX() + mSpeedX : blockBeens.get(nextIndex).getViewAddressX() - mSpeedX;
blockBeens.get(nextIndex).setViewAddressX(addressX);
for (int i = 0; i < blockBeens.size(); i++) {
moveRectf(canvas, blockBeens.get(i));
}
invalidate();
} else {
index = nextIndex;
nextIndex = directionToRight ? (nextIndex + 1) : (nextIndex - 1);
moveCirclePath(canvas);
}
} else {
BlockBeen indexBlockBeen = blockBeens.get(index);
blockBeens.remove(indexBlockBeen);
int insertIndex = directionToRight ? blockBeens.size() : 0;//当数据运行到集合的结尾或者头部的时候,需要按照图形位置,进行数据交换位置
blockBeens.add(insertIndex, indexBlockBeen);
index = nextIndex;
nextIndex = directionToRight ? (nextIndex -= 1) : (nextIndex += 1);
moveCirclePath(canvas);
}
break;
case BELOW:
Log.i(TAG, "BELOW");
blockBeens.get(index).setViewAddressY(blockBeens.get(index).getViewAddressY() - mSpeedY);
blockBeens.get(nextIndex).setViewAddressY(blockBeens.get(nextIndex).getViewAddressY() - mSpeedY);
for (int i = 0; i < blockBeens.size(); i++) {
moveRectf(canvas, blockBeens.get(i));
}
invalidate();
break;
default:
Toast.makeText(context, "View 位置状态错误", Toast.LENGTH_SHORT).show();
}
}
/**
* 画View
*
* @param canvas
* @param position 第几个view
*/
public void drawBlock(Canvas canvas, int position) {//viewAddressY+ position * (viewWide+padding)+viewHeight
BlockBeen blockBeen;
if (position >= blockBeens.size()) {
blockBeen = new BlockBeen();
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);//创建画笔
paint.setColor(getResources().getColor(R.color.colorPrimaryDark));//添加画笔颜色
blockBeen.setViewPain(paint);
} else {
blockBeen = blockBeens.get(position);
}
if (position == 0) {
blockBeen.setViewAddressX(viewAddressX);
blockBeen.setViewAddressY(viewAddressY - padding - blockBeen.getViewHeight());
} else {
blockBeen.setViewAddressX(viewAddressX + (position - 1) * (viewWide + padding));
blockBeen.setViewAddressY(viewAddressY);
}
blockBeen.setViewWide(viewWide);
blockBeen.setViewHeight(viewHeight);
RectF rectF;
if (blockBeen.getRectF() == null) {
rectF = new RectF(blockBeen.getViewAddressX(), blockBeen.getViewAddressY(),
blockBeen.getViewAddressX() + blockBeen.getViewWide(),
blockBeen.getViewAddressY() + blockBeen.getViewHeight());//先画一个矩形
blockBeen.setRectF(rectF);
} else {
rectF = blockBeen.getRectF();
rectF.set(blockBeens.get(position).getViewAddressX()
, blockBeens.get(position).getViewAddressY(), blockBeens.get(position).getViewAddressX() + blockBeens.get(position).getViewWide()
, blockBeens.get(position).getViewAddressY() + blockBeens.get(position).getViewHeight());
}
if (!blockBeens.contains(blockBeen)) {
blockBeens.add(blockBeen);
}
canvas.drawRoundRect(rectF, 30, 30, blockBeen.getViewPain());//根据提供的矩形为四个角画弧线,(其中的数字:第一个表示X轴方向大小,第二个Y轴方向大小。可以改成其他的,你可以自己体验),最后添加画笔。
//绘制View
invalidate();
}
}
关于在xml中的引用,因为只是针对这个动画的逻辑代码部分做一个尝试,所以关于自定义属性,等等没有去解决,这里只是提供一个思路供大家参考,有更好的点子,希望大家能积极评论,谢谢!