在实际项目中,常常要制作一个简易的图像裁剪功能,即获取一张图片。并用一个遮罩层选择目标范围并截取保存的功能。例如以下图所看到的:
在此分享下该自己定义视图的制作过程。
需求说明
整一个视图包括一个透明的遮罩层,一个透明带白色边框的矩形。要实现的功能是:
- 点击矩形框外围:无不论什么响应
- 点击矩形框内部:可随手指移动而移动
- 点击矩形框的4个顶点:可进行对角顶点坐标不变的情况下的矩形的缩放,同一时候边框变色
以下是实现该功能的完整源代码
/** * Created by Farble on 2015/3/10. */ public class PhotoCropView extends View { private static final String TAG = "PhotoCropView"; private onLocationListener locationListener;/*listen to the Rect */ private onChangeLocationlistener changeLocationlistener;/*listening position changed */ private int MODE; private static final int MODE_OUTSIDE = 0x000000aa;/*170*/ private static final int MODE_INSIDE = 0x000000bb;/*187*/ private static final int MODE_POINT = 0X000000cc;/*204*/ private static final int MODE_ILLEGAL = 0X000000dd;/*221*/ private static final int minWidth = 100;/*the minimum width of the rectangle*/ private static final int minHeight = 200;/*the minimum height of the rectangle*/ private static final int START_X = 200; private static final int START_Y = 200; private static final float EDGE_WIDTH = 1.8f; private static final int ACCURACY= 15;/*touch accuracy*/ private int pointPosition;/*vertex of a rectangle*/ private int sX;/*start X location*/ private int sY;/*start Y location*/ private int eX;/*end X location*/ private int eY;/*end Y location*/ private int pressX;/*X coordinate values while finger press*/ private int pressY;/*Y coordinate values while finger press*/ private int memonyX;/*the last time the coordinate values of X*/ private int memonyY;/*the last time the coordinate values of Y*/ private int coverWidth = 300;/*width of selection box*/ private int coverHeight = 400;/*height of selection box*/ private Paint mPaint; private Paint mPaintLine; private Bitmap mBitmapCover; private Bitmap mBitmapRectBlack; private PorterDuffXfermode xfermode;/*paint mode*/ public PhotoCropView(Context context) { super(context); init(); } public PhotoCropView(Context context, AttributeSet attrs) { super(context, attrs); init(); } public PhotoCropView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } @SuppressWarnings("deprecation") private void init() { sX = START_X; sY = START_Y; WindowManager manager = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE); int width = manager.getDefaultDisplay().getWidth(); int height = manager.getDefaultDisplay().getHeight(); mBitmapCover = makeBitmap(width, height, 0x5A000000, 0, 0); mBitmapRectBlack = makeBitmap(coverWidth, coverHeight, 0xff000000, coverWidth, coverHeight); eX = sX + coverWidth; eY = sY + coverHeight; pressX = 0; pressY = 0; xfermode = new PorterDuffXfermode(PorterDuff.Mode.SRC_IN); mPaint = new Paint(); mPaint.setAntiAlias(true); mPaintLine = new Paint(); mPaintLine.setColor(Color.WHITE); mPaintLine.setStrokeWidth(2.0f); } /*生成bitmap*/ private Bitmap makeBitmap(int mwidth, int mheight, int resource, int staX, int staY) { Bitmap bm = Bitmap.createBitmap(mwidth, mheight, Bitmap.Config.ARGB_8888); Canvas c = new Canvas(bm); Paint p = new Paint(Paint.ANTI_ALIAS_FLAG); p.setColor(resource); c.drawRect(staX, staY, mwidth, mheight, p); return bm; } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); mPaint.setFilterBitmap(false); int sc = canvas.saveLayer(0, 0, canvas.getWidth(), canvas.getHeight(), null, Canvas.MATRIX_SAVE_FLAG | Canvas.CLIP_SAVE_FLAG | Canvas.HAS_ALPHA_LAYER_SAVE_FLAG | Canvas.FULL_COLOR_LAYER_SAVE_FLAG | Canvas.CLIP_TO_LAYER_SAVE_FLAG); canvas.drawBitmap(mBitmapCover, 0, 0, mPaint); mPaint.setXfermode(xfermode); canvas.drawBitmap(mBitmapRectBlack, sX, sY, mPaint); if (locationListener != null) { locationListener.locationRect(sX, sY, eX, eY); } mPaint.setXfermode(null); canvas.restoreToCount(sc); canvas.drawLine((float) sX - EDGE_WIDTH, (float) sY - EDGE_WIDTH, (float) eX + EDGE_WIDTH, (float) sY - EDGE_WIDTH, mPaintLine);/*up -*/ canvas.drawLine((float) sX - EDGE_WIDTH, (float) eY + EDGE_WIDTH, (float) eX + EDGE_WIDTH, (float) eY + EDGE_WIDTH, mPaintLine);/*down -*/ canvas.drawLine((float) sX - EDGE_WIDTH, (float) sY - EDGE_WIDTH, (float) sX - EDGE_WIDTH, (float) eY + EDGE_WIDTH, mPaintLine);/*left |*/ canvas.drawLine((float) eX + EDGE_WIDTH, (float) sY - EDGE_WIDTH, (float) eX + EDGE_WIDTH, (float) eY + EDGE_WIDTH, mPaintLine);/*righ |*/ } @SuppressWarnings("NullableProblems") @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: if (changeLocationlistener != null) { changeLocationlistener.locationChange("change self"); } else { changeLocationlistener = null; } memonyX = (int) event.getX(); memonyY = (int) event.getY(); checkMode(memonyX, memonyY); break; case MotionEvent.ACTION_MOVE: { switch (MODE) { case MODE_ILLEGAL: pressX = (int) event.getX(); pressY = (int) event.getY(); recoverFromIllegal(pressX, pressY); postInvalidate(); break; case MODE_OUTSIDE: //do nothing; break; case MODE_INSIDE: pressX = (int) event.getX(); pressY = (int) event.getY(); moveByTouch(pressX, pressY); postInvalidate(); break; default: /*MODE_POINT*/ pressX = (int) event.getX(); pressY = (int) event.getY(); mPaintLine.setColor(getContext().getResources().getColor(R.color.orange)); moveByPoint(pressX, pressY); postInvalidate(); break; } } break; case MotionEvent.ACTION_UP: mPaintLine.setColor(Color.WHITE); postInvalidate(); break; default: break; } return true; } /*从非法状态恢复,这里处理的是达到最小值后能拉伸放大*/ private void recoverFromIllegal(int rx, int ry) { if ((rx > sX && ry > sY) && (rx < eX && ry < eY)) { MODE = MODE_ILLEGAL; } else { MODE = MODE_POINT; } } private void checkMode(int cx, int cy) { if (cx > sX && cx < eX && cy > sY && cy < eY) { MODE = MODE_INSIDE; } else if (nearbyPoint(cx, cy) < 4) { MODE = MODE_POINT; } else { MODE = MODE_OUTSIDE; } } /*推断点(inX,inY)是否靠近矩形的4个顶点*/ private int nearbyPoint(int inX, int inY) { if ((Math.abs(sX - inX) <= ACCURACY && (Math.abs(inY - sY) <= ACCURACY))) {/*left-up angle*/ pointPosition = 0; return 0; } if ((Math.abs(eX - inX) <= ACCURACY && (Math.abs(inY - sY) <= ACCURACY))) {/*right-up angle*/ pointPosition = 1; return 1; } if ((Math.abs(sX - inX) <= ACCURACY && (Math.abs(inY - eY) <= ACCURACY))) {/*left-down angle*/ pointPosition = 2; return 2; } if ((Math.abs(eX - inX) <= ACCURACY && (Math.abs(inY - eY) <= ACCURACY))) {/*right-down angle*/ pointPosition = 3; return 3; } pointPosition = 100; return 100; } /*刷新矩形的坐标*/ private void refreshLocation(int isx, int isy, int iex, int iey) { this.sX = isx; this.sY = isy; this.eX = iex; this.eY = iey; } /*矩形随手指移动*/ private void moveByTouch(int mx, int my) {/*move center point*/ int dX = mx - memonyX; int dY = my - memonyY; sX += dX; sY += dY; eX = sX + coverWidth; eY = sY + coverHeight; memonyX = mx; memonyY = my; } /*检測矩形是否达到最小值*/ private boolean checkLegalRect(int cHeight, int cWidth) { return (cHeight > minHeight && cWidth > minWidth); } /*点击顶点附近时的缩放处理*/ @SuppressWarnings("SuspiciousNameCombination") private void moveByPoint(int bx, int by) { switch (pointPosition) { case 0:/*left-up*/ coverWidth = Math.abs(eX - bx); coverHeight = Math.abs(eY - by); //noinspection SuspiciousNameCombination if (!checkLegalRect(coverWidth, coverHeight)) { MODE = MODE_ILLEGAL; } else { mBitmapRectBlack = null; mBitmapRectBlack = makeBitmap(coverWidth, coverHeight, 0xff000000, coverWidth, coverHeight); refreshLocation(bx, by, eX, eY); } break; case 1:/*right-up*/ coverWidth = Math.abs(bx - sX); coverHeight = Math.abs(eY - by); if (!checkLegalRect(coverWidth, coverHeight)) { MODE = MODE_ILLEGAL; } else { mBitmapRectBlack = null; mBitmapRectBlack = makeBitmap(coverWidth, coverHeight, 0xff000000, coverWidth, coverHeight); refreshLocation(sX, by, bx, eY); } break; case 2:/*left-down*/ coverWidth = Math.abs(eX - bx); coverHeight = Math.abs(by - sY); if (!checkLegalRect(coverWidth, coverHeight)) { MODE = MODE_ILLEGAL; } else { mBitmapRectBlack = null; mBitmapRectBlack = makeBitmap(coverWidth, coverHeight, 0xff000000, coverWidth, coverHeight); refreshLocation(bx, sY, eX, by); } break; case 3:/*right-down*/ coverWidth = Math.abs(bx - sX); coverHeight = Math.abs(by - sY); if (!checkLegalRect(coverWidth, coverHeight)) { MODE = MODE_ILLEGAL; } else { mBitmapRectBlack = null; mBitmapRectBlack = makeBitmap(coverWidth, coverHeight, 0xff000000, coverWidth, coverHeight); refreshLocation(sX, sY, bx, by); } break; default: break; } } public void setLocationListener(onLocationListener locationListener) { this.locationListener = locationListener; } public interface onLocationListener { public void locationRect(int startX, int startY, int endX, int endY); } public interface onChangeLocationlistener { @SuppressWarnings("SameParameterValue") public void locationChange(String msg); } }
简要说明
1.可移动的透明矩形框通过PorterDuffXfermode(在还有一篇博文中有介绍,可点击这里查看)来实现
2.矩形边框的移动。缩放主要由onTouch事件做处理
3.onLocationListener 用于侦听矩形的坐标(终于可通过实现内部方法确定图像须要截取的位置)
3.onLocationListener 用于侦听矩形的坐标(终于可通过实现内部方法确定图像须要截取的位置)
怎样使用PhotoCropView
在布局文件里导入
<com.xxx.PhotoCropView android:id="@+id/test" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_marginLeft="20dp" android:layout_marginRight="20dp"/>
绑定并设置侦听器:
mCropView = (PhotoCropView)mActivity.findViewById(R.id.photo_crop_photocrop); mCropView .setLocationListener(this);
获取坐标信息:
@Override public void locationRect(int startX, int startY, int endX, int endY) { Log.d("[ "+startX+"--"+startY+"--"+endX+"--"+endY+" ]"); }
扩展该视图
在此基础上可进行进一步的扩展,如:
1.进一步改动边框的色值做警示之用
2.舍弃边框改为添加4个边角
等等,可自行在onDraw()方法及外部方法中进行扩展如:
@Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); mPaint.setFilterBitmap(false); int sc = canvas.saveLayer(0, 0, canvas.getWidth(), canvas.getHeight(), null, Canvas.MATRIX_SAVE_FLAG | Canvas.CLIP_SAVE_FLAG | Canvas.HAS_ALPHA_LAYER_SAVE_FLAG | Canvas.FULL_COLOR_LAYER_SAVE_FLAG | Canvas.CLIP_TO_LAYER_SAVE_FLAG); canvas.drawBitmap(mBitmapCover, 0, 0, mPaint); mPaint.setXfermode(xfermode); canvas.drawBitmap(mBitmapRectBlack, sX, sY, mPaint); if (locationListener != null) { locationListener.locationRect(sX, sY, eX, eY); } mPaint.setXfermode(null); canvas.restoreToCount(sc); /*在此加入4个边角...*/ }
致读者
首先感谢您阅读此文,因为本人能力有限。不免出现一些疏漏,错误。假设您有发现不论什么错误。不妥,或有更好的实现方式,可私信我。
Thanks advanced!
版权声明:本文博主原创文章。博客,未经同意不得转载。