看得見的資料結構Android版之陣列表(檢視篇)

張風捷特烈發表於2018-11-23

零、前言:

1.本文的姊妹篇:看得見的資料結構Android版之表的陣列實現(資料結構篇)
2.希望你可以和我在Github一同見證:DS4Android的誕生與成長,歡迎star
3.激動人心的時刻到了,要畫圖了,鉛筆、草稿紙、飲料、花生米準備好,現在開始了

先留圖鎮樓:

表結構的常規操作

表結構的常規操作.gif

陣列的擴容與縮容

陣列的擴容與縮容


一、先繪製操作介面:

1.自定義View:ArrayView

準備一個主畫筆和主路徑並確定一些常量
然後用analyze包繪製封裝好的網格和座標系以便檢視

/**
 * 作者:張風捷特烈<br/>
 * 時間:2018/11/21 0021:8:01<br/>
 * 郵箱:1981462002@qq.com<br/>
 * 說明:陣列實現表結構---測試檢視
 */
public class ArrayView<E> extends View {
    private Point mCoo = new Point(200, 150);//座標系
    private Picture mCooPicture;//座標系canvas元件
    private Picture mGridPicture;//網格canvas元件

    private Path mPath;//主路徑
    private Paint mPaint;//主畫筆

    private static final int OFFSET_X = 10;//X空隙
    private static final int OFFSET_Y = 60;//Y空隙
    private static final int OFFSET_OF_TXT_Y = 10;//文字的偏移
    private static final int BOX_RADIUS = 10;//陣列盒子的圓角

    public ArrayView(Context context) {
        this(context, null);
    }

    public ArrayView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init();//初始化
    }

    private void init() {
        //初始化主畫筆
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaint.setColor(Color.BLUE);
        mPaint.setStrokeWidth(5);
        mPaint.setTextAlign(Paint.Align.CENTER);
        mPaint.setTextSize(50);
        //初始化主路徑
        mPath = new Path();
        //輔助線
         mCooPicture = HelpDraw.getCoo(getContext(), mCoo,false);//座標系:無文字
        mGridPicture = HelpDraw.getGrid(getContext());//網格
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        HelpDraw.draw(canvas, mGridPicture);
        canvas.save();
        canvas.translate(mCoo.x, mCoo.y);//畫布移到座標原點
        //TODO draw
        canvas.restore();
        HelpDraw.draw(canvas, mCooPicture);
    }
}
複製程式碼

座標系.png


2.把後面要用的畫筆準備一下:
private void init() {
    //初始化主畫筆
    mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    mPaint.setColor(Color.BLUE);
    mPaint.setStrokeWidth(5);
    mPaint.setTextAlign(Paint.Align.CENTER);
    mPaint.setTextSize(50);
    //初始化主路徑
    mPath = new Path();
    //初始化文字畫筆
    mTxtPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    mTxtPaint.setColor(Color.WHITE);
    mTxtPaint.setTextAlign(Paint.Align.CENTER);
    mTxtPaint.setTextSize(40);
    //初始化路徑畫筆
    mPathPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    mPathPaint.setColor(Color.GRAY);
    mPathPaint.setStyle(Paint.Style.STROKE);
    mCooPicture = HelpDraw.getCoo(getContext(), mCoo,false);
    mGridPicture = HelpDraw.getGrid(getContext());
    //初始化圓球按鈕畫筆
    mCtrlPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    mCtrlPaint.setColor(Color.RED);
    mCtrlPaint.setTextAlign(Paint.Align.CENTER);
    mCtrlPaint.setTextSize(30);
}
複製程式碼
3.準備小球的資訊

