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;    }}

这样就完成了一些基本功能,不过需求不仅仅如此,所以未完待续。

0 0