本文已参与「新人创作礼」活动,一起开启掘金创作之路。
-
本文参考鸿洋大佬的博客 ,解决了当填充颜色一致时,连续点击同一位置会引发死循环/程序崩溃的bug ,并可以自定义一些色板
-
效果图如下
-
布局代码如下
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/back"
tools:context=".MainActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<RelativeLayout
android:layout_width="50dp"
android:layout_height="50dp"
>
<Button
android:id="@+id/btn_red"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_centerInParent="true"
android:background="@drawable/btnred"
/>
</RelativeLayout>
<RelativeLayout
android:layout_width="50dp"
android:layout_height="50dp"
>
<Button
android:id="@+id/btn_green"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_centerInParent="true"
android:background="@drawable/btngreen"
/>
</RelativeLayout>
<RelativeLayout
android:layout_width="50dp"
android:layout_height="50dp"
>
<Button
android:id="@+id/btn_blue"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_centerInParent="true"
android:background="@drawable/btnblue"
/>
</RelativeLayout>
<RelativeLayout
android:layout_width="50dp"
android:layout_height="50dp"
>
<Button
android:id="@+id/btn_black"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_centerInParent="true"
android:background="@drawable/btnblack"
/>
</RelativeLayout>
<RelativeLayout
android:layout_width="50dp"
android:layout_height="50dp"
>
<Button
android:id="@+id/btn_white"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_centerInParent="true"
android:background="@drawable/btnwhite"
/>
</RelativeLayout>
<Button
android:layout_marginTop="10dp"
android:id="@+id/suiji"
android:layout_marginLeft="20dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/btn2"
android:text="随机颜色"
/>
</LinearLayout>
<!-- <Button
android:id="@+id/onQiehuan"
android:layout_marginTop="80dp"
android:layout_marginLeft="10dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/btn1"
android:text="切换图片"
/>-->
<com.example.demoe.ColourImageView
android:id="@+id/image1"
android:src="@drawable/image_007"
android:background="#33ff0000"
android:layout_width="match_parent"
android:layout_centerInParent="true"
android:layout_height="match_parent"/>
<com.example.demoe.ColourImageView
android:visibility="gone"
android:id="@+id/image2"
android:src="@drawable/bm_024"
android:background="#33ff0000"
android:layout_width="match_parent"
android:layout_centerInParent="true"
android:layout_height="match_parent"/>
<Button
android:layout_marginTop="10dp"
android:id="@+id/qiehuan"
android:layout_marginLeft="20dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/btn1"
android:layout_alignParentBottom="true"
android:layout_marginBottom="20dp"
android:text="切换图片"
/>
<!-- <Button
android:layout_marginTop="10dp"
android:id="@+id/btn_save"
android:layout_marginLeft="20dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/btn1"
android:layout_alignParentRight="true"
android:layout_alignParentBottom="true"
android:layout_marginBottom="20dp"
android:text="保存图片"
android:layout_marginRight="20dp" />-->
</RelativeLayout>
- 自定义一个ColourImageView类
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.Point;
import android.graphics.drawable.BitmapDrawable;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.widget.ImageView;
import java.util.Random;
import java.util.Stack;
@SuppressLint("AppCompatCustomView")
public class ColourImageView extends ImageView {
private static int mtype;//1是自定义 2是随机
private Bitmap mBitmap;
private static int mColor=-1;//默认颜色是白色
private static int oldColor;
private int newColor;
private int iswhith=0;
private int isheight=0;
/**
* 边界的颜色
*/
private int mBorderColor = -1;
private boolean hasBorderColor = false;
private Stack<Point> mStacks = new Stack<Point>();
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);
}
@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;
}
if (mtype==1){
newColor = mColor;
}else {
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));
}
public static void getColorId(int acolor) {
mColor=acolor;
}
public static void getType(int type) {
mtype=type;
}
/**
* @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));
if (pixel==newColor && newColor==oldColor){
return;
}
//步骤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;
oldColor=newColor;
/**
* 步骤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为种子节点)
*/
while (end >= begin) {
if (pixels[end] == pixel) {
if (!hasSeed) {
rx = end % w;
mStacks.push(new Point(rx, ry));
hasSeed = true;
}
} else {
hasSeed = false;
}
end--;
}
}
/**
* 往右填色,返回填充的个数
*
* @return
*/
private int fillLineRight(int[] pixels, int pixel, int w, int h, int newColor, int x, int y) {
int count = 0;
while (x < w)
{
//拿到索引
int index = y * w + x;
if (needFillPixel(pixels, pixel, index))
{
pixels[index] = newColor;
count++;
x++;
} else
{
break;
}
}
return count;
}
/**
* 往左填色,返回填色的数量值
*
* @return
*/
private int fillLineLeft(int[] pixels, int pixel, int w, int h, int newColor, int x, int y) {
int count = 0;
while (x >= 0)
{
//计算出索引
int index = y * w + x;
if (needFillPixel(pixels, pixel, index))
{
pixels[index] = newColor;
count++;
x--;
} else
{
break;
}
}
return count;
}
private boolean needFillPixel(int[] pixels, int pixel, int index) {
if (hasBorderColor)
{
return pixels[index] != mBorderColor;
} else
{
return pixels[index] == pixel;
}
}
/**
* 返回一个随机颜色
*
* @return
*/
private static int randomColor() {
Random random = new Random();
int color = Color.argb(255, random.nextInt(256), random.nextInt(256), random.nextInt(256));
return color;
}
}
- R.styleable.ColourImageView
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="ColourImageView">
<attr name="border_color" format="color|reference"></attr>
</declare-styleable>
</resources>
- ColourImageView所在位置
- Log统一管理类
import android.util.Log;
/**
* Log统一管理类
*/
public class L
{
private L()
{
/* cannot be instantiated */
throw new UnsupportedOperationException("cannot be instantiated");
}
public static boolean isDebug = true;// 是否需要打印bug,可以在application的onCreate函数里面初始化
private static final String TAG = "fyh";
// 下面四个是默认tag的函数
public static void i(String msg)
{
if (isDebug)
Log.i(TAG, msg);
}
public static void d(String msg)
{
if (isDebug)
Log.d(TAG, msg);
}
public static void e(String msg)
{
if (isDebug)
Log.e(TAG, msg);
}
public static void v(String msg)
{
if (isDebug)
Log.v(TAG, msg);
}
// 下面是传入自定义tag的函数
public static void i(String tag, String msg)
{
if (isDebug)
Log.i(tag, msg);
}
public static void d(String tag, String msg)
{
if (isDebug)
Log.i(tag, msg);
}
public static void e(String tag, String msg)
{
if (isDebug)
Log.i(tag, msg);
}
public static void v(String tag, String msg)
{
if (isDebug)
Log.i(tag, msg);
}
}
- MainActivity类的处理
import androidx.appcompat.app.AppCompatActivity;
import android.graphics.Color;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.widget.Button;
public class MainActivity extends AppCompatActivity {
private Button btn_red,btn_green,btn_blue,btn_black,suiji,btn_white,qiehuan;
private int isqh=1;
private ColourImageView image1,image2;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main2);
initView();
onClick();
}
private void initView() {
btn_red=findViewById(R.id.btn_red);
btn_green=findViewById(R.id.btn_green);
btn_blue=findViewById(R.id.btn_blue);
suiji=findViewById(R.id.suiji);
btn_white=findViewById(R.id.btn_white);
qiehuan=findViewById(R.id.qiehuan);
image1=findViewById(R.id.image1);
image2=findViewById(R.id.image2);
btn_black=findViewById(R.id.btn_black);
//默认给随机的动画
Animation scale= AnimationUtils.loadAnimation(getApplication(),R.anim.scale);
suiji.startAnimation(scale);
}
private void onClick() {
btn_red.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
ColourImageView.getColorId(Color.RED);
ColourImageView.getType(1);
btn_red.setBackgroundResource(R.drawable.btnred1);
btn_green.setBackgroundResource(R.drawable.btngreen);
btn_blue.setBackgroundResource(R.drawable.btnblue);
suiji.setBackgroundResource(R.drawable.btn1);
btn_black.setBackgroundResource(R.drawable.btnblack);
btn_white.setBackgroundResource(R.drawable.btnwhite);
Animation scale= AnimationUtils.loadAnimation(getApplication(),R.anim.scale);
btn_red.startAnimation(scale);
btn_green.clearAnimation();
btn_blue.clearAnimation();
btn_black.clearAnimation();
btn_white.clearAnimation();
suiji.clearAnimation();
}
});
btn_green.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
ColourImageView.getColorId(Color.GREEN);
ColourImageView.getType(1);
btn_red.setBackgroundResource(R.drawable.btnred);
btn_green.setBackgroundResource(R.drawable.btngreen1);
btn_blue.setBackgroundResource(R.drawable.btnblue);
suiji.setBackgroundResource(R.drawable.btn1);
btn_black.setBackgroundResource(R.drawable.btnblack);
btn_white.setBackgroundResource(R.drawable.btnwhite);
Animation scale= AnimationUtils.loadAnimation(getApplication(),R.anim.scale);
btn_green.startAnimation(scale);
btn_red.clearAnimation();
btn_blue.clearAnimation();
btn_black.clearAnimation();
btn_white.clearAnimation();
suiji.clearAnimation();
}
});
btn_blue.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
ColourImageView.getColorId(Color.BLUE);
ColourImageView.getType(1);
btn_red.setBackgroundResource(R.drawable.btnred);
btn_green.setBackgroundResource(R.drawable.btngreen);
btn_blue.setBackgroundResource(R.drawable.btnblue1);
suiji.setBackgroundResource(R.drawable.btn1);
btn_black.setBackgroundResource(R.drawable.btnblack);
btn_white.setBackgroundResource(R.drawable.btnwhite);
Animation scale= AnimationUtils.loadAnimation(getApplication(),R.anim.scale);
btn_blue.startAnimation(scale);
btn_red.clearAnimation();
btn_green.clearAnimation();
btn_black.clearAnimation();
btn_white.clearAnimation();
suiji.clearAnimation();
}
});
btn_black.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
ColourImageView.getColorId(Color.BLACK);
ColourImageView.getType(1);
suiji.setBackgroundResource(R.drawable.btn1);
btn_red.setBackgroundResource(R.drawable.btnred);
btn_green.setBackgroundResource(R.drawable.btngreen);
btn_blue.setBackgroundResource(R.drawable.btnblue);
btn_black.setBackgroundResource(R.drawable.btnblack1);
btn_white.setBackgroundResource(R.drawable.btnwhite);
Animation scale= AnimationUtils.loadAnimation(getApplication(),R.anim.scale);
btn_black.startAnimation(scale);
btn_red.clearAnimation();
btn_green.clearAnimation();
btn_blue.clearAnimation();
btn_white.clearAnimation();
suiji.clearAnimation();
}
});
btn_white.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
ColourImageView.getColorId(Color.WHITE);
ColourImageView.getType(1);
suiji.setBackgroundResource(R.drawable.btn1);
btn_red.setBackgroundResource(R.drawable.btnred);
btn_green.setBackgroundResource(R.drawable.btngreen);
btn_blue.setBackgroundResource(R.drawable.btnblue);
btn_black.setBackgroundResource(R.drawable.btnblack);
btn_white.setBackgroundResource(R.drawable.btnwhite1);
Animation scale= AnimationUtils.loadAnimation(getApplication(),R.anim.scale);
btn_white.startAnimation(scale);
btn_red.clearAnimation();
btn_green.clearAnimation();
btn_blue.clearAnimation();
btn_black.clearAnimation();
suiji.clearAnimation();
}
});
suiji.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
ColourImageView.getType(2);
suiji.setBackgroundResource(R.drawable.btn2);
btn_red.setBackgroundResource(R.drawable.btnred);
btn_green.setBackgroundResource(R.drawable.btngreen);
btn_blue.setBackgroundResource(R.drawable.btnblue);
btn_black.setBackgroundResource(R.drawable.btnblack);
btn_white.setBackgroundResource(R.drawable.btnwhite);
Animation scale= AnimationUtils.loadAnimation(getApplication(),R.anim.scale);
suiji.startAnimation(scale);
btn_red.clearAnimation();
btn_green.clearAnimation();
btn_blue.clearAnimation();
btn_black.clearAnimation();
btn_white.clearAnimation();
}
});
//切换图片
qiehuan.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (isqh==1){
image2.setVisibility(View.VISIBLE);
image1.setVisibility(View.GONE);
isqh=2;
}else if(isqh==2){
image1.setVisibility(View.VISIBLE);
image2.setVisibility(View.GONE);
isqh=1;
}
}
});
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_main, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
//noinspection SimplifiableIfStatement
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
protected void onDestroy() {
super.onDestroy();
btn_red.clearAnimation();
btn_green.clearAnimation();
btn_blue.clearAnimation();
btn_black.clearAnimation();
btn_white.clearAnimation();
suiji.clearAnimation();
}
}