首先说一下思路,首先绘制一个闭合路径来作为可移动的图片块的形状,在对应的背景图片位置绘制该路径并填充为灰色,接着在新建一个同样大小的图片,背景设为透明,以及一个对应新的Canvas,利用该路径切割Canvas,并绘制背景图片,此时该图片只有路径区域显示背景图片对应位置其他区域为透明,接着重写onDraw(),接着绘制这两张图片,并通过监听move事件移动背景透明的图片。效果如图:
下面是源码:
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
/**
* Created by lc on 16-12-26.
*/
public class MySlideImage extends View {
private int backgroundId;//验证图片资源ID
private int drawX;//随机生成的x坐标
private int drawY;//随机生成的Y坐标
private boolean backgroundHasDraw = false;//是否时第一次绘制
private int width;//资源图片宽度
private int height;//资源图片高度
private int targetWidth;//图片块宽度
private int targetHeight;//图片块高度
private MoveFinishBack mMoveFinishBack = null;//验证完成后回调接口
private Path mPath;//图片块路径
Bitmap mBitmap;//验证图片
private float lastX = 0;//监听滑动事件上一次坐标
private int targetLength;//可移动图片块所在图片距离左边缘距离,范围为(-drawX,0)
private int moveLength = 12;//底部圆心距离左边缘距离
public MySlideImage(Context context) {
super(context);
}
public MySlideImage(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.MySlideImage);
backgroundId = array.getResourceId(R.styleable.MySlideImage_imageSrc, 0);
mBitmap = getBitmapById(backgroundId);
width = mBitmap.getWidth();
height = mBitmap.getHeight();
Log.d("lc", "backgroundId = " + backgroundId + "height = " + height + " width = " + width);
array.recycle();
}
/*
创建可移动图块,只有路径所围成的区域可见,其他为透明
*/
public Bitmap createMoveDrawable() {
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);//创建可移动图片
Canvas canvas = new Canvas(bitmap);
canvas.clipPath(getPath());//截取画布,目的只显示需要的图片区域
Paint paint = new Paint();
paint.setAntiAlias(true);
if (mBitmap != null) {
// canvas.drawBitmap(mBitmap, new Rect(drawX,drawY,drawX+targetWidth,drawY+targetHeight), new RectF(drawX,drawY,drawX+targetWidth,drawY+targetHeight), paint);
canvas.drawBitmap(mBitmap, 0, 0, paint);
} else {
Log.d("lc", "createTargetDrawable :bitmap is null");
}
return bitmap;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(width, height + 30);
}
/*
创建底部背景
*/
public void createTargetDrawable(Canvas canvas) {
Paint paint = new Paint();
paint.setColor(Color.GRAY);
paint.setAntiAlias(true);
paint.setStyle(Paint.Style.FILL_AND_STROKE);
if (mBitmap != null) {
canvas.drawBitmap(mBitmap, 0, 0, paint);
} else {
Log.d("lc", "createTargetDrawable :bitmap is null");
}
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.XOR));
paint.setColor(Color.GREEN);
canvas.drawPath(getPath(), paint);
backgroundHasDraw = true;
}
public void getRandomXY() {
double random = Math.random();
random = random > (5 * 1.00 / 6) ? (5 * 1.00 / 6) : (random > 1.00 / 6 ? random : 1.00 / 6);
Log.d("lc", " random = " + random);
drawX = (int) (width * random);
drawY = (int) (height * random);
Log.d("lc", " drawX = " + drawX + " drawY = " + drawY);
targetHeight = height / 6;
targetWidth = width / 6;
Log.d("lc", " targetHeight = " + targetHeight + " targetWidth = " + targetWidth);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Paint paint = new Paint();
paint.setAntiAlias(true);
if (!backgroundHasDraw) {
getRandomXY();
targetLength = -drawX;
moveLength = 12;
}
drawSeekBar(canvas, paint);
createTargetDrawable(canvas);
canvas.drawBitmap(createMoveDrawable(), targetLength, 0, paint);
drawSeekButton(canvas, paint);
}
public Bitmap getBitmapById(int resourceId) {
return BitmapFactory.decodeResource(getContext().getResources(), resourceId);
}
public Path getPath() {
if (mPath == null) {
mPath = new Path();
mPath.moveTo(drawX, drawY);
mPath.lineTo(drawX + 10, drawY);
mPath.lineTo(drawX + 10, drawY + 15);
mPath.lineTo(drawX + targetWidth, drawY + 15);
mPath.lineTo(drawX + targetWidth, drawY + targetHeight);
mPath.lineTo(drawX, drawY + targetHeight);
mPath.lineTo(drawX, drawY);
}
return mPath;
}
/*
绘制底部绿色bar
*/
public void drawSeekBar(Canvas canvas, Paint paint) {
paint.setColor(Color.GREEN);
paint.setStyle(Paint.Style.FILL_AND_STROKE);
canvas.drawRoundRect(new RectF(5, height + 10, width - 5, height + 20), 0.5f, 0.5f, paint);
}
/*
绘制底部红色圆
*/
public void drawSeekButton(Canvas canvas, Paint paint) {
paint.setColor(Color.RED);
paint.setStyle(Paint.Style.FILL_AND_STROKE);
canvas.drawCircle(moveLength, height + 12, 12, paint);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
lastX = event.getX();
Log.d("lc", "action down");
break;
case MotionEvent.ACTION_MOVE:
float currentX = event.getX();
int length = (int) (currentX - lastX);
Log.d("lc", "action move length = " + length);
moveLength = Math.max(12, Math.min(width - 12, moveLength + length));
targetLength = Math.max(-drawX, Math.min(width - targetWidth, targetLength + length));
invalidate();
lastX = currentX;
break;
case MotionEvent.ACTION_UP:
boolean finished = false;
if (Math.abs(targetLength) < 6) {
finished = true;
Log.d("lc", " action up " + finished);
targetLength = 0;
postInvalidateDelayed(300);
} else {
Log.d("lc", " action up " + finished);
moveLength = 12;
targetLength = -drawX;
postInvalidateDelayed(300);
}
if (mMoveFinishBack != null) {
if (finished) {
mMoveFinishBack.MoveFinish(true);
} else {
mMoveFinishBack.MoveFinish(false);
}
}
break;
}
return true;
}
public void setCallBack(MoveFinishBack moveFinishBack) {
this.mMoveFinishBack = moveFinishBack;
}
/*
自定义路径以及设置图片块宽高
*/
public void setPathAndRect(Path path,int height,int width) {
mPath = path;
targetWidth = width;
targetHeight = height;
}
/*
刷新图片
*/
public void refreshImage(){
backgroundHasDraw = false;
mPath = null;
invalidate();
}
interface MoveFinishBack {
void MoveFinish(boolean correct);
}
}
说明:
1.上面代码中未对图片大小进行判断,所以测试大的图片时需要先对其进行尺寸压缩。
2.图片块的位置采用随机值
3.上例中图片的资源Id是通过自定义属性获得
4.部分手机(图片块只显示为一个矩形)需要在清单文件中关闭硬件加速才能看到完整效果