Android开发之图片处理框架 (一)

最近研究公司老的图片处理框架,根据新的需求研究出了一套新的图片处理框架,性能上估计没有什么提升,不过整体的代码质量有了非常高的改进,因为毕竟要作为往下传的框架,代码要优雅装逼,可维护性好。这里估计会写两三篇博客讲这个框架,这是第一篇。
本篇博客实现的功能很简单,就是点击添加素材按钮,然后在屏幕上点击出现图片(张学友表情包),可以放大缩小旋转和删除,点击屏幕其他位置就算是取消选中,再点击可以重新选中对其进行操作,再点击按钮也可以再往上面添加图片。如图所示。

这里写图片描述

整个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
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值