Android修改头像之拍照、从相册选择、裁剪

news2024/10/6 12:26:38

手写一个修改头像的需求,头像图片支持手机拍照裁剪和从相册选择图片裁剪;

实现效果:

 本节主要内容:

1)头像修改对话框实现;

2)调用系统相机拍照;

3)自定义图片裁剪页面;

4)从系统相册选择图片。

一、头像修改对话框实现

很简单的一个对话框

class HeadChangeDialog(context: Context) : Dialog(context, R.style.BottomDialog), View.OnClickListener {
    var onHeadChangeListener: OnHeadChangeListener? = null

    init {
        this.init()
    }

    fun init() {
        val contentView = LayoutInflater.from(context).inflate(R.layout.dialog_sdk_head_change, null)
        setContentView(contentView)
        val params = contentView.layoutParams as ViewGroup.MarginLayoutParams
        params.width = context.resources.displayMetrics.widthPixels - DensityUtil.dp2px(context, 0f)
        params.bottomMargin = DensityUtil.dp2px(context, 0f)
        contentView.layoutParams = params
        setCanceledOnTouchOutside(true)
        window?.setGravity(Gravity.BOTTOM)
        window?.setWindowAnimations(R.style.BottomDialog_Animation)
        // 设置点击事件
        contentView.findViewById<TextView>(R.id.tv_camera).setOnClickListener(this)
        contentView.findViewById<TextView>(R.id.tv_photo).setOnClickListener(this)
        contentView.findViewById<TextView>(R.id.tv_cancel).setOnClickListener(this)
    }

    override fun onClick(v: View?) {
        when (v!!.id) {
            R.id.tv_camera -> {
                onHeadChangeListener?.onCamera()
                dismiss()
            }
            R.id.tv_photo -> {
                onHeadChangeListener?.onPhoto()
                dismiss()
            }
            R.id.tv_cancel -> dismiss()
        }
    }

    interface OnHeadChangeListener {
        fun onCamera()
        fun onPhoto()
    }
}

二、调用系统相机拍照

1)动态申请权限

private void toCamera() {
	// 判断是否需要动态申请存储权限
	if (!PermissionUtil.checksCameraPermission(this)) {
		DialogManager.getInstance().showDialog(getString(R.string.camera_tip1), this, () -> {
			if (PermissionUtil.deniedPermission()) {
				// 用户已拒绝权限,跳转系统设置页
				Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
						.setData(Uri.fromParts("package", getPackageName(), null));
				intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
				startActivity(intent);
				overridePendingTransition(R.anim.slide_in_right, R.anim.slide_out_left);
			} else {
				// 用户未拒绝权限,弹框动态申请权限
				PermissionUtil.requestCameraPermission(this);
			}
		});
		return;
	}
	startCamera();
}

2)调用系统相机拍照

在设置Uri是要注意,Android7.0及以上系统禁止应用向外部公开file://URI ,因此需要FileProvider来向外界传递URI。

public void takePicture(Activity activity, int requestCode) {
	try {
		Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
		takePictureIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
		if (FileUtil.existSDCard()) {
			takeImageFile = new File(Environment.getExternalStorageDirectory(), "/DCIM/camera/");
		} else {
			takeImageFile = Environment.getDataDirectory();
		}
		takeImageFile = createFile(takeImageFile, "IMG_", ".jpg");
		if (takeImageFile != null) {
			// 默认情况下,即不需要指定intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
			// 照相机有自己默认的存储路径,拍摄的照片将返回一个缩略图。如果想访问原始图片,
			// 可以通过dat extra能够得到原始图片位置。即,如果指定了目标uri,data就没有数据,
			// 如果没有指定uri,则data就返回有数据!
			Uri uri;
			if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.M) {
				uri = Uri.fromFile(takeImageFile);
			} else {
				/**
				 * 7.0 调用系统相机拍照不再允许使用Uri方式,应该替换为FileProvider
				 * 并且这样可以解决MIUI系统上拍照返回size为0的情况
				 */
				uri = FileProvider.getUriForFile(activity, ProviderUtil.getFileProviderName(activity), takeImageFile);
				//加入uri权限 要不三星手机不能拍照
				List<ResolveInfo> resInfoList = activity.getPackageManager().queryIntentActivities(takePictureIntent, PackageManager.MATCH_DEFAULT_ONLY);
				for (ResolveInfo resolveInfo : resInfoList) {
					String packageName = resolveInfo.activityInfo.packageName;
					activity.grantUriPermission(packageName, uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
				}
			}

			LogUtil.i(TAG, ProviderUtil.getFileProviderName(activity));
			takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
		}
		activity.startActivityForResult(takePictureIntent, requestCode);
	} catch (Exception e) {
		e.printStackTrace();
	}
}

3)自定义FileProvider

FileProvider是ContentProvider的一个子类,用于应用程序之间私有文件的传递。自Android 7.0后系统禁止应用向外部公开file://URI ,因此需要FileProvider来向外界传递URI,传递的形式是content : //Uri,使用时需要在清单文件中注册。

// 自定义一个Provider,以免和引入的项目的provider冲突
public class ImagePickerProvider extends FileProvider {
}

注册清单文件

<provider
	android:name=".image.ImagePickerProvider"
	android:authorities="${applicationId}.provider"
	android:exported="false"
	android:grantUriPermissions="true">
	<meta-data
		android:name="android.support.FILE_PROVIDER_PATHS"
		android:resource="@xml/provider_paths"/>
</provider>

创建provider_paths.xml文件

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <external-path name="external_files" path="."/>
</paths>

4)获取相机拍照图片路径,裁剪图片

@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
	if (resultCode == RESULT_OK) {
		switch (requestCode) {
			case ImagePicker.REQUEST_CODE_TAKE:
				String imagePath = ImagePicker.getInstance().getTakeImageFile().getAbsolutePath();
				LogUtil.i(TAG, "startCamera imagePath " + imagePath);
				if (!TextUtils.isEmpty(imagePath)) {
					Intent intent = new Intent(mContext, CorpImgActivity.class);
					intent.putExtra(Constants.IMAGE_PATH, imagePath);
					startActivityForResult(intent, ImagePicker.REQUEST_CODE_CROP);
				}
				break;
		}
	}
	super.onActivityResult(requestCode, resultCode, data);
}

三、自定义图片裁剪页面

图片裁剪页面功能实现:

1)显示图片

private void initData() {
	Bundle bundle = getIntent().getExtras();
	if (bundle != null && bundle.containsKey(Constants.IMAGE_URI)) {
		Uri imageUri = (Uri) bundle.get(Constants.IMAGE_URI);
		if (imageUri != null) {
			try {
				// 要裁剪的图片
				mCropBitmap = BitmapFactory.decodeStream(getContentResolver().openInputStream(imageUri));
				mCorpImg.setImageBitmap(mCropBitmap);
			} catch (FileNotFoundException e) {
				e.printStackTrace();
			}
		}
	}

	if (bundle != null && bundle.containsKey(Constants.IMAGE_PATH)) {
		String imagePath = (String) bundle.get(Constants.IMAGE_PATH);
		if (imagePath != null) {
			try {
				// 要裁剪的图片
				mCropBitmap = BitmapFactory.decodeStream(new FileInputStream(imagePath));
				// 旋转图片
				int degree = BitmapUtil.getBitmapDegree(imagePath);
				LogUtil.i(TAG, "degree " + degree);
				if (degree != 0)
					mCropBitmap = BitmapUtil.rotateBitmapByDegree(mCropBitmap, degree);
				mCorpImg.setImageBitmap(mCropBitmap);
			} catch (FileNotFoundException e) {
				e.printStackTrace();
			}
		}
	}
}

2)实现拖拽图片

mCorpImg.setOnTouchListener(new View.OnTouchListener() {
	@Override
	public boolean onTouch(View view, MotionEvent motionEvent) {
		switch (motionEvent.getAction() ) {
			case MotionEvent.ACTION_DOWN:
				downX = motionEvent.getX();
				downY = motionEvent.getY();
				break;
			case MotionEvent.ACTION_MOVE:
				Matrix matrix = mCorpImg.getImageMatrix();
				//指定移动距离
				matrix.postTranslate(motionEvent.getX() - downX, motionEvent.getY() - downY);
				downX = motionEvent.getX();
				downY = motionEvent.getY();
				break;
			case MotionEvent.ACTION_UP:
				break;
		}
		mCorpImg.invalidate();
		return true;
	}
});

3)放大缩小图片

mCorpImg.setOnTouchListener(new View.OnTouchListener() {
	@Override
	public boolean onTouch(View view, MotionEvent motionEvent) {
		switch (motionEvent.getAction() & motionEvent.getActionMasked()) {
			case MotionEvent.ACTION_POINTER_DOWN:
				//次要点被点击
				oldSpacing = spacing(motionEvent);
				if (oldSpacing > 10f) {
					isPointer = false;
					isMain = false;
					state = ZOOM;
					midPoint(mid, motionEvent);
				}
				break;
			case MotionEvent.ACTION_POINTER_UP:
				//次要点松开
				isPointer = true;
				if (isPointer && isMain) {
					isPointer = false;
					isMain = false;
					state = DRAG;
					oldSpacing = 1f;
				}
				break;
			case MotionEvent.ACTION_DOWN:
				downX = motionEvent.getX();
				downY = motionEvent.getY();
				break;
			case MotionEvent.ACTION_MOVE:
				Matrix matrix = mCorpImg.getImageMatrix();
				if (state == DRAG) {
					//拖拽
					matrix.postTranslate(motionEvent.getX() - downX, motionEvent.getY() - downY);
					downX = motionEvent.getX();
					downY = motionEvent.getY();
				} else if (state == ZOOM && !isPointer && !isMain) {
					//放大缩小
					float newSpacing = spacing(motionEvent);
					float scale = newSpacing / oldSpacing;
					matrix.postScale(scale, scale, mid.x, mid.y);
					oldSpacing = newSpacing;
				}
				break;
			case MotionEvent.ACTION_UP:
				//用户多点触碰释放时,需要同时释放所有点才初始,防止发生偏移。
				isMain = true;
				if (isPointer && isMain) {
					isPointer = false;
					isMain = false;
					state = DRAG;
					oldSpacing = 1f;
				}
				break;
		}
		mCorpImg.invalidate();
		return true;
	}
});


/**
 * 多点触控时,计算最先放下的两指距离
 *
 * @param event
 * @return
 */
private float spacing(MotionEvent event) {
	float x = event.getX(0) - event.getX(1);
	float y = event.getY(0) - event.getY(1);
	return (float) Math.sqrt(x * x + y * y);
}

/**
 * 多点触控时,计算最先放下的两指中心坐标
 *
 * @param point
 * @param event
 */
private void midPoint(PointF point, MotionEvent event) {
	float x = event.getX(0) + event.getX(1);
	float y = event.getY(0) + event.getY(1);
	point.set(x / 2, y / 2);
}

4)保存裁剪框区域图片

@Override
public void onRightTopMenuClick() {
	super.onRightTopMenuClick();
	Bitmap bitmap = getBitmap();
	File file = FileUtil.createImageFile();
	file = BitmapUtil.compressImage(bitmap, file);
	mCropBitmap.recycle();
	mCropBitmap = null;

	Intent intent = new Intent();
	intent.putExtra(Constants.IMAGE_PATH, file.getPath());
	setResult(RESULT_OK, intent);
	finish();
}

/**
 * 获取裁剪框内截图
 *
 * @return
 */
private Bitmap getBitmap() {
	// 获取截屏
	mCropLayout.setDrawingCacheEnabled(true);
	mCropLayout.buildDrawingCache();
	// 计算裁剪框的开始位置
	left = (mCropLayout.getWidth() - mCropView.getWidth()) / 2;
	top = (mCropLayout.getHeight() - mCropView.getHeight()) / 2;
	// 裁剪框的边框宽度
	int borderWidth = DensityUtil.dp2px(getContext(), 2.5f);
	Bitmap finalBitmap = Bitmap.createBitmap(mCropLayout.getDrawingCache(),
			left + borderWidth, top + borderWidth, mCropView.getWidth() - 2 * borderWidth,
			mCropView.getHeight() - 2 * borderWidth);

	// 释放资源
	mCropLayout.destroyDrawingCache();
	return finalBitmap;
}

四、从系统相册选择图片

1)动态申请权限

private void toChooseImg() {
	// 判断是否需要动态申请存储权限
	if (!PermissionUtil.checksStoragePermission(this)) {
		DialogManager.getInstance().showDialog(getString(R.string.storage_tip1), this, () -> {
			if (PermissionUtil.deniedStoragePermission()) {
				// 用户已拒绝权限,跳转系统设置页
				Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
						.setData(Uri.fromParts("package", getPackageName(), null));
				intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
				startActivity(intent);
				overridePendingTransition(R.anim.slide_in_right, R.anim.slide_out_left);
			} else {
				// 用户未拒绝权限,弹框动态申请权限
				PermissionUtil.requestStoragePermission(this);
			}
		});
		return;
	}
	choosePhoto();
}

2)打开系统相册

public void photoAlbum(Activity activity, int requestCode) {
	Intent intentToPickPic = new Intent(Intent.ACTION_PICK, null);
	// 单个图片类型:"image/jpeg 、 image/png等的类型",所有类型:"image/*"
	intentToPickPic.setDataAndType(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, "image/*");
	activity.startActivityForResult(intentToPickPic, requestCode);
}

3)获取获取手机相册图片Uri,裁剪图片

@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
	if (resultCode == RESULT_OK) {
		switch (requestCode) {
			case ImagePicker.REQUEST_CODE_PICK:
				// 获取图片
				try {
					//该uri是上一个Activity返回的
					Uri imageUri = data.getData();
					LogUtil.i(TAG, "choosePhoto imageUri " + imageUri);
					if (imageUri != null) {
						Intent intent = new Intent(mContext, CorpImgActivity.class);
						intent.putExtra(Constants.IMAGE_URI, imageUri);
						startActivityForResult(intent, ImagePicker.REQUEST_CODE_CROP);
					}
				} catch (Exception e) {
					e.printStackTrace();
				}
				break;
		}
	}
	super.onActivityResult(requestCode, resultCode, data);
}

功能完成,测试手机Android6.0、7.0及以上系统版本的华为、小米、三星、谷歌手机暂未发现问题。

源码:

图片选择工具类

package com.lib.common.image;

import android.app.Activity;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.provider.MediaStore;

import androidx.core.content.FileProvider;

import com.lib.common.util.FileUtil;
import com.lib.common.util.LogUtil;

import java.io.File;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.text.SimpleDateFormat;

/**
 * @Author: Jin
 * @Description: 图片选择工具
 * @CreateDate: 2023/4/20  14:50
 */
public class ImagePicker {
    private static final String TAG = "ImagePicker";
    public static final int REQUEST_CODE_TAKE = 1001; // 打开照相机
    public static final int REQUEST_CODE_CROP = 1002; // 图片裁剪
    public static final int REQUEST_CODE_PICK = 1003; // 打开相册

    private File takeImageFile;

    private static ImagePicker mInstance;

    private ImagePicker() {
    }

    public static ImagePicker getInstance() {
        if (mInstance == null) {
            synchronized (ImagePicker.class) {
                if (mInstance == null) {
                    mInstance = new ImagePicker();
                }
            }
        }
        return mInstance;
    }

    public File getTakeImageFile() {
        return takeImageFile;
    }

    /**
     * 拍照的方法
     */
    public void takePicture(Activity activity, int requestCode) {
        try {
            Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
            takePictureIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
            if (FileUtil.existSDCard()) {
                takeImageFile = new File(Environment.getExternalStorageDirectory(), "/DCIM/camera/");
            } else {
                takeImageFile = Environment.getDataDirectory();
            }
            takeImageFile = createFile(takeImageFile, "IMG_", ".jpg");
            if (takeImageFile != null) {
                // 默认情况下,即不需要指定intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
                // 照相机有自己默认的存储路径,拍摄的照片将返回一个缩略图。如果想访问原始图片,
                // 可以通过dat extra能够得到原始图片位置。即,如果指定了目标uri,data就没有数据,
                // 如果没有指定uri,则data就返回有数据!
                Uri uri;
                if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.M) {
                    uri = Uri.fromFile(takeImageFile);
                } else {
                    /**
                     * 7.0 调用系统相机拍照不再允许使用Uri方式,应该替换为FileProvider
                     * 并且这样可以解决MIUI系统上拍照返回size为0的情况
                     */
                    uri = FileProvider.getUriForFile(activity, ProviderUtil.getFileProviderName(activity), takeImageFile);
                    //加入uri权限 要不三星手机不能拍照
                    List<ResolveInfo> resInfoList = activity.getPackageManager().queryIntentActivities(takePictureIntent, PackageManager.MATCH_DEFAULT_ONLY);
                    for (ResolveInfo resolveInfo : resInfoList) {
                        String packageName = resolveInfo.activityInfo.packageName;
                        activity.grantUriPermission(packageName, uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
                    }
                }

                LogUtil.i(TAG, ProviderUtil.getFileProviderName(activity));
                takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
            }
            activity.startActivityForResult(takePictureIntent, requestCode);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 根据系统时间、前缀、后缀产生一个文件
     */
    public static File createFile(File folder, String prefix, String suffix) {
        if (!folder.exists() || !folder.isDirectory()) folder.mkdirs();
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.CHINA);
        String filename = prefix + dateFormat.format(new Date(System.currentTimeMillis())) + suffix;
        return new File(folder, filename);
    }

    /**
     * 打开相册
     */
    public void photoAlbum(Activity activity, int requestCode) {
        Intent intentToPickPic = new Intent(Intent.ACTION_PICK, null);
        // 单个图片类型:"image/jpeg 、 image/png等的类型",所有类型:"image/*"
        intentToPickPic.setDataAndType(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, "image/*");
        activity.startActivityForResult(intentToPickPic, requestCode);
    }
}

图片裁剪页面

package com.lib.common.image;

import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Matrix;
import android.graphics.PointF;
import android.net.Uri;
import android.os.Bundle;
import android.view.MotionEvent;
import android.view.View;
import android.widget.ImageView;
import android.widget.RelativeLayout;

import com.lib.common.R;
import com.lib.common.constant.Constants;
import com.lib.common.ui.base.BaseActivity;
import com.lib.common.util.BitmapUtil;
import com.lib.common.util.DensityUtil;
import com.lib.common.util.FileUtil;
import com.lib.common.util.LogUtil;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;

public class CorpImgActivity extends BaseActivity implements View.OnTouchListener {
    private static final String TAG = "CorpImgActivity";
    private View mCropView;
    private ImageView mCorpImg;
    private RelativeLayout mCropLayout;
    private Bitmap mCropBitmap;

    private int top, left;
    private float downX, downY;
    private final int ZOOM = 1, DRAG = 0;
    private float oldSpacing = 1f;
    private int state;

    /**
     * 记录缩放时两指中间点坐标
     */
    private PointF mid = new PointF();
    private boolean isMain, isPointer;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setActivityTitle(getString(R.string.crop_img));
        setRightTopMenuShow(true);
        setRightTopTitle(getString(R.string.save));
        initView();
        initData();
    }

    @Override
    public int setLayoutId() {
        return R.layout.activity_corp_img;
    }

    private void initView() {
        mCropView = findViewById(R.id.crop_handler_select);
        mCorpImg = findViewById(R.id.crop_handler_img);
        mCropLayout = findViewById(R.id.crop_handler_layout);
        //添加缩放功能
        mCorpImg.setOnTouchListener(this);
    }

    private void initData() {
        Bundle bundle = getIntent().getExtras();
        if (bundle != null && bundle.containsKey(Constants.IMAGE_URI)) {
            Uri imageUri = (Uri) bundle.get(Constants.IMAGE_URI);
            if (imageUri != null) {
                try {
                    // 要裁剪的图片
                    mCropBitmap = BitmapFactory.decodeStream(getContentResolver().openInputStream(imageUri));
                    mCorpImg.setImageBitmap(mCropBitmap);
                } catch (FileNotFoundException e) {
                    e.printStackTrace();
                }
            }
        }

        if (bundle != null && bundle.containsKey(Constants.IMAGE_PATH)) {
            String imagePath = (String) bundle.get(Constants.IMAGE_PATH);
            if (imagePath != null) {
                try {
                    // 要裁剪的图片
                    mCropBitmap = BitmapFactory.decodeStream(new FileInputStream(imagePath));
                    // 旋转图片
                    int degree = BitmapUtil.getBitmapDegree(imagePath);
                    LogUtil.i(TAG, "degree " + degree);
                    if (degree != 0)
                        mCropBitmap = BitmapUtil.rotateBitmapByDegree(mCropBitmap, degree);
                    mCorpImg.setImageBitmap(mCropBitmap);
                } catch (FileNotFoundException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    @Override
    public boolean onTouch(View view, MotionEvent motionEvent) {
        switch (motionEvent.getAction() & motionEvent.getActionMasked()) {
            case MotionEvent.ACTION_POINTER_DOWN:
                // 次要点被点击
                oldSpacing = spacing(motionEvent);
                if (oldSpacing > 10f) {
                    isPointer = false;
                    isMain = false;
                    state = ZOOM;
                    midPoint(mid, motionEvent);
                }
                break;
            case MotionEvent.ACTION_POINTER_UP:
                // 次要点松开
                isPointer = true;
                if (isPointer && isMain) {
                    isPointer = false;
                    isMain = false;
                    state = DRAG;
                    oldSpacing = 1f;
                }
                break;
            case MotionEvent.ACTION_DOWN:
                downX = motionEvent.getX();
                downY = motionEvent.getY();
                break;
            case MotionEvent.ACTION_MOVE:
                Matrix matrix = mCorpImg.getImageMatrix();
                if (state == DRAG) {
                    // 拖拽
                    matrix.postTranslate(motionEvent.getX() - downX, motionEvent.getY() - downY);
                    downX = motionEvent.getX();
                    downY = motionEvent.getY();
                } else if (state == ZOOM && !isPointer && !isMain) {
                    // 放大缩小
                    float newSpacing = spacing(motionEvent);
                    float scale = newSpacing / oldSpacing;
                    matrix.postScale(scale, scale, mid.x, mid.y);
                    oldSpacing = newSpacing;
                }
                break;
            case MotionEvent.ACTION_UP:
                // 用户多点触碰释放时,需要同时释放所有点才初始,防止发生偏移。
                isMain = true;
                if (isPointer && isMain) {
                    isPointer = false;
                    isMain = false;
                    state = DRAG;
                    oldSpacing = 1f;
                }
                break;
        }
        mCorpImg.invalidate();
        return true;
    }

    /**
     * 多点触控时,计算最先放下的两指距离
     *
     * @param event
     * @return
     */
    private float spacing(MotionEvent event) {
        float x = event.getX(0) - event.getX(1);
        float y = event.getY(0) - event.getY(1);
        return (float) Math.sqrt(x * x + y * y);
    }

    /**
     * 多点触控时,计算最先放下的两指中心坐标
     *
     * @param point
     * @param event
     */
    private void midPoint(PointF point, MotionEvent event) {
        float x = event.getX(0) + event.getX(1);
        float y = event.getY(0) + event.getY(1);
        point.set(x / 2, y / 2);
    }

    @Override
    public void onWindowFocusChanged(boolean hasFocus) {
        super.onWindowFocusChanged(hasFocus);
        if (hasFocus) {
            int layoutWidth = mCropLayout.getWidth();
            int layoutHeight = mCropLayout.getHeight();
            int imgWidth = mCropBitmap.getWidth();
            int imgHeight = mCropBitmap.getHeight();
            int selectWidth = mCropView.getWidth();
            int selectHeight = mCropView.getHeight();

            float scaleNum; // 缩放比例
            // 将要裁剪的图片长宽高做对比, 将较小的一方做等比缩放成裁剪框大小
            if (imgWidth < imgHeight) {
                scaleNum = (selectWidth * 1.0f) / (imgWidth * 1.0f);
                imgHeight = (int) (scaleNum * imgHeight);
                imgWidth = selectWidth;
            } else {
                scaleNum = (selectHeight * 1.0f) / (imgHeight * 1.0f);
                imgWidth = (int) (scaleNum * imgWidth);
                imgHeight = selectHeight;
            }

            Matrix matrix = new Matrix();
            matrix.postScale(scaleNum, scaleNum);
            // 平移距离
            matrix.postTranslate((layoutWidth - imgWidth) / 2, (layoutHeight - imgHeight) / 2);
            // 设置缩放类型为 矩阵
            mCorpImg.setScaleType(ImageView.ScaleType.MATRIX);
            mCorpImg.setImageMatrix(matrix);
            mCorpImg.setImageBitmap(mCropBitmap);
        }
    }

    @Override
    public void onRightTopMenuClick() {
        super.onRightTopMenuClick();
        Bitmap bitmap = getBitmap();
        File file = FileUtil.createImageFile();
        file = BitmapUtil.compressImage(bitmap, file);
        mCropBitmap.recycle();
        mCropBitmap = null;

        Intent intent = new Intent();
        intent.putExtra(Constants.IMAGE_PATH, file.getPath());
        setResult(RESULT_OK, intent);
        finish();
    }

    /**
     * 获取裁剪框内截图
     *
     * @return
     */
    private Bitmap getBitmap() {
        // 获取截屏
        mCropLayout.setDrawingCacheEnabled(true);
        mCropLayout.buildDrawingCache();
        // 计算裁剪框的开始位置
        left = (mCropLayout.getWidth() - mCropView.getWidth()) / 2;
        top = (mCropLayout.getHeight() - mCropView.getHeight()) / 2;
        // 裁剪框的边框宽度
        int borderWidth = DensityUtil.dp2px(getContext(), 2.5f);
        Bitmap finalBitmap = Bitmap.createBitmap(mCropLayout.getDrawingCache(),
                left + borderWidth, top + borderWidth, mCropView.getWidth() - 2 * borderWidth,
                mCropView.getHeight() - 2 * borderWidth);

        // 释放资源
        mCropLayout.destroyDrawingCache();
        return finalBitmap;
    }
}

裁剪页面布局

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/black"
    android:orientation="vertical">

    <RelativeLayout
        android:id="@+id/crop_handler_layout"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <ImageView
            android:id="@+id/crop_handler_img"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />

        <View
            android:id="@+id/crop_handler_select"
            android:layout_width="200dp"
            android:layout_height="200dp"
            android:layout_centerInParent="true"
            android:background="@drawable/bg_crop" />
    </RelativeLayout>
</LinearLayout>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/446022.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

centos7 配置LNMP环境

文章目录 LNMP环境的搭建LNMP工作流程FastCGI接口配置LNMP部署环境配置环境测试安装 Discuz LNMP环境的搭建 随着我们 Nginx web 服务器的流行&#xff0c;又出现了我们叫做 LNMP 的一种新的 web 环境服务组合。LNMP 就是 Linux Nginx Mysql PHP 等首字母的缩写。现在&…

STL学习+acwing 67 数字在排序数组中出现的次数

题目链接 67. 数字在排序数组中出现的次数 传统暴力解法 class Solution { public:int getNumberOfK(vector<int>& nums , int k) {int c0;for(int i0;i<nums.size();i){if(nums[i]k)c;}return c;} };容器的应用 set和multiset两个容器有一个count函数 set 为…

eBPF 虚拟机是如何工作的?

eBPF 是一个运行在内核中的虚拟机&#xff0c;很多人在初次接触它时&#xff0c;会把它跟系统虚拟化&#xff08;比如 kvm&#xff09;中的虚拟机弄混。其实&#xff0c;虽然都被称为“虚拟机”&#xff0c;系统虚拟化和 eBPF 虚拟机还是有着本质不同的。 系统虚拟化基于 x86 …

vue 中的事件修饰符介绍+示例说明

vue 中的事件修饰符介绍示例说明 Start 最近使用到 vue 的事件修饰符&#xff0c;发现由于时间太过久远&#xff0c;今天快速的过一下 vue 中的事件修饰符. 1. 官方文档 vue2 v-on 点击这里 vue3 v-on 点击这里 vue2中有关 v-on 的介绍 vue3中有关 v-on 的介绍 初步看下…

Ribbon负载均衡·入门·壹

文章目录 1 Ribbon概述1.1 什么是Ribbon1.2 Ribbon解决的问题1.3 什么是负载均衡 2 SpringCloud与Ribbon2.1 集成Ribbong工具类2.2 单独引入Ribbon 3 Ribbon实现负载均衡源码跟踪3.1 打开LoadBalanced3.2 发现Qualifier3.3 LoadBalancerAutoConfiguration自动装配类 1 Ribbon概…

C++入门(后篇)

&#x1f525;&#x1f525;本章重内容 C入门 1.auto关键字(C11)auto的使用细则auto不能使用的场景 2.基于范围的for循环(C11)3.指针空值nullptr(C11) 1.auto关键字(C11) 在早期C/C中auto的含义是&#xff1a;使用auto修饰的变量&#xff0c;是具有自动存储器的局部变量&#…

深入了解 Transformers – Part 1: 介绍 Transformer 模型

动动发财的小手&#xff0c;点个赞吧&#xff01; 自从最新的Large Language Models&#xff08;LLaM&#xff09;发布以来&#xff0c;如OpenAI的GPT系列、开源模型Bloom或谷歌发布的LaMDA等&#xff0c;Transformer展现出了巨大的潜力&#xff0c;成为了深度学习的前沿架构楷…

easyexcel内容追加与单元格合并

这里的需求是&#xff0c;如果表格不存在&#xff0c;则新建表格&#xff0c;并填入数据&#xff0c;如果表格存在&#xff0c;那么就追加内容&#xff0c;并且支持单元格合并。 内容追加&#xff0c;需要分两种方式插入&#xff0c;第一种就是没有表格&#xff0c;需要生成表头…

内网渗透之横向移动ipc

0x00 内网横向移动介绍 内网横向移动是什么 在内网渗透中&#xff0c;当攻击者获取到内网某台机器的控制权后&#xff0c;会以被攻陷的主机为跳板&#xff0c;通过收集域内凭证等各种方法&#xff0c;访问域内其他机器&#xff0c;进一步扩大资产范围。通过此类手段&#xff0…

SpringCloud_Eureka服务的注册与发现

文章目录 一、微服务的理论1、微服务和分布式的区别2、微服务的拆分规范和原则 二、微服务的注册与发现(Eureka)1、Spring Cloud Eureka的概念2、构建聚合父工程3、搭建Eureka服务4、搭建Eureka服务的提供者5、创建Eureka服务的消费者 三、Eureka的其他功能1、服务的剔除和自保…

DC-6靶机通关详解

信息收集 漏洞发现 发现无法访问web 加个hosts 这题类似那个dc2还是dc3,网站长的一样 wordPress5.1.1 上wpscan扫 enumrate一下user 看看能不能弱口令 测了wp给的那几个用户,都不能弱口令,dirsearch也没扫到什么有价值的路径 尝试ssh弱口令 没爆出来,回官网看了下描述 确实…

基于药效团的药物设计(Pharmacophore Construction)

基于药效团的药物设计&#xff08;Pharmacophore Construction&#xff09; 药效团模型不仅仅利用分子拓扑学相似性而且利用了基团的功能相似性&#xff0c;从而运用了生物电子等排体&#xff08;bioisosterism&#xff09;的概念使得模型更加可靠。基于药效团的虚拟筛选的方法…

华为OD机试真题(Java),最小步骤数(100%通过+复盘思路)

一、题目描述 一个正整数数组 设为nums&#xff0c;最大为100个成员&#xff0c;求从第一个成员开始正好走到数组最后一个成员所使用的最小步骤数。 要求&#xff1a; 第一步 必须从第一元素起 且 1<第一步步长<len/2 (len为数组长度)&#xff1b;从第二步开始只能以所…

Algo C++:课程介绍

目录 课程介绍前言1. 课程特色2. 课程前言3. 具备条件4. 预期的收获 课程介绍 前言 手写AI推出的全新面向AI算法的C课程 Algo C&#xff0c;链接。记录下个人学习笔记&#xff0c;仅供自己参考。 本次课程主要是课程介绍 课程大纲可看下面的思维导图 1. 课程特色 案例驱动 讲解…

Kubernetes(k8s)

Kubernetes(k8s) 前言 ​ 在学习过程中&#xff0c;我们经常会遇到遗忘的问题。为了避免忘记&#xff0c;多复习是非常必要的。复习可以帮助我们巩固知识&#xff0c;加深记忆&#xff0c;提高学习效率。因此&#xff0c;我们应该养成良好的复习习惯&#xff0c;定期回顾所学…

ai免费写作在线平台-ai免费伪原创文章生成器软件

ai伪原创能检测出来吗 人工智能技术可以检测伪原创&#xff0c;但是不是所有的伪原创都可以被检测出来。 现在有许多自然语言处理&#xff08;NLP&#xff09;算法和技术可以用来检测伪原创内容&#xff0c;例如文本相似度比较算法&#xff0c;语气分析算法等。这些算法可以检…

iptables移植+内核修改

iptables移植&#xff0c;交叉编译后的文件&#xff0c;如果交叉编译工具一致可以直接使用-嵌入式文档类资源-CSDN文库 移植iptables过程中出现一些问题&#xff0c;这里记录一下 Iptables是用户态提供的更改过滤规则的便捷工具&#xff0c;通过使用这个工具&#xff0c;可以…

openEuler-linux下部署zabbix-超级详细

一、准备工作 下载&#xff1a;zabbix包 地址&#xff1a;下载Zabbix 准备2台openEuler-linux虚拟机&#xff1a; linux-1&#xff1a;当服务器端 IP地址&#xff1a;192.168.100.100 修改hosts文件 [rootzbx ~]# vim /etc/hosts 192.168.100.100 zbx.xx.cn linux-2&…

Spring Boot Web请求响应

在上一讲&#xff0c;学习了Spring Boot Web的快速入门以及Web开发的基础知识&#xff0c;包括HTTP协议以及Web服务器Tomcat等内容。基于SpringBoot的方式开发一个web应用&#xff0c;浏览器发起请求 /hello 后 &#xff0c;给浏览器返回字符串 “Hello World ~”。运行启动类启…

【Spring篇】IOC相关内容

&#x1f353;系列专栏:Spring系列专栏 &#x1f349;个人主页:个人主页 目录 一、bean基础配置 1.bean基础配置(id与class) 2.bean的name属性 3.bean作用范围scope配置 二、bean实例化 1.构造方法实例化 2.分析Spring的错误信息 3.静态工厂实例化 4.实例工厂 5.FactoryBean 三…