Android开发之图片处理框架 (一)
来源:互联网 发布:淘宝上传宝贝怎么分类 编辑:程序博客网 时间:2024/06/07 21:43
最近研究公司老的图片处理框架,根据新的需求研究出了一套新的图片处理框架,性能上估计没有什么提升,不过整体的代码质量有了非常高的改进,因为毕竟要作为往下传的框架,代码要优雅装逼,可维护性好。这里估计会写两三篇博客讲这个框架,这是第一篇。
本篇博客实现的功能很简单,就是点击添加素材按钮,然后在屏幕上点击出现图片(张学友表情包),可以放大缩小旋转和删除,点击屏幕其他位置就算是取消选中,再点击可以重新选中对其进行操作,再点击按钮也可以再往上面添加图片。如图所示。
整个Activity的上部分就是个自定义ViewGroup,自定义类PaintLayout 继承FrameLayout,因为图片可以在viewgroup随意拖动,所以用FrameLayout比较合适。下方就是个LingearLayout,放几个简单的按钮,这里就一个添加素材的按钮。布局代码:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/activity_main" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="com.example.administrator.customview.MainActivity"> <com.example.administrator.customview.PaintLayout android:id="@+id/paint_layout" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_marginBottom="40dp"> </com.example.administrator.customview.PaintLayout> <LinearLayout android:weightSum="4" android:orientation="horizontal" android:layout_alignParentBottom="true" android:layout_width="match_parent" android:layout_height="40dp"> <Button android:id="@+id/add_btn" android:text="添加素材" android:layout_width="0dp" android:layout_weight="1" android:layout_height="match_parent" /> </LinearLayout></RelativeLayout>
这里既然有了自定义的ViewGroup,那就再自定义View,这也几乎就是本节的重难点,自定义PaintView继承View,在PaintView里面定义一个接口,暴露给外层的ViewGroup去处理删除和选中事件
public interface OnEnableListener{ void callback(PaintView stampView); void onDelete(PaintView stampView); }
这里我们可以用枚举去定义状态和触摸点,状态有五种,分别是啥都没,旋转,缩放,移动,删除。触摸点应该是9个,不过这里只要四个就行了,分别是左上,右上,右下和中间
public enum Status{NONE,ROTATE,SCALE,MOVE,DELETE} /* 图片控制点 * 0---1---2 * | | * 7 8 3 * | | * 6---5---4 */ private enum PositionType{TOP_LEFT, TOP_RIGHT, BOTTOM_LEFT, BOTTOM_RIGHT} public static final int CTR_LEFT_TOP = 0; public static final int CTR_RIGHT_TOP = 2; public static final int CTR_RIGHT_BOTTOM = 4; public static final int CTR_MID_MID = 8; private Status status; private PositionType positionType;
这里中间的主图片,旋转图片,删除图片,缩放图片(两张),都需要定义;这里的移动、缩放和旋转都需要矩阵Matrix;屏幕的长宽;图片的长宽;按钮的长宽;是否显示按钮的标志值;画线条和按钮的画笔;还有辅助数据;
private Bitmap deleteBmp,rotateBmp,scaleBmp,scaleBmp2; private Bitmap bmp; private Matrix matrix; private int viewWidth,viewHeight; private int bmpWidth,bmpHeight; private int toolBmpWidth,toolBmpHeight; private Paint paint; private boolean viewEnable=false; private RectF srcRect, dstRect,horizontalRect, verticalRect, obliqueRect; private transient PointF pointHorizontal, pointVertical, pointOblique; private float[] srcPs, dstPs; private float preDegree; private float diagonalLength = 0f; private float scaleTotal = 1f; private float deltaX = 0, deltaY = 0;
需要定义的数据就这么多,现在构造方法中构造一些必须的数据
public PaintView(Context context,int pointX,int pointY,OnEnableListener listener) { super(context); this.listener=listener; WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); viewWidth = wm.getDefaultDisplay().getWidth(); viewHeight = wm.getDefaultDisplay().getHeight(); deleteBmp= BitmapFactory.decodeResource(context.getResources(),R.drawable.delete2); rotateBmp=BitmapFactory.decodeResource(context.getResources(),R.drawable.rotate); scaleBmp=BitmapFactory.decodeResource(context.getResources(),R.drawable.scale); scaleBmp2=BitmapFactory.decodeResource(context.getResources(),R.drawable.scale2); bmp=BitmapFactory.decodeResource(context.getResources(),R.drawable.a13); bmpWidth=bmp.getWidth(); bmpHeight=bmp.getHeight(); toolBmpWidth=rotateBmp.getWidth(); toolBmpHeight=rotateBmp.getHeight(); srcPs = new float[]{0, 0, bmpWidth / 2, 0, bmpWidth, 0, bmpWidth, bmpHeight / 2, bmpWidth, bmpHeight,bmpWidth / 2, bmpHeight, 0, bmpHeight, 0, bmpHeight / 2, bmpWidth / 2, bmpHeight / 2}; dstPs = srcPs.clone(); srcRect = new RectF(0, 0,bmpWidth,bmpHeight); dstRect = new RectF(); matrix=new Matrix(); matrix.setTranslate(pointX,pointY); matrix.mapPoints(dstPs, srcPs); matrix.mapRect(dstRect, srcRect); paint = new Paint(); paint.setStrokeWidth(5); paint.setColor(Color.WHITE); paint.setStyle(Paint.Style.STROKE); paint.setAntiAlias(true); paint.setPathEffect(new DashPathEffect(new float[]{10, 10}, 1)); lastPoint = new Point(0, 0); prePoint = new Point(0, 0); }
接下来是实现如何在放大缩小以及旋转时控制几个按钮的位置,直接上代码
private void drawButton(Canvas canvas){ float extendLength = getExtendLength(); positionType= getPositionType(); pointHorizontal = getHorizontalPoint(positionType, dstRect, extendLength); pointVertical = getVerticalPoint(positionType, dstRect, extendLength); pointOblique = getObliquePoint(); canvas.drawLine(dstPs[CTR_MID_MID*2], dstPs[CTR_MID_MID*2+1], pointHorizontal.x, pointHorizontal.y, paint); canvas.drawLine(dstPs[CTR_MID_MID*2], dstPs[CTR_MID_MID*2+1], pointVertical.x, pointVertical.y, paint); canvas.drawLine(dstPs[CTR_MID_MID * 2], dstPs[CTR_MID_MID * 2 + 1], pointOblique.x, pointOblique.y, paint); canvas.drawBitmap(deleteBmp, pointVertical.x - deleteBmp.getWidth() / 2 , pointVertical.y - deleteBmp.getHeight() / 2, null); canvas.drawBitmap(rotateBmp, pointHorizontal.x - scaleBmp.getWidth() / 2 , pointHorizontal.y - scaleBmp.getHeight() / 2, null); if(positionType== PositionType.TOP_LEFT||positionType== PositionType.BOTTOM_RIGHT){ canvas.drawBitmap(scaleBmp2, pointOblique.x-rotateBmp.getWidth()/2, pointOblique.y-rotateBmp.getHeight()/2,null); }else { canvas.drawBitmap(scaleBmp, pointOblique.x-rotateBmp.getWidth()/2, pointOblique.y-rotateBmp.getHeight()/2,null); } float expend_width = 20f; horizontalRect = new RectF(pointHorizontal.x - toolBmpWidth / 2- expend_width, pointHorizontal.y - toolBmpHeight / 2 - expend_width, pointHorizontal.x + toolBmpWidth / 2 + expend_width, pointHorizontal.y + toolBmpHeight / 2 + expend_width); verticalRect = new RectF(pointVertical.x - toolBmpWidth / 2- expend_width, pointVertical.y - toolBmpHeight/ 2 - expend_width, pointVertical.x + toolBmpWidth / 2 + expend_width, pointVertical.y + toolBmpHeight/ 2 + expend_width); obliqueRect = new RectF(pointOblique.x - toolBmpWidth / 2- expend_width, pointOblique.y - toolBmpHeight / 2 - expend_width, pointOblique.x + toolBmpWidth / 2 + expend_width, pointOblique.y + toolBmpHeight / 2 + expend_width); }private PositionType getPositionType(){ if (dstPs[CTR_MID_MID*2] < (viewWidth / 2)){ if (dstPs[CTR_MID_MID*2+1] < (viewHeight / 2)){ return PositionType.TOP_LEFT; } else { return PositionType.BOTTOM_LEFT; } } else { if (dstPs[CTR_MID_MID*2+1] < (viewHeight / 2)){ return PositionType.TOP_RIGHT; } else { return PositionType.BOTTOM_RIGHT; } } } private PointF getHorizontalPoint(PositionType positionType, RectF rectF, float extendLength){ PointF hPoint = new PointF(); switch (positionType){ case TOP_LEFT: case BOTTOM_LEFT: hPoint.x = rectF.right + extendLength; break; case TOP_RIGHT: case BOTTOM_RIGHT: hPoint.x = rectF.left - extendLength; break; default: break; } if (dstPs[CTR_MID_MID*2+1] < 20f){ hPoint.y = 20f; } else if (dstPs[CTR_MID_MID*2+1] > (viewHeight - 20f)) { hPoint.y = viewHeight - 20f; } else { hPoint.y = dstPs[CTR_MID_MID*2+1]; } return hPoint; } private PointF getVerticalPoint(PositionType positionType, RectF rectF, float extendLength){ PointF hPoint = new PointF(); switch (positionType){ case TOP_LEFT: case TOP_RIGHT: hPoint.y = rectF.bottom + extendLength; break; case BOTTOM_LEFT: case BOTTOM_RIGHT: hPoint.y = rectF.top - extendLength; break; default: break; } if (dstPs[CTR_MID_MID*2] < 20f){ hPoint.x = 20f; } else if (dstPs[CTR_MID_MID*2] > (viewWidth - 20f)) { hPoint.x = viewWidth - 20f; } else { hPoint.x = dstPs[CTR_MID_MID*2]; } return hPoint; } private PointF getObliquePoint(){ PointF oPoint = new PointF(); oPoint.x= pointHorizontal.x; oPoint.y=pointVertical.y; return oPoint; } private float getExtendLength(){ float max_length = 180f; float min_length = 40f; float length = diagonalLength * scaleTotal; if (length < (max_length - min_length)){ return max_length - length; } else { return min_length; } }
这段代码通过计算位置和缩放控制几个按钮的摆放
再通过触摸点的位置坐标去计算究竟触摸了哪一个按钮
private Status getCurrentStatue(float x,float y) { if (viewEnable) { //Respond area if (horizontalRect != null && horizontalRect.contains(x, y)) { //rotate return Status.ROTATE; } else if (verticalRect != null && verticalRect.contains(x, y)) { //delete return Status.DELETE; } else if (obliqueRect != null && obliqueRect.contains(x, y)) { //scale return Status.SCALE; } } float a1 = Math.abs(dstPs[CTR_LEFT_TOP * 2] - dstPs[CTR_RIGHT_TOP * 2]); float b1 = Math.abs(dstPs[CTR_LEFT_TOP * 2 + 1] - dstPs[CTR_RIGHT_TOP * 2 + 1]); int realWidth = (int) Math.sqrt(a1 * a1 + b1 * b1); float a2 = Math.abs(dstPs[CTR_RIGHT_TOP * 2] - dstPs[CTR_RIGHT_BOTTOM * 2]); float b2 = Math.abs(dstPs[CTR_RIGHT_TOP * 2 + 1] - dstPs[CTR_RIGHT_BOTTOM * 2 + 1]); int realHeight = (int) Math.sqrt(a2 * a2 + b2 * b2); RectF rectf1 = new RectF(dstPs[CTR_MID_MID * 2] - realWidth / 2, dstPs[CTR_MID_MID * 2 + 1] - realHeight / 2 , dstPs[CTR_MID_MID * 2] + realWidth / 2, dstPs[CTR_MID_MID * 2 + 1] + realHeight / 2); if (rectf1.contains(x, y)) { return Status.MOVE; } return Status.NONE; }
封装相应的旋转,缩放,和移动函数
private void translate(){ deltaX = (prePoint.x - lastPoint.x); deltaY = (prePoint.y - lastPoint.y); matrix.postTranslate(deltaX, deltaY); reMapping(); } private void rotate() { if (Math.abs(prePoint.x - lastPoint.x) > 5 || Math.abs(prePoint.y - lastPoint.y) > 5) { preDegree = computeDegree(prePoint, new Point((int) dstPs[2], (int) dstPs[3])) - computeDegree(lastPoint, new Point((int) dstPs[2], (int) dstPs[3])); matrix.postRotate(preDegree, dstPs[CTR_MID_MID * 2], dstPs[CTR_MID_MID * 2 + 1]); reMapping(); } } private void scale(){ //计算缩放触摸点到图片中点的距离 float a,b,diagonalL1; a=dstPs[CTR_MID_MID*2]-lastPoint.x; b=dstPs[CTR_MID_MID*2+1]-lastPoint.y; diagonalL1= (float) Math.sqrt(a*a+b*b); //计算触摸前后两个点的偏移 float c,d,diagonalL2; c=dstPs[CTR_MID_MID*2]-prePoint.x; d=dstPs[CTR_MID_MID*2+1]-prePoint.y; diagonalL2= (float) Math.sqrt(c*c+d*d); float scaleValue=diagonalL2/diagonalL1; matrix.postScale(scaleValue,scaleValue,dstPs[CTR_MID_MID*2],dstPs[CTR_MID_MID*2+1]); reMapping(); }private float computeDegree(Point p1, Point p2) { float tran_x = p1.x - p2.x; float tran_y = p1.y - p2.y; float degree = 0.0f; float angle = (float) (Math.asin(tran_x / Math.sqrt(tran_x * tran_x + tran_y * tran_y)) * 180 / Math.PI); if (!Float.isNaN(angle)) { if (tran_x >= 0 && tran_y <= 0) {//第一象限 degree = angle; } else if (tran_x <= 0 && tran_y <= 0) {//第二象限 degree = angle; } else if (tran_x <= 0 && tran_y >= 0) {//第三象限 degree = -180 - angle; } else if (tran_x >= 0 && tran_y >= 0) {//第四象限 degree = 180 - angle; } } return degree; }private void reMapping(){ matrix.mapPoints(dstPs, srcPs); matrix.mapRect(dstRect, srcRect); invalidate();//重绘 }
重写onDraw和onTouchEvent
@Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()){ case MotionEvent.ACTION_DOWN: status=getCurrentStatue((int) event.getX(), (int) event.getY()); if(status!= Status.NONE){ lastPoint.set((int)event.getX(),(int)event.getY()); viewEnable=true; if(status== Status.DELETE){ listener.onDelete(this); }else { setViewEnable(true); listener.callback(this); } } else { viewEnable=false; invalidate(); } break; case MotionEvent.ACTION_MOVE: prePoint.set((int)event.getX(),(int)event.getY()); if(status== Status.MOVE){ translate(); }else if(status== Status.ROTATE){ rotate(); }else if(status== Status.SCALE){ scale(); } lastPoint.set(prePoint.x,prePoint.y); break; case MotionEvent.ACTION_UP: status= Status.NONE; break; } return status== Status.NONE?false:true; } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); if(viewEnable){ drawButton(canvas); } canvas.drawBitmap(bmp,matrix,null); }
这里就将图片的所有动作全部封装在了自定义View的内部,只需要暴露给外层些许接口实现一些功能就可以了,再写我们的自定义ViewGroup,很简单,就直接上全部代码了
public class PaintLayout extends FrameLayout implements PaintView.OnEnableListener{ private boolean canInit; private Context context; private LayoutParams params; public PaintLayout(Context context) { this(context,null); } public PaintLayout(Context context, AttributeSet attrs) { super(context, attrs); this.context=context; params = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); } private void addView(MotionEvent event){ PaintView paintView=new PaintView(context,(int)event.getX(),(int)event.getY(),this); this.addView(paintView,params); } @Override public boolean onTouchEvent(MotionEvent event) { if(canInit&&event.getAction()==MotionEvent.ACTION_DOWN){ addView(event); canInit=false; } return super.onTouchEvent(event); } @Override public void callback(PaintView stampView) { for (int i = 0; i < this.getChildCount(); ++i) { if (this.getChildAt(i) != stampView) { ((PaintView) this.getChildAt(i)).setViewEnable(false); } } } @Override public void onDelete(PaintView stampView) { stampView.setVisibility(GONE); } public void setCanInit(boolean canInit) { this.canInit = canInit; }}
这样就完成了一些基本功能,不过需求不仅仅如此,所以未完待续。
- Android开发之图片处理框架 (一)
- Android开发之图片处理框架(二)
- Android开发之图片处理框架(三)
- Android应用开发之(图片处理技巧一)
- Android应用开发之(图片处理技巧一)
- Android应用开发之(图片处理技巧一)
- Android开发之图片处理专题(一):利用软引用构建图片高速缓存
- Android四大图片缓存框架之-Fresco(一)
- Android 图片处理(一)
- Android图片处理(一)
- Android开发之图片加载框架Glide
- Android之使用Android-query框架进行开发(一)
- Android之使用Android-query框架进行开发(一)
- Android之使用Android-query框架进行开发(一)
- Android之使用Android-query框架进行开发(一)
- Android之使用Android-query框架进行开发(一)
- Android之使用Android-query框架进行开发(一)
- Android之使用Android-query框架进行开发(一)
- Android-wifi热点开关
- freeswitch结构之sofia_profile_t
- MySQl之最全且必会的sql语句
- dede:list 调用某个自定义字段失败解决方法
- 外部引入css文件,transition属性问题
- Android开发之图片处理框架 (一)
- 编程之路
- Golang1.7.3发送大附件邮件
- ReentrantLock源码解析
- 正确理解Java的反射机制
- RecyclerView Glide加载图片刷新时会闪一下
- 执行Git命令时出现各种 SSL certificate problem 的解决办法
- Centos 6.5 在线Install php70
- input value值得实时监听