你也可以將這些資訊封裝成一個bean,這裡為了bean太多容易混淆,用幾個陣列記錄資訊

    private static final Point[] CTRL_POS = new Point[]{//控制按鈕的點位
            new Point(-100, 100),//新增
            new Point(-100, 300),//更新
            new Point(-100, 500),//檢視
            new Point(-100, 700),//刪除

            new Point(700, -70),//定點新增
            new Point(700 + 300, -70),//定值查詢
            new Point(700 + 300 * 2, -70),//定點刪除
            new Point(700 + 300 * 3, -70),//清除

    };

    private static int[] CTRL_COLOR = new int[]{//控制按鈕的顏色
            0xff1EF519,//新增
            0xff2992F2,//更新
            0xffB946F4,//新增
            0xffF50C0C,//刪除

            0xff1EF519,//定點新增
            0xffB946F4,//定值查詢
            0xffF50C0C,//定點刪除
            0xffF46410,//清除
    };

    private static final String[] CTRL_TXT = new String[]{//控制按鈕的文字
            "新增",//新增
            "更新",//更新
            "查尋",//新增
            "刪除",//刪除

            "定點+",//定點新增
            "值查",//定值查詢
            "定點-",//定點刪除
            "清空",//清除按鍵
    };

    private static final int CTRL_RADIUS = 50;//控制按鈕的半徑
複製程式碼

3.繪製小圓球:

人狠話不多----一個迴圈,一波帶走

/**
 * 控制皮膚--圓球
 *
 * @param canvas 畫布
 */
private void ctrlView(Canvas canvas) {
    for (int i = 0; i < CTRL_POS.length; i++) {
        mCtrlPaint.setColor(CTRL_COLOR[i]);
        canvas.drawCircle(CTRL_POS[i].x, CTRL_POS[i].y, CTRL_RADIUS, mCtrlPaint);
        canvas.drawText(CTRL_TXT[i], CTRL_POS[i].x, CTRL_POS[i].y + OFFSET_OF_TXT_Y, mTxtPaint);
    }
}
複製程式碼

繪製控制按鈕.png

這就...畫完了?對...這就畫完了


三、小球的點選事件(區域判斷)

1.有請吾王麾下十二戰神之一:審斷之神(JudgeMan.java)
/**
 * 審斷之神能力一:第一形態:區域限定----判斷出是否在某點的半徑為r圓範圍內
 *
 * @param srcX 目標點X
 * @param srcY 目標點Y
 * @param dstX 主動點X
 * @param dstY 主動點Y
 * @param r    半徑區域
 * @return 是否在區域內
 */
public static boolean judgeCircleArea(float srcX, float srcY, float dstX, float dstY, float r) {
    return disPos2d(srcX, srcY, dstX, dstY) <= r;
}

/**
 * 審斷之神被動能力一:兩點間距離函式
 * @param srcX 目標點X
 * @param srcY 目標點Y
 * @param dstX 主動點X
 * @param dstY 主動點Y
 * @return 兩點間距離函式
 */
private static float disPos2d(float srcX, float srcY, float dstX, float dstY) {
    return (float) Math.sqrt((srcX - dstX) * (srcX - dstX) + (srcY - dstY) * (srcY - dstY));
}
複製程式碼

判斷dst點是否在src周圍r的圓內.png


2.先寫一下回撥監聽介面:OnCtrlClickListener

兵馬未動,糧草先行,有介面好辦事

/**
 * 作者:張風捷特烈<br/>
 * 時間:2018/11/21 0021:10:17<br/>
 * 郵箱:1981462002@qq.com<br/>
 * 說明:控操作介面
 */
public interface OnCtrlClickListener<T> {
    /**
     * 新增時回撥
     * @param view
     */
    void onAdd(T view);
    /**
     * 定點新增時回撥
     * @param view
     */
    void onAddByIndex(T view);
    /**
     * 移除時回撥
     * @param view
     */
    void onRemove(T view);
    /**
     * 定點移除時回撥
     * @param view
     */
    void onRemoveByIndex(T view);
    /**
     * 設定時回撥
     * @param view
     */
    void onSet(T view);
    /**
     * 查詢時回撥
     * @param view
     */
    void onFind(T view);
    /**
     * 定值查詢時回撥
     * @param view
     */
    void onFindByData(T view);
    /**
     * 清空時回撥
     * @param view
     */
    void onClear(T view);
}

複製程式碼
3.觸控事件獲取主動點:

