近来做项目,发现自己项目里并没有对用户上传头像做裁剪处理,目前需求也比较少,于是就有了这个裁剪控件的诞生。网友们写了很多类似的控件,但是用着别人的总是感觉不舒服,考虑到简单实用的原则就自己实现了下。
自定义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。。。。。。。。。。。。。。。。。。。。
当然这就是一个简单的实现,一般也可满足正常的需求了。使用中有什么意见===欢迎留言=====