Android 自定義動畫 單個View平面位移以及一組View輪迴旋轉(二)

Android 自定義動畫 單個View平面位移以及一組View輪迴旋轉(一)

 

這一篇文章主要講到的是那個循環動畫,好了先把動畫的樣子奉上,請各位大佬輕噴:

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中的引用,因爲只是針對這個動畫的邏輯代碼部分做一個嘗試,所以關於自定義屬性,等等沒有去解決,這裏只是提供一個思路供大家參考,有更好的點子,希望大家能積極評論,謝謝!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章