還是一個for迴圈一波帶走----(mark:一開始我也是一個一個if,然後發現可以優化,才變得優雅的)
也許無法一步想到位,但可以先實現,然後再優化,我就喜歡這種一波帶走的感覺
注意點:downX和downY要相對於canvas的座標系,所以要偏移一下

private OnCtrlClickListener<ArrayView<E>> mOnCtrlClickListener;//監聽器
public void setOnCtrlClickListener(OnCtrlClickListener<ArrayView<E>> onCtrlClickListener) {
    mOnCtrlClickListener = onCtrlClickListener;
}

@Override
public boolean onTouchEvent(MotionEvent event) {
    switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            float downX = event.getX() - mCoo.x;
            float downY = event.getY() - mCoo.y;
            for (int i = 0; i < CTRL_POS.length; i++) {
                if (JudgeMan.judgeCircleArea( //區域判定
                        CTRL_POS[i].x, CTRL_POS[i].y,
                        downX, downY, CTRL_RADIUS * 1.2f)) {
                    if (mOnCtrlClickListener != null) {
                        switch (i) {
                            case 0://插入尾部
                                mOnCtrlClickListener.onAdd(this);
                                break;
                            case 1://更新
                                mOnCtrlClickListener.onSet(this);
                                  contactTest();
                                break;
                            case 2://查詢
                                mOnCtrlClickListener.onFind(this);
                                break;
                            case 3://刪除尾部
                                selectIndex = mArrayBoxes.size() - 1;
                                mAnimator.start();
                                break;
                            case 4://定點新增尾部
                                mOnCtrlClickListener.onAddByIndex(this);
                                break;
                            case 5://定值查詢
                                mOnCtrlClickListener.onFindByData(this);
                                break;
                            case 6://定點移除
                                mAnimator.start();
                                break;
                            case 7://清空
                                mOnCtrlClickListener.onClear(this);
                                break;
                        }
                        CTRL_COLOR[i] = 0xff54E1F8;//點選更換顏色
                    }
                }
            }
            updayeSelectIndex(downX, downY);//更新selectIndex
            break;
        case MotionEvent.ACTION_UP://還原顏色
            CTRL_COLOR[0] = 0xff1EF519;
            CTRL_COLOR[1] = 0xff2992F2;
            CTRL_COLOR[2] = 0xffB946F4;
            CTRL_COLOR[3] = 0xffF50C0C;
            CTRL_COLOR[4] = 0xff1EF519;
            CTRL_COLOR[5] = 0xffB946F4;
            CTRL_COLOR[6] = 0xffF50C0C;
            CTRL_COLOR[7] = 0xffF46410;
            break;
    }
    invalidate();//記得重繪
    return true;
}
複製程式碼
4.Activity使用介面:
public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ArrayView<String> view = new ArrayView<>(this);
        view.setOnCtrlClickListener(new OnCtrlClickListener<ArrayView<String>>() {
            @Override
            public void onAdd(ArrayView<String> view) {
                Toast.makeText(MainActivity.this, "onAdd", Toast.LENGTH_SHORT).show();
            }
            @Override
            public void onAddByIndex(ArrayView<String> view) {
                Toast.makeText(MainActivity.this, "onAddByIndex", Toast.LENGTH_SHORT).show();
            }

            @Override
            public void onRemove(ArrayView<String> view) {
                Toast.makeText(MainActivity.this, "onRemove", Toast.LENGTH_SHORT).show();
            }
            @Override
            public void onRemoveByIndex(ArrayView<String> view) {
                Toast.makeText(MainActivity.this, "onRemoveByIndex", Toast.LENGTH_SHORT).show();
            }
            @Override
            public void onSet(ArrayView<String> view) {
                Toast.makeText(MainActivity.this, "onSet", Toast.LENGTH_SHORT).show();
            }
            @Override
            public void onFind(ArrayView<String> view) {
                Toast.makeText(MainActivity.this, "onFind", Toast.LENGTH_SHORT).show();
            }
            @Override
            public void onFindByData(ArrayView<String> view) {
                Toast.makeText(MainActivity.this, "onFindByData", Toast.LENGTH_SHORT).show();
            }
            @Override
            public void onClear(ArrayView<String> view) {
                Toast.makeText(MainActivity.this, "onFindByData", Toast.LENGTH_SHORT).show();
            }
        });
        setContentView(view);
    }
}
複製程式碼

