android自定义View之头像裁剪控件

近来做项目,发现自己项目里并没有对用户上传头像做裁剪处理,目前需求也比较少,于是就有了这个裁剪控件的诞生。网友们写了很多类似的控件,但是用着别人的总是感觉不舒服,考虑到简单实用的原则就自己实现了下。

自定义View没有gif效果图的博客都是扯蛋(个人觉得没有看下去的必要),所以先看实现效果再说:

首先思路:控件继承自ImageView

1.绘制周边透明阴影

2.上下左右拉伸移动,四个角的拉伸移动

3.计算边距和裁剪大小,重新绘制

4.使用的话只需要调用clip()方法即可,返回选择区域裁剪后的位图bitmap对象

代码思路都很简单,因为是前景透明度阴影,所以我直接在 onDrawForeground(Canvas canvas)方法中绘制,另外默认底色背景是在onDraw(Canvas canvas)方法super.onDrawForeground(canvas);语句调用前绘制。如下代码示:

package com.example.myapplication.coustom;

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.Path;
import android.graphics.Region;
import android.support.v7.widget.AppCompatImageView;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import com.example.myapplication.R;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;

/**
 * email:1040152329@qq.com
 * Created by gold on 2019/11/14
 * Describe:
 **/
public class PickImageView extends AppCompatImageView {
    public PickImageView(Context context) {
        this(context, null);
    }

