自定义 ImageView 裁剪,如 Whatsapp

Posted

技术标签:

【中文标题】自定义 ImageView 裁剪,如 Whatsapp【英文标题】:Cutom ImageView Cropping like Whatsapp 【发布时间】:2019-05-13 13:49:37 【问题描述】:

我正在尝试创建自定义图像裁剪,例如 whatsapp,用户上传个人资料图片,然后用户获得矩形裁剪器,让用户可以随意裁剪图像。我不想使用任何库

根据this answer,我成功地在 imageview 上获得了可拖动的 imageview,它工作正常,但是这段代码有一些我无法修复的问题。我评论了他的github,但他不再维护此代码。

到目前为止我所尝试的:

初始化指向构造函数

public DrawView(Context context, AttributeSet attrs) 
        super(context, attrs);
        paint = new Paint();
        setFocusable(true); // necessary for getting the touch events
        canvas = new Canvas();
points[0] = new Point();
points[0].x = 150;
points[0].y = 20;
points[1] = new Point();
points[1].x = 150;
points[1].y = 20;
points[2] = new Point();
points[2].x = 150;
points[2].y = 20;
points[3] = new Point();
points[3].x = 150;
points[3].y = 20;
    

在左、右、上、下变量的onDraw上手动设置值

// the method that draws the balls

    @Override
    protected void onDraw(Canvas canvas) 
        if(points[3]==null) //point4 null when user did not touch and move on screen.
            return;
        int left, top, right, bottom;
        left = 150;
        top = 50;
        right = 150;
        bottom = 50;
        for (int i = 1; i < points.length; i++) 
            left = left > points[i].x ? points[i].x:left;
            top = top > points[i].y ? points[i].y:top;
            right = right < points[i].x ? points[i].x:right;
            bottom = bottom < points[i].y ? points[i].y:bottom;
        
        paint.setAntiAlias(true);
        paint.setDither(true);
        paint.setStrokeJoin(Paint.Join.ROUND);
        paint.setStrokeWidth(5);

        //draw stroke
        paint.setStyle(Paint.Style.STROKE);
        paint.setColor(Color.parseColor("#AADB1255"));
        paint.setStrokeWidth(2);
        canvas.drawRect(
                    left + colorballs.get(0).getWidthOfBall() / 2,
                    top + colorballs.get(0).getWidthOfBall() / 2, 
                    right + colorballs.get(2).getWidthOfBall() / 2, 
                    bottom + colorballs.get(2).getWidthOfBall() / 2, paint);
        //fill the rectangle
        paint.setStyle(Paint.Style.FILL);
        paint.setColor(Color.parseColor("#55DB1255"));
        paint.setStrokeWidth(0);
        canvas.drawRect(
                left + colorballs.get(0).getWidthOfBall() / 2,
                top + colorballs.get(0).getWidthOfBall() / 2, 
                right + colorballs.get(2).getWidthOfBall() / 2, 
                bottom + colorballs.get(2).getWidthOfBall() / 2, paint);

        //draw the corners
        BitmapDrawable bitmap = new BitmapDrawable();
        // draw the balls on the canvas
        paint.setColor(Color.BLUE);
        paint.setTextSize(18);
        paint.setStrokeWidth(0);
        for (int i =0; i < colorballs.size(); i ++) 
            ColorBall ball = colorballs.get(i);
            canvas.drawBitmap(ball.getBitmap(), ball.getX(), ball.getY(),
                    paint);

            canvas.drawText("" + (i+1), ball.getX(), ball.getY(), paint);
        
    

所以我需要解决一些问题。请帮忙

    所有 4 个点在首次启动时相互重叠,并且仅在用户触摸屏幕后可见。我尝试在背景 ImageView 的 50% 处拉伸 4 个点,但没有这样做。

    成功选择要裁剪的特定区域后。如何获取该区域的背景图像视图(请告诉我如何执行此操作)。

【问题讨论】:

【参考方案1】:

1. All 4 dots overlapping each other at first launch and only after visible after user touch the screen. I tried to make 4 dots stretch at 50% of background ImageView but failed to do so.

Answer: 你应该为points数组设置一个合适的init值,origin answer代码逻辑是在手指触摸屏int onTouchEvent()MotionEvent.ACTION_DOWN类型时赋值points数组并使视图无效然后视图将执行onDraw()绘制矩形。

所以如果你想在触摸屏幕之前显示裁剪矩形,你应该为点数组设置一个适当的值(可能是固定宽度和高度的正方形的中心,你应该计算)并手动添加绘制矩形代码在onDraw() 方法中。

2. After successfully selecting particular area to crop. How to get background imageview of that region(please just give me idea how to do this).

回答:当你有一个特定的区域要裁剪时,你必须知道该区域的坐标信息。

(1) 获取源位图,你已经在背景图像视图中显示了一张图片,所以你可以得到一个源位图,或者你可以从文件或资源中解码一个位图

