Android 自定義動畫 單個View平面位移以及一組View輪迴旋轉(一)
這一篇文章主要講到的是那個循環動畫,好了先把動畫的樣子奉上,請各位大佬輕噴:
關於這個動畫,與遇上一個動畫的聯繫就是,使用同樣的方法去繪製的小方塊,爲了避免大家翻看過於麻煩,在這裏再給大家展示出來,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<BlockBeen> 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中的引用,因爲只是針對這個動畫的邏輯代碼部分做一個嘗試,所以關於自定義屬性,等等沒有去解決,這裏只是提供一個思路供大家參考,有更好的點子,希望大家能積極評論,謝謝!