Android 自定义控件——图片剪裁

Android 自定义控件——图片剪裁
分类: Android   171人阅读  评论(3)  收藏  举报

本文介绍一个自定义的图片剪裁控件

该控件由另一篇博文:Android 图片拖拽、放大缩小的自定义控件 扩展而来

如图:

         

思路:在一个自定义View上绘制一张图片(参照前面提到的另一篇博文),在该自定义View上绘制一个自定义的FloatDrawable,也就是图中的浮层。绘制图片和FloatDrawable的交集的补集部分灰色阴影(这个其实很简单,就一句话)。在自定义View的touch中去处理具体的拖动事件和FloatDrawable的变换。图片的绘制和FloatDrawable的绘制以及变换最终其实就是在操作各自的Rect而已,Rect就是一个有矩形,有四个坐标,图片和FloatDrawable就是按照坐标去绘制的。


CropImageView.java

该类继承View

功能:在onDraw方法中画图片、浮层,处理touch事件,最后根据坐标对图片进行剪裁。

[java]  view plain copy
  1. public class CropImageView extends View {  
  2.     // 在touch重要用到的点,  
  3.     private float mX_1 = 0;  
  4.     private float mY_1 = 0;  
  5.     // 触摸事件判断  
  6.     private final int STATUS_SINGLE = 1;  
  7.     private final int STATUS_MULTI_START = 2;  
  8.     private final int STATUS_MULTI_TOUCHING = 3;  
  9.     // 当前状态  
  10.     private int mStatus = STATUS_SINGLE;  
  11.     // 默认裁剪的宽高  
  12.     private int cropWidth;  
  13.     private int cropHeight;  
  14.     // 浮层Drawable的四个点  
  15.     private final int EDGE_LT = 1;  
  16.     private final int EDGE_RT = 2;  
  17.     private final int EDGE_LB = 3;  
  18.     private final int EDGE_RB = 4;  
  19.     private final int EDGE_MOVE_IN = 5;  
  20.     private final int EDGE_MOVE_OUT = 6;  
  21.     private final int EDGE_NONE = 7;  
  22.   
  23.     public int currentEdge = EDGE_NONE;  
  24.   
  25.     protected float oriRationWH = 0;  
  26.     protected final float maxZoomOut = 5.0f;  
  27.     protected final float minZoomIn = 0.333333f;  
  28.   
  29.     protected Drawable mDrawable;  
  30.     protected FloatDrawable mFloatDrawable;  
  31.   
  32.     protected Rect mDrawableSrc = new Rect();// 图片Rect变换时的Rect  
  33.     protected Rect mDrawableDst = new Rect();// 图片Rect  
  34.     protected Rect mDrawableFloat = new Rect();// 浮层的Rect  
  35.     protected boolean isFrist = true;  
  36.     private boolean isTouchInSquare = true;  
  37.   
  38.     protected Context mContext;  
  39.   
  40.     public CropImageView(Context context) {  
  41.         super(context);  
  42.         init(context);  
  43.     }  
  44.   
  45.     public CropImageView(Context context, AttributeSet attrs) {  
  46.         super(context, attrs);  
  47.         init(context);  
  48.     }  
  49.   
  50.     public CropImageView(Context context, AttributeSet attrs, int defStyle) {  
  51.         super(context, attrs, defStyle);  
  52.         init(context);  
  53.   
  54.     }  
  55.   
  56.     @SuppressLint("NewApi")  
  57.     private void init(Context context) {  
  58.         this.mContext = context;  
  59.         try {  
  60.             if (android.os.Build.VERSION.SDK_INT >= 11) {  
  61.                 this.setLayerType(LAYER_TYPE_SOFTWARE, null);  
  62.             }  
  63.         } catch (Exception e) {  
  64.             e.printStackTrace();  
  65.         }  
  66.         mFloatDrawable = new FloatDrawable(context);  
  67.     }  
  68.   
  69.     public void setDrawable(Drawable mDrawable, int cropWidth, int cropHeight) {  
  70.         this.mDrawable = mDrawable;  
  71.         this.cropWidth = cropWidth;  
  72.         this.cropHeight = cropHeight;  
  73.         this.isFrist = true;  
  74.         invalidate();  
  75.     }  
  76.   
  77.     @SuppressLint("ClickableViewAccessibility")  
  78.     @Override  
  79.     public boolean onTouchEvent(MotionEvent event) {  
  80.   
  81.         if (event.getPointerCount() > 1) {  
  82.             if (mStatus == STATUS_SINGLE) {  
  83.                 mStatus = STATUS_MULTI_START;  
  84.             } else if (mStatus == STATUS_MULTI_START) {  
  85.                 mStatus = STATUS_MULTI_TOUCHING;  
  86.             }  
  87.         } else {  
  88.             if (mStatus == STATUS_MULTI_START  
  89.                     || mStatus == STATUS_MULTI_TOUCHING) {  
  90.                 mX_1 = event.getX();  
  91.                 mY_1 = event.getY();  
  92.             }  
  93.   
  94.             mStatus = STATUS_SINGLE;  
  95.         }  
  96.   
  97.         switch (event.getAction()) {  
  98.         case MotionEvent.ACTION_DOWN:  
  99.             mX_1 = event.getX();  
  100.             mY_1 = event.getY();  
  101.             currentEdge = getTouch((int) mX_1, (int) mY_1);  
  102.             isTouchInSquare = mDrawableFloat.contains((int) event.getX(),  
  103.                     (int) event.getY());  
  104.   
  105.             break;  
  106.   
  107.         case MotionEvent.ACTION_UP:  
  108.             checkBounds();  
  109.             break;  
  110.   
  111.         case MotionEvent.ACTION_POINTER_UP:  
  112.             currentEdge = EDGE_NONE;  
  113.             break;  
  114.   
  115.         case MotionEvent.ACTION_MOVE:  
  116.             if (mStatus == STATUS_MULTI_TOUCHING) {  
  117.   
  118.             } else if (mStatus == STATUS_SINGLE) {  
  119.                 int dx = (int) (event.getX() - mX_1);  
  120.                 int dy = (int) (event.getY() - mY_1);  
  121.   
  122.                 mX_1 = event.getX();  
  123.                 mY_1 = event.getY();  
  124.                 // 根據得到的那一个角,并且变换Rect  
  125.                 if (!(dx == 0 && dy == 0)) {  
  126.                     switch (currentEdge) {  
  127.                     case EDGE_LT:  
  128.                         mDrawableFloat.set(mDrawableFloat.left + dx,  
  129.                                 mDrawableFloat.top + dy, mDrawableFloat.right,  
  130.                                 mDrawableFloat.bottom);  
  131.                         break;  
  132.   
  133.                     case EDGE_RT:  
  134.                         mDrawableFloat.set(mDrawableFloat.left,  
  135.                                 mDrawableFloat.top + dy, mDrawableFloat.right  
  136.                                         + dx, mDrawableFloat.bottom);  
  137.                         break;  
  138.   
  139.                     case EDGE_LB:  
  140.                         mDrawableFloat.set(mDrawableFloat.left + dx,  
  141.                                 mDrawableFloat.top, mDrawableFloat.right,  
  142.                                 mDrawableFloat.bottom + dy);  
  143.                         break;  
  144.   
  145.                     case EDGE_RB:  
  146.                         mDrawableFloat.set(mDrawableFloat.left,  
  147.                                 mDrawableFloat.top, mDrawableFloat.right + dx,  
  148.                                 mDrawableFloat.bottom + dy);  
  149.                         break;  
  150.   
  151.                     case EDGE_MOVE_IN:  
  152.                         if (isTouchInSquare) {  
  153.                             mDrawableFloat.offset((int) dx, (int) dy);  
  154.                         }  
  155.                         break;  
  156.   
  157.                     case EDGE_MOVE_OUT:  
  158.                         break;  
  159.                     }  
  160.                     mDrawableFloat.sort();  
  161.                     invalidate();  
  162.                 }  
  163.             }  
  164.             break;  
  165.         }  
  166.   
  167.         return true;  
  168.     }  
  169.   
  170.     // 根据初触摸点判断是触摸的Rect哪一个角  
  171.     public int getTouch(int eventX, int eventY) {  
  172.         if (mFloatDrawable.getBounds().left <= eventX  
  173.                 && eventX < (mFloatDrawable.getBounds().left + mFloatDrawable  
  174.                         .getBorderWidth())  
  175.                 && mFloatDrawable.getBounds().top <= eventY  
  176.                 && eventY < (mFloatDrawable.getBounds().top + mFloatDrawable  
  177.                         .getBorderHeight())) {  
  178.             return EDGE_LT;  
  179.         } else if ((mFloatDrawable.getBounds().right - mFloatDrawable  
  180.                 .getBorderWidth()) <= eventX  
  181.                 && eventX < mFloatDrawable.getBounds().right  
  182.                 && mFloatDrawable.getBounds().top <= eventY  
  183.                 && eventY < (mFloatDrawable.getBounds().top + mFloatDrawable  
  184.                         .getBorderHeight())) {  
  185.             return EDGE_RT;  
  186.         } else if (mFloatDrawable.getBounds().left <= eventX  
  187.                 && eventX < (mFloatDrawable.getBounds().left + mFloatDrawable  
  188.                         .getBorderWidth())  
  189.                 && (mFloatDrawable.getBounds().bottom - mFloatDrawable  
  190.                         .getBorderHeight()) <= eventY  
  191.                 && eventY < mFloatDrawable.getBounds().bottom) {  
  192.             return EDGE_LB;  
  193.         } else if ((mFloatDrawable.getBounds().right - mFloatDrawable  
  194.                 .getBorderWidth()) <= eventX  
  195.                 && eventX < mFloatDrawable.getBounds().right  
  196.                 && (mFloatDrawable.getBounds().bottom - mFloatDrawable  
  197.                         .getBorderHeight()) <= eventY  
  198.                 && eventY < mFloatDrawable.getBounds().bottom) {  
  199.             return EDGE_RB;  
  200.         } else if (mFloatDrawable.getBounds().contains(eventX, eventY)) {  
  201.             return EDGE_MOVE_IN;  
  202.         }  
  203.         return EDGE_MOVE_OUT;  
  204.     }  
  205.   
  206.     @Override  
  207.     protected void onDraw(Canvas canvas) {  
  208.   
  209.         if (mDrawable == null) {  
  210.             return;  
  211.         }  
  212.   
  213.         if (mDrawable.getIntrinsicWidth() == 0  
  214.                 || mDrawable.getIntrinsicHeight() == 0) {  
  215.             return;  
  216.         }  
  217.   
  218.         configureBounds();  
  219.         // 在画布上花图片  
  220.         mDrawable.draw(canvas);  
  221.         canvas.save();  
  222.         // 在画布上画浮层FloatDrawable,Region.Op.DIFFERENCE是表示Rect交集的补集  
  223.         canvas.clipRect(mDrawableFloat, Region.Op.DIFFERENCE);  
  224.         // 在交集的补集上画上灰色用来区分  
  225.         canvas.drawColor(Color.parseColor("#a0000000"));  
  226.         canvas.restore();  
  227.         // 画浮层  
  228.         mFloatDrawable.draw(canvas);  
  229.     }  
  230.   
  231.     protected void configureBounds() {  
  232.         // configureBounds在onDraw方法中调用  
  233.         // isFirst的目的是下面对mDrawableSrc和mDrawableFloat只初始化一次,  
  234.         // 之后的变化是根据touch事件来变化的,而不是每次执行重新对mDrawableSrc和mDrawableFloat进行设置  
  235.         if (isFrist) {  
  236.             oriRationWH = ((float) mDrawable.getIntrinsicWidth())  
  237.                     / ((float) mDrawable.getIntrinsicHeight());  
  238.   
  239.             final float scale = mContext.getResources().getDisplayMetrics().density;  
  240.             int w = Math.min(getWidth(), (int) (mDrawable.getIntrinsicWidth()  
  241.                     * scale + 0.5f));  
  242.             int h = (int) (w / oriRationWH);  
  243.   
  244.             int left = (getWidth() - w) / 2;  
  245.             int top = (getHeight() - h) / 2;  
  246.             int right = left + w;  
  247.             int bottom = top + h;  
  248.   
  249.             mDrawableSrc.set(left, top, right, bottom);  
  250.             mDrawableDst.set(mDrawableSrc);  
  251.   
  252.             int floatWidth = dipTopx(mContext, cropWidth);  
  253.             int floatHeight = dipTopx(mContext, cropHeight);  
  254.   
  255.             if (floatWidth > getWidth()) {  
  256.                 floatWidth = getWidth();  
  257.                 floatHeight = cropHeight * floatWidth / cropWidth;  
  258.             }  
  259.   
  260.             if (floatHeight > getHeight()) {  
  261.                 floatHeight = getHeight();  
  262.                 floatWidth = cropWidth * floatHeight / cropHeight;  
  263.             }  
  264.   
  265.             int floatLeft = (getWidth() - floatWidth) / 2;  
  266.             int floatTop = (getHeight() - floatHeight) / 2;  
  267.             mDrawableFloat.set(floatLeft, floatTop, floatLeft + floatWidth,  
  268.                     floatTop + floatHeight);  
  269.   
  270.             isFrist = false;  
  271.         }  
  272.   
  273.         mDrawable.setBounds(mDrawableDst);  
  274.         mFloatDrawable.setBounds(mDrawableFloat);  
  275.     }  
  276.   
  277.     // 在up事件中调用了该方法,目的是检查是否把浮层拖出了屏幕  
  278.     protected void checkBounds() {  
  279.         int newLeft = mDrawableFloat.left;  
  280.         int newTop = mDrawableFloat.top;  
  281.   
  282.         boolean isChange = false;  
  283.         if (mDrawableFloat.left < getLeft()) {  
  284.             newLeft = getLeft();  
  285.             isChange = true;  
  286.         }  
  287.   
  288.         if (mDrawableFloat.top < getTop()) {  
  289.             newTop = getTop();  
  290.             isChange = true;  
  291.         }  
  292.   
  293.         if (mDrawableFloat.right > getRight()) {  
  294.             newLeft = getRight() - mDrawableFloat.width();  
  295.             isChange = true;  
  296.         }  
  297.   
  298.         if (mDrawableFloat.bottom > getBottom()) {  
  299.             newTop = getBottom() - mDrawableFloat.height();  
  300.             isChange = true;  
  301.         }  
  302.   
  303.         mDrawableFloat.offsetTo(newLeft, newTop);  
  304.         if (isChange) {  
  305.             invalidate();  
  306.         }  
  307.     }  
  308.   
  309.     // 进行图片的裁剪,所谓的裁剪就是根据Drawable的新的坐标在画布上创建一张新的图片  
  310.     public Bitmap getCropImage() {  
  311.         Bitmap tmpBitmap = Bitmap.createBitmap(getWidth(), getHeight(),  
  312.                 Config.RGB_565);  
  313.         Canvas canvas = new Canvas(tmpBitmap);  
  314.         mDrawable.draw(canvas);  
  315.   
  316.         Matrix matrix = new Matrix();  
  317.         float scale = (float) (mDrawableSrc.width())  
  318.                 / (float) (mDrawableDst.width());  
  319.         matrix.postScale(scale, scale);  
  320.   
  321.         Bitmap ret = Bitmap.createBitmap(tmpBitmap, mDrawableFloat.left,  
  322.                 mDrawableFloat.top, mDrawableFloat.width(),  
  323.                 mDrawableFloat.height(), matrix, true);  
  324.         tmpBitmap.recycle();  
  325.         tmpBitmap = null;  
  326.   
  327.         return ret;  
  328.     }  
  329.   
  330.     public int dipTopx(Context context, float dpValue) {  
  331.         final float scale = context.getResources().getDisplayMetrics().density;  
  332.         return (int) (dpValue * scale + 0.5f);  
  333.     }  
  334. }  