(2)执行裁剪操作

Bitmap bmp = BitmapFactory.decodeResource(getResources(), R.drawable.xyz);

//you also can use other logic to get a bitmap, this according to your app logic

//create a crop area rectangle
//this crop area rectangle should calculate from the points array
Bitmap croppedBitmap = Bitmap.createBitmap(bmp, x, y, cropWidth, cropHeight);

然后你可以得到一个裁剪的位图。

希望这些提示可以让您对存档您想要的内容有所了解。

这里是Demo link 和GIF link


package com.image.crop;

import java.util.ArrayList;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;


public class DrawView extends View 

    Point[] points = new Point[4];

    /**
     * point1 and point 3 are of same group and same as point 2 and point4
     */
    int groupId = -1;
    private ArrayList<ColorBall> colorballs = new ArrayList<>();

    private int mStrokeColor = Color.parseColor("#AADB1255");
    private int mFillColor = Color.parseColor("#55DB1255");
    private Rect mCropRect = new Rect();

    // array that holds the balls
    private int balID = 0;
    // variable to know what ball is being dragged
    Paint paint;

    public DrawView(Context context) 
        this(context, null);
    

    public DrawView(Context context, AttributeSet attrs) 
        this(context, attrs, -1);
    

    public DrawView(Context context, AttributeSet attrs, int defStyle) 
        super(context, attrs, defStyle);
        init();
    

    private void init() 
        paint = new Paint();
        setFocusable(true); // necessary for getting the touch events
    

    private void initRectangle(int X, int Y) 
        //initialize rectangle.
        points[0] = new Point();
        points[0].x = X;
        points[0].y = Y;

        points[1] = new Point();
        points[1].x = X;
        points[1].y = Y + 30;

        points[2] = new Point();
        points[2].x = X + 30;
        points[2].y = Y + 30;

        points[3] = new Point();
        points[3].x = X +30;
        points[3].y = Y;

        balID = 2;
        groupId = 1;
        // declare each ball with the ColorBall class
        for (int i = 0; i < points.length; i++) 
            colorballs.add(new ColorBall(getContext(), R.drawable.gray_circle, points[i], i));
        
    

    // the method that draws the balls
    @Override
    protected void onDraw(Canvas canvas) 
        if(points[3]==null) 
            //point4 null when view first create
            initRectangle(getWidth() / 2, getHeight() / 2);
        

        int left, top, right, bottom;
        left = points[0].x;
        top = points[0].y;
        right = points[0].x;
        bottom = points[0].y;
        for (int i = 1; i < points.length; i++) 
            left = left > points[i].x ? points[i].x : left;
            top = top > points[i].y ? points[i].y : top;
            right = right < points[i].x ? points[i].x : right;
            bottom = bottom < points[i].y ? points[i].y : bottom;
        
        paint.setAntiAlias(true);
        paint.setDither(true);
        paint.setStrokeJoin(Paint.Join.ROUND);
        paint.setStrokeWidth(5);

        //draw stroke
        paint.setStyle(Paint.Style.STROKE);
        paint.setColor(mStrokeColor);
        paint.setStrokeWidth(2);

        mCropRect.left = left + colorballs.get(0).getWidthOfBall() / 2;
        mCropRect.top = top + colorballs.get(0).getWidthOfBall() / 2;
        mCropRect.right = right + colorballs.get(2).getWidthOfBall() / 2;
        mCropRect.bottom = bottom + colorballs.get(3).getWidthOfBall() / 2;
        canvas.drawRect(mCropRect, paint);

        //fill the rectangle
        paint.setStyle(Paint.Style.FILL);
        paint.setColor(mFillColor);
        paint.setStrokeWidth(0);
        canvas.drawRect(mCropRect, paint);

        // draw the balls on the canvas
        paint.setColor(Color.RED);
        paint.setTextSize(18);
        paint.setStrokeWidth(0);
        for (int i =0; i < colorballs.size(); i ++) 
            ColorBall ball = colorballs.get(i);
            canvas.drawBitmap(ball.getBitmap(), ball.getX(), ball.getY(),
                    paint);

            canvas.drawText("" + (i+1), ball.getX(), ball.getY(), paint);
        
    

    // events when touching the screen
    public boolean onTouchEvent(MotionEvent event) 
        int eventAction = event.getAction();

        int X = (int) event.getX();
        int Y = (int) event.getY();

        switch (eventAction) 

            case MotionEvent.ACTION_DOWN: // touch down so check if the finger is on
                // a ball
                if (points[0] == null) 
                    initRectangle(X, Y);
                 else 
                    //resize rectangle
                    balID = -1;
                    groupId = -1;
                    for (int i = colorballs.size()-1; i>=0; i--) 
                        ColorBall ball = colorballs.get(i);
                        // check if inside the bounds of the ball (circle)
                        // get the center for the ball
                        int centerX = ball.getX() + ball.getWidthOfBall();
                        int centerY = ball.getY() + ball.getHeightOfBall();
                        paint.setColor(Color.CYAN);
                        // calculate the radius from the touch to the center of the
                        // ball
                        double radCircle = Math
                                .sqrt((double) (((centerX - X) * (centerX - X)) + (centerY - Y)
                                        * (centerY - Y)));

                        if (radCircle < ball.getWidthOfBall()) 

                            balID = ball.getID();
                            if (balID == 1 || balID == 3) 
                                groupId = 2;
                             else 
                                groupId = 1;
                            
                            invalidate();
                            break;
                        
                        invalidate();
                    
                
                break;

            case MotionEvent.ACTION_MOVE: // touch drag with the ball

                if (balID > -1) 
                    // move the balls the same as the finger
                    colorballs.get(balID).setX(X);
                    colorballs.get(balID).setY(Y);

                    paint.setColor(Color.CYAN);
                    if (groupId == 1) 
                        colorballs.get(1).setX(colorballs.get(0).getX());
                        colorballs.get(1).setY(colorballs.get(2).getY());
                        colorballs.get(3).setX(colorballs.get(2).getX());
                        colorballs.get(3).setY(colorballs.get(0).getY());
                     else 
                        colorballs.get(0).setX(colorballs.get(1).getX());
                        colorballs.get(0).setY(colorballs.get(3).getY());
                        colorballs.get(2).setX(colorballs.get(3).getX());
                        colorballs.get(2).setY(colorballs.get(1).getY());
                    

                    invalidate();
                

                break;

            case MotionEvent.ACTION_UP:
                // touch drop - just do things here after dropping
                // doTheCrop()
                break;
        
        // redraw the canvas
        invalidate();
        return true;
    

    public void doTheCrop() 
        Bitmap sourceBitmap = null;
        Drawable backgroundDrawable = getBackground();
        if (backgroundDrawable instanceof BitmapDrawable) 
            BitmapDrawable bitmapDrawable = (BitmapDrawable) backgroundDrawable;
            if(bitmapDrawable.getBitmap() != null) 
                sourceBitmap = bitmapDrawable.getBitmap();
            
        
        if (sourceBitmap != null) 
            //source bitmap was scaled, you should calculate the rate
            float widthRate = ((float) sourceBitmap.getWidth()) / getWidth();
            float heightRate =  ((float) sourceBitmap.getHeight()) / getHeight();

            //crop the source bitmap with rate value
            int left = (int) (mCropRect.left * widthRate);
            int top = (int) (mCropRect.top * heightRate);
            int right = (int) (mCropRect.right * widthRate);
            int bottom = (int) (mCropRect.bottom * heightRate);
            Bitmap croppedBitmap = Bitmap.createBitmap(sourceBitmap, left, top, right - left, bottom - top);
            BitmapDrawable drawable = new BitmapDrawable(getResources(), croppedBitmap);
            setBackground(drawable);
        
    

    public static class ColorBall 

        Bitmap bitmap;
        Context mContext;
        Point point;
        int id;

        public ColorBall(Context context, int resourceId, Point point, int id) 
            this.id = id;
            bitmap = BitmapFactory.decodeResource(context.getResources(),
                    resourceId);
            mContext = context;
            this.point = point;
        

        public int getWidthOfBall() 
            return bitmap.getWidth();
        

        public int getHeightOfBall() 
            return bitmap.getHeight();
        

        public Bitmap getBitmap() 
            return bitmap;
        

        public int getX() 
            return point.x;
        

        public int getY() 
            return point.y;
        

        public int getID() 
            return id;
        

        public void setX(int x) 
            point.x = x;
        

        public void setY(int y) 
            point.y = y;
        
    


【讨论】:

感谢您的回答,但正如您所见,我已经尝试过 onDraw 方法,但它根本不起作用。如果您能告诉我如何正确操作,将对我有所帮助。 @Magic 我尝试了上面的解决方案,它给了我预期的结果,但是用上面的方法裁剪图像不起作用,因为上面的代码永远不会满足if (backgroundDrawable instanceof BitmapDrawable) @androidXP你可以查看demo代码,图片被设置为视图背景,所以doTheCrop()方法从视图背景中获取BitmapDrawable。您可以根据需要自定义逻辑解码位图或其他内容,但想法是获取位图并根据裁剪矩形创建裁剪位图。 感谢回复

以上是关于自定义 ImageView 裁剪,如 Whatsapp的主要内容,如果未能解决你的问题,请参考以下文章

java 自定义Android ImageView,用于包含drawable的顶部裁剪。

为啥我裁剪的图像很小?

android 怎么裁剪drawable

如何为 UITableViews 裁剪 cell.imageview.image 中的图像?

Android开发之自定义圆角矩形图片ImageView的实现

如何在android中以圆形裁剪图像?