OK,完美執行,核心在審斷之神的judgeCircleArea

檢視按鈕執行狀況.gif


三、正文來了,繪製資料結構:

1.可繪製物件單體:使用資料結構單體的父類

擁有座標、顏色、速度三種核心屬性

/**
 * 作者:張風捷特烈<br/>
 * 時間:2018/11/21 0021:8:50<br/>
 * 郵箱:1981462002@qq.com<br/>
 * 說明:可顯示出來的基本條件
 */
public class Viewable {
    public float x;//單體的x座標
    public float y;//單體的y座標
    public int color = 0xff43A3FA;//單體的顏色

    public float vX;//單體的水平速度
    public float vY;//單體的數值速度

    public Viewable() {
    }

    public Viewable(float x, float y) {
        this.x = x;
        this.y = y;
    }
}

複製程式碼
2.陣列表結構單體的繪圖承載物件:

陣列盒子:擁有索引和資料兩個額外屬性

/**
 * 作者:張風捷特烈<br/>
 * 時間:2018/11/21 0021:8:46<br/>
 * 郵箱:1981462002@qq.com<br/>
 * 說明:模型層陣列的單體
 */
public class ArrayBox<T> extends Viewable {

    public int index;//陣列單體索引
    public T data;//資料結構承載的核心資料

    @Override
    public boolean equals(Object obj) {
        return ((ArrayBox) obj).data == data;
    }

    public ArrayBox(T data) {
        this.data = data;
    }

    public ArrayBox(float x, float y) {
        super(x, y);
    }
}
複製程式碼
3.繪製陣列的長度個空白矩形
1)如何根據索引確定點位

就是稍微畫個圖,看看有什麼關係,找到通式就ok了(程式碼巧多了,寫字就是醜...)
然後我們用上篇的陣列表結構來進行測試

位置計算.jpg

2)繪製陣列的長度個空白矩形

根據上面的分析圖,確定了第x列,第y行的矩形座標,應該就不難畫了
注意:繪製陣列的長度個空白矩形,陣列的長度!! 陣列的長度個!!,不是集合大小
本來應該把陣列完全封裝在陣列表結構中的,這裡為了演示擴容和縮容,陣列長度還是必要的

//建立一個上篇定義的陣列表類,泛型當然是要畫的陣列盒子了
private IChart<ArrayBox<E>> mArrayBoxes = new ArrayChart<>();

/**
 * 繪製陣列的長度個空白矩形
 *
 * @param canvas
 */
private void helpView(Canvas canvas) {
    for (int i = 0; i < mArrayBoxes.capacity(); i++) {
        int y = i / 8;//行座標
        int x = i % 8;//列座標
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setColor(0xff821AFA);
        canvas.drawRoundRect(
                (Cons.BOX_WIDTH + OFFSET_X) * x, (Cons.BOX_HEIGHT + OFFSET_Y) * y,
                (Cons.BOX_WIDTH + OFFSET_X) * x + Cons.BOX_WIDTH, 
                (Cons.BOX_HEIGHT + OFFSET_Y) * y + Cons.BOX_HEIGHT,
                BOX_RADIUS, BOX_RADIUS, mPaint);
        mTxtPaint.setColor(0xff821AFA);
        canvas.drawText(i + "",
                (Cons.BOX_WIDTH + OFFSET_X) * x + Cons.BOX_WIDTH / 2,
                (Cons.BOX_HEIGHT + OFFSET_Y) * y + 3 * OFFSET_OF_TXT_Y, mTxtPaint);
    }

}
複製程式碼

陣列初始長度為10,沒錯

陣列體現.png


4.繪製表結構

根據mArrayBoxes的大小,繪製出mArrayBoxes裡面的所有元素。
繪製單體在前言篇已經給出,這裡只是加上了偏移量,應該不難理解。

/**
 * 繪製表結構
 * @param canvas
 */
