9

看得见的数据结构Android版之表的数组实现(视图篇)

 2 years ago
source link: http://www.androidchina.net/9393.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client
看得见的数据结构Android版之表的数组实现(视图篇) – Android开发中文站
你的位置:Android开发中文站 > Android开发 > 开发进阶 > 看得见的数据结构Android版之表的数组实现(视图篇)

零、前言:

1.本文的姊妹篇:看得见的数据结构Android版之表的数组实现(数据结构篇)
2.希望你可以和我在Github一同见证:DS4Android的诞生与成长,欢迎star
3.激动人心的时刻到了,要画图了,铅笔、草稿纸、饮料、花生米准备好,现在开始了

先留图镇楼:

表结构的常规操作

表结构的常规操作.gif

数组的扩容与缩容

数组的扩容与缩容


一、先绘制操作界面:

1.自定义View:ArrayView

准备一个主画笔和主路径并确定一些常量
然后用analyze包绘制封装好的网格和坐标系以便查看

/**
 * 作者:张风捷特烈<br/>
 * 时间:2018/11/21 0021:8:01<br/>
 * 邮箱:[email protected]<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/>
 * 邮箱:[email protected]<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/>
 * 邮箱:[email protected]<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/>
 * 邮箱:[email protected]<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

基本上就是这样,思路什么的都理清了,细节方面可参看源码


本系列后续更新链接合集:(动态更新)

看得见的数据结构Android版之开篇前言
看得见的数据结构Android版之表的数组实现(数据结构篇)
看得见的数据结构Android版之表的数组实现(视图篇)
看得见的数据结构Android版之单链表篇(待完成)
看得见的数据结构Android版之双链表篇(待完成)
看得见的数据结构Android版之栈(待完成)
看得见的数据结构Android版之队列(待完成)
看得见的数据结构Android版之二叉树篇(待完成)
看得见的数据结构Android版之二分搜索树篇(待完成)
看得见的数据结构Android版之AVL树篇(待完成)
看得见的数据结构Android版之红黑树篇(待完成)
更多数据结构—以后再说吧


后记:捷文规范

1.本文成长记录及勘误表
项目源码 日期 备注
2.更多关于我
笔名 QQ 微信 爱好 张风捷特烈 1981462002 zdl1994328 语言 我的github 我的简书 我的掘金 个人网站

1—-本文由张风捷特烈原创,转载请注明
2—-欢迎广大编程爱好者共同交流
3—-个人能力有限,如有不正之处欢迎大家批评指证,必定虚心改正
4—-看到这里,我在此感谢你的喜欢与支持


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK