近來做項目,發現自己項目裏並沒有對用戶上傳頭像做裁剪處理,目前需求也比較少,於是就有了這個裁剪控件的誕生。網友們寫了很多類似的控件,但是用着別人的總是感覺不舒服,考慮到簡單實用的原則就自己實現了下。
自定義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:[email protected]
* 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。。。。。。。。。。。。。。。。。。。。
當然這就是一個簡單的實現,一般也可滿足正常的需求了。使用中有什麼意見===歡迎留言=====