private void dataView(Canvas canvas) {
    mPaint.setColor(Color.BLUE);
    mPaint.setStyle(Paint.Style.FILL);
    mPath.reset();
    for (int i = 0; i < mArrayBoxes.size(); i++) {
        ArrayBox box = mArrayBoxes.get(i);
        mPaint.setColor(box.color);
        canvas.drawRoundRect(
                box.x, box.y, box.x + Cons.BOX_WIDTH, box.y + Cons.BOX_HEIGHT,
                BOX_RADIUS, BOX_RADIUS, mPaint);
        mPath.moveTo(box.x, box.y);
        mPath.rCubicTo(Cons.BOX_WIDTH / 2, Cons.BOX_HEIGHT / 2,
                Cons.BOX_WIDTH / 2, Cons.BOX_HEIGHT / 2, Cons.BOX_WIDTH, 0);
        canvas.drawPath(mPath, mPathPaint);
        canvas.drawText(box.data + "",
                box.x + Cons.BOX_WIDTH / 2,
                box.y + Cons.BOX_HEIGHT / 2 + 3 * OFFSET_OF_TXT_Y, mTxtPaint);
    }
}
複製程式碼
5.更新繪製單體的點位

每次增刪操作後更新一下點位

/**
 * 更新繪製單體的點位
 */
private void updatePosOfData() {
    for (int i = 0; i < mArrayBoxes.size(); i++) {
        int y = i / 8;//行座標
        int x = i % 8;//列座標
        ArrayBox box = mArrayBoxes.get(i);
        box.x = (Cons.BOX_WIDTH + OFFSET_X) * x;
        box.y = (Cons.BOX_HEIGHT + OFFSET_Y) * y;
        box.vY = 100;
        box.vX = 100;
    }
}
複製程式碼
6.點選時動態更新選中值:
/**
 * 點選時動態更新選中值
 * @param downX 按下點X
 * @param downY 按下點Y
 */
private void updateSelectIndex(float downX, float downY) {
    float x = downX / (Cons.BOX_WIDTH + OFFSET_X) - 0.5f;
    float y = downY / (Cons.BOX_HEIGHT + OFFSET_Y) - 0.5f;
    if (x > -0.5 && y > -0.5) {
        int indexOfData = Math.round(y) * 8 + Math.round(x);//逆向求取點中的資料索引
        if (indexOfData < mArrayBoxes.size()) {
            mArrayBoxes.get(indexOfData).color = ColUtils.randomRGB();
            selectIndex = indexOfData;
        }
    }
}
複製程式碼

四、操作按鈕繪製:

注意:以下操作是在Activity中的點選回撥中進行,呼叫了view層的方法,實現操作與檢視分離

1.增加操作:
/**
 * 檢視的資料操作介面方法--新增
 *
 * @param data 資料
 */
public void addData(E data) {
    ArrayBox<E> arrayBox = new ArrayBox<>(0, 0);
    arrayBox.data = data;
    mArrayBoxes.add(arrayBox);
    updatePosOfData();//更新一下點位
}
複製程式碼

新增操作.gif

 * 檢視的資料操作介面方法--根據索引新增
 *
 * @param index
 * @param data
 */
public void addDataById(int index, E data) {
    if (mArrayBoxes.size() > 0 && index < mArrayBoxes.size() && index >= 0) {
        ArrayBox<E> arrayBox = new ArrayBox<>(0, 0);
        arrayBox.data = data;
        mArrayBoxes.add(index, arrayBox);
        updatePosOfData();
    }
}
複製程式碼

定點新增.gif


2.查詢和更新操作
/**
 * 檢視的資料操作介面方法--根據id查詢操作
 * @param index
 * @return
 */
public E findData(int index) {
    if (mArrayBoxes.size() > 0 && index < mArrayBoxes.size() && index >= 0) {
        return mArrayBoxes.get(index).data;
    }
    return null;
}

/**
 * 更新資料
 * @param index 索引
 * @param data 資料
 */