    public PickImageView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public PickImageView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }


    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int sizeW = MeasureSpec.getSize(widthMeasureSpec);
        int sizeH = MeasureSpec.getSize(heightMeasureSpec);
        Log.e("+++++sizeW=", sizeW + "===" + sizeH);
        setMeasuredDimension(sizeW, sizeH);
    }

    private int w, h;//控件宽高
    private boolean isFirst = true;//第一次获取宽高

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        if (isFirst) {
            isFirst = false;
            w = r - l;
            h = b - t;
            movePadding = w / 8;
            region.set(0, 0, w, h);
            clipW = clipH = w / 2;
            paddingLeft = paddingRight = w / 4;
            paddingTop = paddingBottom = (h - clipH) / 2;
        }
    }

    private Paint paint;//画笔
    private Region region;//整个范围
    private Region regionL;//左边界区域
    private Region regionR;//右边界区域
    private Region regionT;//上边界区域
    private Region regionB;//下边界区域
    private Region regionInner;//裁剪内部边界区域
    //下面是对应的Path对象
    private Path pathLeft;
    private Path pathRight;
    private Path pathTop;
    private Path pathBottom;
    private Path pathInner;

    private void init() {
        paint = new Paint();
        paint.setColor(Color.parseColor("#991F1F1F"));
        paint.setStrokeWidth(1);
        paint.setAntiAlias(true);
        paint.setStyle(Paint.Style.FILL);

        region = new Region();
        regionL = new Region();
        regionR = new Region();
        regionT = new Region();
        regionB = new Region();
        regionInner = new Region();

        pathLeft = new Path();
        pathRight = new Path();
        pathTop = new Path();
        pathBottom = new Path();
        pathInner = new Path();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        canvas.drawColor(0xff000000);
        super.onDraw(canvas);

    }

    private int paddingLeft;
    private int paddingRight;
    private int paddingTop;
    private int paddingBottom;
    private int clipW, clipH;//裁剪图片的宽高

    @Override
    public void onDrawForeground(Canvas canvas) {
        super.onDrawForeground(canvas);
        //绘制边界带透明度阴影部分
        canvas.drawRect(0, 0, paddingLeft, h, paint);//左边
        canvas.drawRect(paddingLeft, 0, w - paddingRight, paddingTop, paint);//上
        canvas.drawRect(w - paddingRight, 0, w, h, paint);//右
        canvas.drawRect(paddingLeft, h - paddingBottom, w - paddingRight, h, paint);//下

        //重置区域,拿到有效区域的path
        pathLeft.reset();
        pathRight.reset();
        pathTop.reset();
        pathBottom.reset();
        pathInner.reset();
        pathLeft.addRect(paddingLeft - movePadding, paddingTop - movePadding,
                paddingLeft + movePadding, h - paddingBottom + movePadding, Path.Direction.CW);
        pathRight.addRect(w - paddingRight - movePadding, paddingTop - movePadding,
                w - paddingRight + movePadding, h - paddingBottom + movePadding, Path.Direction.CW);
        pathTop.addRect(paddingLeft - movePadding, paddingTop - movePadding,
                w - paddingRight + movePadding, paddingTop + movePadding, Path.Direction.CW);
        pathBottom.addRect(paddingLeft - movePadding, h - paddingBottom - movePadding,
                w - paddingRight + movePadding, h - paddingBottom + movePadding, Path.Direction.CW);
        pathInner.addRect(paddingLeft + movePadding, paddingTop + movePadding,
                w - paddingRight - movePadding, h - paddingBottom - movePadding, Path.Direction.CW);
        regionL.setPath(pathLeft, region);
        regionR.setPath(pathRight, region);
        regionT.setPath(pathTop, region);
        regionB.setPath(pathBottom, region);
        regionInner.setPath(pathInner, region);

        //获取屏幕中裁剪图片的宽高
        clipW = w - paddingLeft - paddingRight;
        clipH = h - paddingTop - paddingBottom;

        Log.e("********", clipW + "====" + clipH);
    }


    private int movePadding = 50;//移动的左右触摸有效区域
    private int downX, downY;//手指按下的位置
    private int flag = -1;//移动位置确认上、下、左、右、左上、左下、右上、右下、内部,详细见checkLocation方法内备注

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        int action = event.getAction();
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                downX = (int) event.getX();
                downY = (int) event.getY();
                checkLocation(downX, downY);//确定触摸位置
                break;
            case MotionEvent.ACTION_MOVE:
                if (flag != -1) {
                    int moveX = (int) event.getX();
                    int moveY = (int) event.getY();
                    int dx = moveX - downX;
                    int dy = moveY - downY;
                    downX = moveX;
                    downY = moveY;
                    doMoveEvent(dx, dy);//具体移动操作
                }
                break;
            case MotionEvent.ACTION_UP://重置
                flag = -1;
                break;
        }
        return true;
    }

    /**
     * 计算有效移动后的参数用于刷新页面
     *
     * @param dx x方向增量
     * @param dy y方向增量
     */
    private void doMoveEvent(int dx, int dy) {
        Log.e("+++++MMMM", dx + "----" + dy);
        switch (flag) {
            case 0://中间
                if (paddingLeft + dx <= 0 && paddingTop + dy <= 0) {//左上位置限制
                    Log.e("+++++MMM1M", dx + "----" + dy);
                    break;
                }
                if (paddingLeft + dx <= 0 && paddingBottom - dy <= 0) {//左下位置限制
                    Log.e("+++++MMM2M", dx + "----" + dy);
                    break;
                }

                if (paddingRight - dx <= 0 && paddingTop + dy <= 0) {//右上位置限制
                    Log.e("+++++MMM3M", dx + "----" + dy);
                    break;
                }
                if (paddingRight - dx <= 0 && paddingBottom - dy <= 0) {//右下位置限制
                    Log.e("+++++MMM4M", dx + "----" + dy);
                    break;
                }


                if (paddingLeft + dx <= 0 || paddingRight - dx <= 0) {//左右边界时继续往左右移动只能上下
                    Log.e("+++++MMM6M", dx + "----" + dy);
                    paddingTop += dy;
                    paddingBottom -= dy;
                    break;
                }

                if (paddingTop + dy <= 0 || paddingBottom - dy <= 0) {//上下边界时继续往左右移动只能左右
                    Log.e("+++++MMM7M", dx + "----" + dy);
                    paddingLeft += dx;
                    paddingRight -= dx;
                    break;
                }

                if (paddingLeft + dx > 0 && paddingRight - dx > 0 && paddingTop + dy > 0 && paddingBottom - dy > 0) {//范围内移动
                    Log.e("+++++MMM5M", dx + "----" + dy);
                    paddingLeft += dx;
                    paddingRight -= dx;
                    paddingTop += dy;
                    paddingBottom -= dy;
                    break;
                }

                break;

            case 1://左上
                paddingLeft += dx;
                paddingTop += dy;
                if (w / 2 > w - paddingLeft - paddingRight) {
                    paddingLeft = w / 2 - paddingRight;
                }
                if (w / 2 > h - paddingTop - paddingBottom) {
                    paddingTop = h - w / 2 - paddingBottom;
                }
                break;
            case 2://左下
                paddingLeft += dx;
                paddingBottom -= dy;
                if (w / 2 > w - paddingLeft - paddingRight) {
                    paddingLeft = w / 2 - paddingRight;
                }
                if (w / 2 > h - paddingTop - paddingBottom) {
                    paddingBottom = h - w / 2 - paddingTop;
                }
                break;
            case 3://右上
                paddingRight -= dx;
                paddingTop += dy;
                if (w / 2 > w - paddingLeft - paddingRight) {
                    paddingRight = w / 2 - paddingLeft;
                }
                if (w / 2 > h - paddingTop - paddingBottom) {
                    paddingTop = h - w / 2 - paddingBottom;
                }
                break;
            case 4://右下
                paddingRight -= dx;
                paddingBottom -= dy;
                if (w / 2 > w - paddingLeft - paddingRight) {
                    paddingRight = w / 2 - paddingLeft;
                }
                if (w / 2 > h - paddingTop - paddingBottom) {
                    paddingBottom = h - w / 2 - paddingTop;
                }
                break;
            case 5://左
                paddingLeft += dx;
                if (w / 2 > w - paddingLeft - paddingRight) {
                    paddingLeft = w / 2 - paddingRight;
                }
                break;
            case 6://右
                paddingRight -= dx;
                if (w / 2 > w - paddingLeft - paddingRight) {
                    paddingRight = w / 2 - paddingLeft;
                }
                break;
            case 7://上
                paddingTop += dy;
                if (w / 2 > h - paddingTop - paddingBottom) {
                    paddingTop = h - w / 2 - paddingBottom;
                }
                break;
            case 8://下
                paddingBottom -= dy;
                if (w / 2 > h - paddingTop - paddingBottom) {
                    paddingBottom = h - w / 2 - paddingTop;
                }
                break;
        }
        checkBounds();
        invalidate();
    }

    /**
     * 统一做边界越界限制
     */
    private void checkBounds() {
        if (paddingLeft < 0) {
            paddingLeft = 0;
        }
        if (paddingRight < 0) {
            paddingRight = 0;
        }
        if (paddingTop < 0) {
            paddingTop = 0;
        }
        if (paddingBottom < 0) {
            paddingBottom = 0;
        }
    }

    /**
     * 确定点击位置
     *
     * @param x 按下的x坐标
     * @param y 按下的y坐标
     */
    private void checkLocation(int x, int y) {
        if (regionInner.contains(x, y)) {//中间
            flag = 0;
            Log.e("+++++MM", "==========" + 0);
            return;
        }
        if (regionL.contains(x, y) && regionT.contains(x, y)) {//左上
            flag = 1;
            Log.e("+++++MM", "==========" + 1);
            return;
        }
        if (regionL.contains(x, y) && regionB.contains(x, y)) {//左下
            flag = 2;
            Log.e("+++++MM", "==========" + 2);
            return;
        }

        if (regionR.contains(x, y) && regionT.contains(x, y)) {//右上
            flag = 3;
            Log.e("+++++MM", "==========" + 3);
            return;
        }
        if (regionR.contains(x, y) && regionB.contains(x, y)) {//右下
            flag = 4;
            Log.e("+++++MM", "==========" + 4);
            return;
        }

        if (regionL.contains(x, y)) {//左
            flag = 5;
            Log.e("+++++MM", "==========" + 5);
            return;
        }
        if (regionR.contains(x, y)) {//右
            flag = 6;
            Log.e("+++++MM", "==========" + 6);
            return;
        }
        if (regionT.contains(x, y)) {//上
            flag = 7;
            Log.e("+++++MM", "==========" + 7);
            return;
        }
        if (regionB.contains(x, y)) {//下
            flag = 8;
            Log.e("+++++MM", "==========" + 8);
            return;
        }
    }

