2024年Android最全Android 不规则封闭区域填充 手指秒变油漆桶,Android大厂高频面试题解析

总结

算法知识点繁多,企业考察的题目千变万化,面对越来越近的“金九银十”,我给大家准备好了一套比较完善的学习方法,希望能帮助大家在有限的时间里尽可能系统快速的恶补算法,通过高效的学习来提高大家面试中算法模块的通过率。

这一套学习资料既有文字档也有视频,里面不仅仅有关键知识点的整理,还有案例的算法相关部分的讲解,可以帮助大家更好更全面的进行学习,二者搭配起来学习效果会更好。

部分资料展示:




有了这套学习资料,坚持刷题一周,你就会发现自己的算法知识体系有明显的完善,离大厂Offer的距离更加近。

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

fillColor01(pixels, w, h, pixel, newColor, i + 1, j);

//下

fillColor01(pixels, w, h, pixel, newColor, i, j + 1);

//左

fillColor01(pixels, w, h, pixel, newColor, i - 1, j);

}

代码很简单,但是如果你去运行,会发生StackOverflowException异常,这个异常主要是因为大量的递归造成的。虽然简单,但是在移动设备上使用该方法不行。

于是,我就想,这个方法不是递归深度过多么,那么我可以使用一个Stack去存像素点,减少递归的深度和次数,于是我把代码改成如下的方式:

/**

  • @param pixels 像素数组

  • @param w 宽度

  • @param h 高度

  • @param pixel 当前点的颜色

  • @param newColor 填充色

  • @param i 横坐标

  • @param j 纵坐标

*/

private void fillColor(int[] pixels, int w, int h, int pixel, int newColor, int i, int j)

{

mStacks.push(new Point(i, j));

while (!mStacks.isEmpty())

{

Point seed = mStacks.pop();

Log.e(“TAG”, "seed = " + seed.x + " , seed = " + seed.y);

int index = seed.y * w + seed.x;

pixels[index] = newColor;

if (seed.y > 0)

{

int top = index - w;

if (pixels[top] == pixel)

{

mStacks.push(new Point(seed.x, seed.y - 1));

}

}

if (seed.y < h - 1)

{

int bottom = index + w;

if (pixels[bottom] == pixel)

{

mStacks.push(new Point(seed.x, seed.y + 1));

}

}

if (seed.x > 0)

{

int left = index - 1;

if (pixels[left] == pixel)

{

mStacks.push(new Point(seed.x - 1, seed.y));

}

}

if (seed.x < w - 1)

{

int right = index + 1;

if (pixels[right] == pixel)

{

mStacks.push(new Point(seed.x + 1, seed.y));

}

}

}

}

方法的思想也比较简单,将当前像素点入栈,然后出栈着色,接下来分别判断四个方向的,如果符合条件也进行入栈(只要栈不为空持续运行)。ok,这个方法我也尝试跑了下,恩,这次不会报错了,但是速度特别的慢~~~~慢得我是不可接受的。(有兴趣可以尝试,记得如果ANR,点击等待)。

这样来看,第一种算法,我们是不考虑了,没有办法使用,主要原因是假设对于矩形同色区域,都是需要填充的,而算法一依然是各种入栈。于是考虑第二种算法

扫描线填充法

详细可参考 扫描线种子填充算法的解析扫描线种子填充算法

算法思想[4]:

  1. 初始化一个空的栈用于存放种子点,将种子点(x, y)入栈;
  1. 判断栈是否为空,如果栈为空则结束算法,否则取出栈顶元素作为当前扫描线的种子点(x, y),y是当前的扫描线;
  1. 从种子点(x, y)出发,沿当前扫描线向左、右两个方向填充,直到边界。分别标记区段的左、右端点坐标为xLeft和xRight;
  1. 分别检查与当前扫描线相邻的y - 1和y + 1两条扫描线在区间[xLeft, xRight]中的像素,从xRight开始向xLeft方向搜索,假设扫描的区间为AAABAAC(A为种子点颜色),那么将B和C前面的A作为种子点压入栈中,然后返回第(2)步;