public void setData(int index, E data) {
    if (mArrayBoxes.size() > 0 && index < mArrayBoxes.size() && index >= 0) {
        mArrayBoxes.get(index).data = data;
    }
}

複製程式碼

set和定索引查詢.gif

/**
 * 檢視的資料操作介面方法--根據資料查詢操作
 * @param data
 * @return
 */
public int[] findData(E data) {
    ArrayBox<E> arrayBox = new ArrayBox<>(0, 0);
    arrayBox.data = data;
    return mArrayBoxes.getIndex(arrayBox);
}
複製程式碼

定值查詢獲取索引.gif


3.刪除操作:
1)刪除的核心方法:
/**
 * 檢視的資料操作介面方法--移除末尾
 */
public void removeData() {
    if (mArrayBoxes.size() > 0) {
        mArrayBoxes.remove();
        updatePosOfData();
    }
}
/**
 * 檢視的資料操作介面方法--定索引刪除操作
 *
 * @param index 索引
 */
public void removeData(int index) {
    if (mArrayBoxes.size() > 0 && index < mArrayBoxes.size() && index >= 0) {
        //更新後面的索引
        for (int i = index; i < mArrayBoxes.size(); i++) {
            mArrayBoxes.get(i).index -= 1;
        }
        mArrayBoxes.remove(index);
        selectIndex = -1;
        updatePosOfData();
    }
}
複製程式碼
2)刪除有個移除動畫,ValueAnimate來幫忙
//初始化時間流ValueAnimator
mAnimator = ValueAnimator.ofFloat(0, 1);
mAnimator.setRepeatCount(-1);
mAnimator.setDuration(2000);
mAnimator.setRepeatMode(ValueAnimator.REVERSE);
mAnimator.setInterpolator(new LinearInterpolator());
mAnimator.addUpdateListener(animation -> {
    updateBall();//更新小球位置
    invalidate();
});
複製程式碼
3)更新選中單體資訊:updateBall
/**
 * 更新小球
 */
private void updateBall() {
    if (mArrayBoxes.size() <= 0 && selectIndex < 0) {
        return;
    }
    ArrayBox ball = mArrayBoxes.get(selectIndex);
    ball.x += ball.vX;
    ball.y += ball.vY;
    if (ball.y > 600) {//大於600就執行移除
        if (mOnCtrlClickListener != null) {
            mOnCtrlClickListener.onRemoveByIndex(this);//移除監聽放在這裡了!!
            mAnimator.pause();//暫停ValueAnimator
        }
    }
}
複製程式碼
//點選的監聽中修改:
 case 3://刪除尾部---這裡將移除的選中物件設為最後一個
     selectIndex = mArrayBoxes.size() - 1;
     mAnimator.start();//開啟Animator
複製程式碼

刪除和定點刪除.gif

4.聯合測試:
private void contactTest() {
    IChart<ArrayBox<E>> contactArr = new ArrayChart<>();
    contactArr.add(new ArrayBox<E>((E) "toly1"));
    contactArr.add(new ArrayBox<E>((E) "toly2"));
    contactArr.add(new ArrayBox<E>((E) "toly3"));
    contactData(selectIndex, contactArr);
}

public void contactData(int index, IChart<ArrayBox<E>> chart) {
    mArrayBoxes.contact(index, chart);
    updatePosOfData();
}
複製程式碼

表的聯合.png

基本上就是這樣,思路什麼的都理清了,細節方面可參看原始碼


本系列後續更新連結合集:(動態更新)


後記:捷文規範

1.本文成長記錄及勘誤表
專案原始碼 日期 備註
V0.1--github 2018-11-23 看得見的資料結構Android版之表的陣列實現(檢視篇)
2.更多關於我
筆名 QQ 微信 愛好
張風捷特烈 1981462002 zdl1994328 語言
我的github 我的簡書 我的掘金 個人網站
3.宣告

1----本文由張風捷特烈原創,轉載請註明
2----歡迎廣大程式設計愛好者共同交流
3----個人能力有限,如有不正之處歡迎大家批評指證,必定虛心改正
4----看到這裡,我在此感謝你的喜歡與支援


icon_wx_200.png

相關文章