//======================重点:开发者只需要调用该方法即可===========================
    /**
     * @return Bitmap  返回裁剪后的位图
     */
    public Bitmap clip() {
        Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.abcdde);
        int width = bitmap.getWidth();
        int height = bitmap.getHeight();
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        bitmap.compress(Bitmap.CompressFormat.PNG, 100, baos);
        byte[] aa = baos.toByteArray();
        InputStream inputStream = new ByteArrayInputStream(aa);
        Bitmap bm = null;
        //需要对宽高按比例转化为真实宽高
        bm = Bitmap.createBitmap(bitmap, (int) (paddingLeft * (width * 1f / w)), (int) (paddingTop * (height * 1f / h)), (int) (clipW * (width * 1f / w)), (int) (clipH * (height * 1f / h)));
        return bm;
    }
}

控件布局使用也是简单地ImageView一样:

    <com.example.myapplication.coustom.PickImageView
        android:id="@+id/clipImage"
        android:layout_width="match_parent"
        android:layout_height="500dp"
        android:scaleType="centerCrop"
        android:src="@drawable/abcdde">
    </com.example.myapplication.coustom.PickImageView>

 

这里没有做裁剪后图片的保存操作,自己需要自己加Ok。。。。。。。。。。。。。。。。。。。。

当然这就是一个简单的实现,一般也可满足正常的需求了。使用中有什么意见===欢迎留言=====

 

 

 

 

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值