上述参考自参考文献[4],做了些修改,文章[4]中描述算法,测试有一点问题,所以做了修改.

可以看到该算法,基本上是一行一行着色的,这样的话在大块需要着色区域的效率比算法一要高很多。

ok,关于算法的步骤大家目前觉得模糊,一会可以参照我们的代码。选定了算法以后,接下来就开始编码了。


三、编码实现

我们代码中引入了一个边界颜色,如果设置的话,着色的边界参考为该边界颜色,否则会只要与种子颜色不一致为边界。

(一)构造方法与测量

public class ColourImageView extends ImageView

{

private Bitmap mBitmap;

/**

  • 边界的颜色

*/

private int mBorderColor = -1;

private boolean hasBorderColor = false;

private Stack mStacks = new Stack();

public ColourImageView(Context context, AttributeSet attrs)

{

super(context, attrs);

TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.ColourImageView);

mBorderColor = ta.getColor(R.styleable.ColourImageView_border_color, -1);

hasBorderColor = (mBorderColor != -1);

L.e("hasBorderColor = " + hasBorderColor + " , mBorderColor = " + mBorderColor);

ta.recycle();

}

@Override

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)

{

super.onMeasure(widthMeasureSpec, heightMeasureSpec);

int viewWidth = getMeasuredWidth();

int viewHeight = getMeasuredHeight();

//以宽度为标准,等比例缩放view的高度

setMeasuredDimension(viewWidth,

getDrawable().getIntrinsicHeight() * viewWidth / getDrawable().getIntrinsicWidth());

L.e("view’s width = " + getMeasuredWidth() + " , view’s height = " + getMeasuredHeight());

//根据drawable,去得到一个和view一样大小的bitmap

BitmapDrawable drawable = (BitmapDrawable) getDrawable();

Bitmap bm = drawable.getBitmap();

mBitmap = Bitmap.createScaledBitmap(bm, getMeasuredWidth(), getMeasuredHeight(), false);

}

可以看到我们选择的是继承ImageView,这样只需要将图片设为src即可。

构造方法中获取我们的自定义边界颜色,当然可以不设置~~

重写测量的目的是为了获取一个和View一样大小的Bitmap便于我们操作。

接下来就是点击啦~

(二)onTouchEvent

@Override

public boolean onTouchEvent(MotionEvent event)

{

final int x = (int) event.getX();

final int y = (int) event.getY();

if (event.getAction() == MotionEvent.ACTION_DOWN)

{

//填色

fillColorToSameArea(x, y);

}

return super.onTouchEvent(event);

}

/**

  • 根据x,y获得改点颜色,进行填充

  • @param x

  • @param y

*/

private void fillColorToSameArea(int x, int y)

{

Bitmap bm = mBitmap;

int pixel = bm.getPixel(x, y);

if (pixel == Color.TRANSPARENT || (hasBorderColor && mBorderColor == pixel))

{

return;

}

int newColor = randomColor();

int w = bm.getWidth();

int h = bm.getHeight();

//拿到该bitmap的颜色数组

int[] pixels = new int[w * h];

bm.getPixels(pixels, 0, w, 0, 0, w, h);

//填色

fillColor(pixels, w, h, pixel, newColor, x, y);

//重新设置bitmap

bm.setPixels(pixels, 0, w, 0, 0, w, h);

setImageDrawable(new BitmapDrawable(bm));

}

可以看到,我们在onTouchEvent中获取(x,y),然后拿到改点坐标:

  • 获得点击点颜色,获得整个bitmap的像素数组

  • 改变这个数组中的颜色

  • 然后重新设置给bitmap,重新设置给ImageView

重点就是通过fillColor去改变数组中的颜色

/**

  • @param pixels 像素数组

  • @param w 宽度

  • @param h 高度

  • @param pixel 当前点的颜色

  • @param newColor 填充色

  • @param i 横坐标

  • @param j 纵坐标

*/

private void fillColor(int[] pixels, int w, int h, int pixel, int newColor, int i, int j)