FloatDrawable.java

继承自Drawable

功能:图片上面的浮动框,通过拖动确定位置

[java]  view plain copy
  1. public class FloatDrawable extends Drawable {  
  2.   
  3.     private Context mContext;  
  4.     private int offset = 50;  
  5.     private Paint mLinePaint = new Paint();  
  6.     private Paint mLinePaint2 = new Paint();  
  7.     {  
  8.         mLinePaint.setARGB(200505050);  
  9.         mLinePaint.setStrokeWidth(1F);  
  10.         mLinePaint.setStyle(Paint.Style.STROKE);  
  11.         mLinePaint.setAntiAlias(true);  
  12.         mLinePaint.setColor(Color.WHITE);  
  13.         //  
  14.         mLinePaint2.setARGB(200505050);  
  15.         mLinePaint2.setStrokeWidth(7F);  
  16.         mLinePaint2.setStyle(Paint.Style.STROKE);  
  17.         mLinePaint2.setAntiAlias(true);  
  18.         mLinePaint2.setColor(Color.WHITE);  
  19.     }  
  20.   
  21.     public FloatDrawable(Context context) {  
  22.         super();  
  23.         this.mContext = context;  
  24.   
  25.     }  
  26.   
  27.     public int getBorderWidth() {  
  28.         return dipTopx(mContext, offset);//根据dip计算的像素值,做适配用的  
  29.     }  
  30.   
  31.     public int getBorderHeight() {  
  32.         return dipTopx(mContext, offset);  
  33.     }  
  34.   
  35.     @Override  
  36.     public void draw(Canvas canvas) {  
  37.   
  38.         int left = getBounds().left;  
  39.         int top = getBounds().top;  
  40.         int right = getBounds().right;  
  41.         int bottom = getBounds().bottom;  
  42.   
  43.         Rect mRect = new Rect(left + dipTopx(mContext, offset) / 2, top  
  44.                 + dipTopx(mContext, offset) / 2, right  
  45.                 - dipTopx(mContext, offset) / 2, bottom  
  46.                 - dipTopx(mContext, offset) / 2);  
  47.         //画默认的选择框  
  48.         canvas.drawRect(mRect, mLinePaint);  
  49.         //画四个角的四个粗拐角、也就是八条粗线  
  50.         canvas.drawLine((left + dipTopx(mContext, offset) / 2 - 3.5f), top  
  51.                 + dipTopx(mContext, offset) / 2,  
  52.                 left + dipTopx(mContext, offset) - 8f,  
  53.                 top + dipTopx(mContext, offset) / 2, mLinePaint2);  
  54.         canvas.drawLine(left + dipTopx(mContext, offset) / 2,  
  55.                 top + dipTopx(mContext, offset) / 2,  
  56.                 left + dipTopx(mContext, offset) / 2,  
  57.                 top + dipTopx(mContext, offset) / 2 + 30, mLinePaint2);  
  58.         canvas.drawLine(right - dipTopx(mContext, offset) + 8f,  
  59.                 top + dipTopx(mContext, offset) / 2,  
  60.                 right - dipTopx(mContext, offset) / 2,  
  61.                 top + dipTopx(mContext, offset) / 2, mLinePaint2);  
  62.         canvas.drawLine(right - dipTopx(mContext, offset) / 2,  
  63.                 top + dipTopx(mContext, offset) / 2 - 3.5f,  
  64.                 right - dipTopx(mContext, offset) / 2,  
  65.                 top + dipTopx(mContext, offset) / 2 + 30, mLinePaint2);  
  66.         canvas.drawLine((left + dipTopx(mContext, offset) / 2 - 3.5f), bottom  
  67.                 - dipTopx(mContext, offset) / 2,  
  68.                 left + dipTopx(mContext, offset) - 8f,  
  69.                 bottom - dipTopx(mContext, offset) / 2, mLinePaint2);  
  70.         canvas.drawLine((left + dipTopx(mContext, offset) / 2), bottom  
  71.                 - dipTopx(mContext, offset) / 2,  
  72.                 (left + dipTopx(mContext, offset) / 2),  
  73.                 bottom - dipTopx(mContext, offset) / 2 - 30f, mLinePaint2);  
  74.         canvas.drawLine((right - dipTopx(mContext, offset) + 8f), bottom  
  75.                 - dipTopx(mContext, offset) / 2,  
  76.                 right - dipTopx(mContext, offset) / 2,  
  77.                 bottom - dipTopx(mContext, offset) / 2, mLinePaint2);  
  78.         canvas.drawLine((right - dipTopx(mContext, offset) / 2), bottom  
  79.                 - dipTopx(mContext, offset) / 2 - 30f,  
  80.                 right - dipTopx(mContext, offset) / 2,  
  81.                 bottom - dipTopx(mContext, offset) / 2 + 3.5f, mLinePaint2);  
  82.   
  83.     }  
  84.   
  85.     @Override  
  86.     public void setBounds(Rect bounds) {  
  87.         super.setBounds(new Rect(bounds.left - dipTopx(mContext, offset) / 2,  
  88.                 bounds.top - dipTopx(mContext, offset) / 2, bounds.right  
  89.                         + dipTopx(mContext, offset) / 2, bounds.bottom  
  90.                         + dipTopx(mContext, offset) / 2));  
  91.     }  
  92.   
  93.     @Override  
  94.     public void setAlpha(int alpha) {  
  95.   
  96.     }  
  97.   
  98.     @Override  
  99.     public void setColorFilter(ColorFilter cf) {  
  100.   
  101.     }  
  102.   
  103.     @Override  
  104.     public int getOpacity() {  
  105.         return 0;  
  106.     }  
  107.   
  108.     public int dipTopx(Context context, float dpValue) {  
  109.         final float scale = context.getResources().getDisplayMetrics().density;  
  110.         return (int) (dpValue * scale + 0.5f);  
  111.     }  
  112.   
  113. }  

使用

布局中:

[html]  view plain copy
  1. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  2.     xmlns:tools="http://schemas.android.com/tools"  
  3.     android:layout_width="match_parent"  
  4.     android:layout_height="match_parent" >  
  5.   
  6.     <com.onehead.cropimage.CropImageView  
  7.         android:id="@+id/cropimage"  
  8.         android:layout_width="match_parent"  
  9.         android:layout_height="match_parent" />  
  10.   
  11. </RelativeLayout>  
Activity中:
[java]  view plain copy
  1. public class MainActivity extends ActionBarActivity {  
  2.     private CropImageView mView;  
  3.   
  4.     @Override  
  5.     protected void onCreate(Bundle savedInstanceState) {  
  6.         super.onCreate(savedInstanceState);  
  7.         setContentView(R.layout.activity_main);  
  8.         mView = (CropImageView) findViewById(R.id.cropimage);  
  9.         //设置资源和默认长宽  
  10.         mView.setDrawable(getResources().getDrawable(R.drawable.test2), 300,  
  11.                 300);  
  12.         //调用该方法得到剪裁好的图片  
  13.         Bitmap mBitmap= mView.getCropImage();  
  14.     }  
  15.   
  16. }  
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值