{

//步骤1:将种子点(x, y)入栈;

mStacks.push(new Point(i, j));

//步骤2:判断栈是否为空,

// 如果栈为空则结束算法,否则取出栈顶元素作为当前扫描线的种子点(x, y),

// y是当前的扫描线;

while (!mStacks.isEmpty())

{

/**

  • 步骤3:从种子点(x, y)出发,沿当前扫描线向左、右两个方向填充,

  • 直到边界。分别标记区段的左、右端点坐标为xLeft和xRight;

*/

Point seed = mStacks.pop();

//L.e("seed = " + seed.x + " , seed = " + seed.y);

int count = fillLineLeft(pixels, pixel, w, h, newColor, seed.x, seed.y);

int left = seed.x - count + 1;

count = fillLineRight(pixels, pixel, w, h, newColor, seed.x + 1, seed.y);

int right = seed.x + count;

/**

  • 步骤4:

  • 分别检查与当前扫描线相邻的y - 1和y + 1两条扫描线在区间[xLeft, xRight]中的像素,

  • 从xRight开始向xLeft方向搜索,假设扫描的区间为AAABAAC(A为种子点颜色),

  • 那么将B和C前面的A作为种子点压入栈中,然后返回第(2)步;

*/

//从y-1找种子

if (seed.y - 1 >= 0)

findSeedInNewLine(pixels, pixel, w, h, seed.y - 1, left, right);

//从y+1找种子

if (seed.y + 1 < h)

findSeedInNewLine(pixels, pixel, w, h, seed.y + 1, left, right);

}

}

可以看到我已经很清楚的将该算法的四个步骤标识到该方法中。好了,最后就是一些依赖的细节上的方法:

/**

  • 在新行找种子节点

  • @param pixels

  • @param pixel

  • @param w

  • @param h

  • @param i

  • @param left

  • @param right

*/

private void findSeedInNewLine(int[] pixels, int pixel, int w, int h, int i, int left, int right)

{

/**

  • 获得该行的开始索引

*/

int begin = i * w + left;

/**

  • 获得该行的结束索引

*/

int end = i * w + right;

boolean hasSeed = false;

int rx = -1, ry = -1;

ry = i;

/**

  • 从end到begin,找到种子节点入栈(AAABAAAB,则B前的A为种子节点)

学习分享

在当下这个信息共享的时代,很多资源都可以在网络上找到,只取决于你愿不愿意找或是找的方法对不对了

很多朋友不是没有资料,大多都是有几十上百个G,但是杂乱无章,不知道怎么看从哪看起,甚至是看后就忘

如果大家觉得自己在网上找的资料非常杂乱、不成体系的话,我也分享一套给大家,比较系统,我平常自己也会经常研读。

2021最新上万页的大厂面试真题

image

七大模块学习资料:如NDK模块开发、Android框架体系架构…

image

2021大厂面试真题:

image

只有系统,有方向的学习,才能在短时间内迅速提高自己的技术,只有不断地学习,不懈的努力才能拥有更好的技术,才能在互联网行业中立于不败之地。

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

可以在网络上找到,只取决于你愿不愿意找或是找的方法对不对了

很多朋友不是没有资料,大多都是有几十上百个G,但是杂乱无章,不知道怎么看从哪看起,甚至是看后就忘

如果大家觉得自己在网上找的资料非常杂乱、不成体系的话,我也分享一套给大家,比较系统,我平常自己也会经常研读。

2021最新上万页的大厂面试真题

[外链图片转存中…(img-6o53WO5F-1715604108324)]

七大模块学习资料:如NDK模块开发、Android框架体系架构…

[外链图片转存中…(img-3uC8DxAU-1715604108324)]

2021大厂面试真题:

[外链图片转存中…(img-wT3Emc0u-1715604108325)]

只有系统,有方向的学习,才能在短时间内迅速提高自己的技术,只有不断地学习,不懈的努力才能拥有更好的技术,才能在互联网行业中立于不败之地。

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 8
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值