文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

Android 以任意比例裁剪图片代码分享

2022-06-06 06:56

关注

公司的一个小伙伴写的,可以按照任意比例裁剪图片。我觉得挺好用的。简单在这里记录一下,以后肯定还会用到。


public class SeniorCropImageView extends ImageView implements ScaleGestureDetector.OnScaleGestureListener, 
View.OnLayoutChangeListener { 
 
private static final int LINE_COLOR = Color.WHITE; 
private static final int OUTER_MASK_COLOR = Color.argb(191, 0, 0, 0); 
private static final int LINE_WIDTH_IN_DP = 1; 
private final float[] mMatrixValues = new float[9]; 
protected Matrix mSupportMatrix; 
protected ScaleGestureDetector mScaleGestureDetector; 
 
protected Paint mPaint; 
 
protected float mRatio = 1.0f; 
protected RectF mCropRect; 
//RectFPadding是适应产品需求,给裁剪框mCropRect设置一下padding -- chenglin 2016年04月18日 
protected float RectFPadding = 0; 
protected int mLastX; 
protected int mLastY; 
protected OPERATION mOperation; 
private onBitmapLoadListener iBitmapLoading = null; 
private boolean mEnableDrawCropWidget = true; 
 
private Matrix mBaseMatrix; 
private Matrix mDrawMatrix; 
private AccelerateDecelerateInterpolator sInterpolator = new AccelerateDecelerateInterpolator(); 
private Path mPath; 
private int mLineWidth; 
private float mScaleMax = 3.0f; 
private RectF mBoundaryRect; 
private int mRotation = 0; 
private int mImageWidth; 
private int mImageHeight; 
private int mDisplayW; 
private int mDisplayH; 
public SeniorCropImageView(Context context) { 
this(context, null); 
} 
public SeniorCropImageView(Context context, AttributeSet attrs) { 
this(context, attrs, 0); 
} 
public SeniorCropImageView(Context context, AttributeSet attrs, int defStyleAttr) { 
super(context, attrs, defStyleAttr); 
if (attrs != null) { 
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.Life_CropImage); 
mRatio = a.getFloat(R.styleable.Life_CropImage_life_Crop_ratio, 1.0f); 
a.recycle(); 
} 
init(); 
} 
public static void decodeImageForCropping(final String path, final IDecodeCallback callback) { 
new Thread(new Runnable() { 
@Override 
public void run() { 
int rotation = 0; 
// 读取一下exif中的rotation 
try { 
ExifInterface exif = new ExifInterface(path); 
final int rotate = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_UNDEFINED); 
switch (rotate) { 
case ExifInterface.ORIENTATION_ROTATE_90: 
rotation = 90; 
break; 
case ExifInterface.ORIENTATION_ROTATE_180: 
rotation = 180; 
break; 
case ExifInterface.ORIENTATION_ROTATE_270: 
rotation = 270; 
break; 
} 
} catch (IOException e) { 
e.printStackTrace(); 
} 
final BitmapFactory.Options options = new BitmapFactory.Options(); 
options.inJustDecodeBounds = true; 
BitmapFactory.decodeFile(path, options); 
final int textureLimit = getMaxTextureSize(); 
int scale = 1; 
while (options.outWidth / scale >= textureLimit) { 
scale *= 2; 
} 
while (options.outHeight / scale >= textureLimit) { 
scale *= 2; 
} 
options.inSampleSize = scale; 
options.inJustDecodeBounds = false; 
Bitmap bitmap = null; 
try { 
bitmap = BitmapFactory.decodeFile(path, options); 
} catch (OutOfMemoryError e) { 
e.printStackTrace(); 
} 
final Bitmap bimapDecoded = bitmap; 
if (bimapDecoded == null) { 
return; 
} 
if (callback != null) { 
callback.onDecoded(rotation, bimapDecoded); 
} 
} 
}).start(); 
} 
private static int getMaxTextureSize() { 
EGL10 egl = (EGL10) EGLContext.getEGL(); 
EGLDisplay display = egl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY); 
// Initialise 
int[] version = new int[2]; 
egl.eglInitialize(display, version); 
// Query total number of configurations 
int[] totalConfigurations = new int[1]; 
egl.eglGetConfigs(display, null, 0, totalConfigurations); 
// Query actual list configurations 
EGLConfig[] configurationsList = new EGLConfig[totalConfigurations[0]]; 
egl.eglGetConfigs(display, configurationsList, totalConfigurations[0], totalConfigurations); 
int[] textureSize = new int[1]; 
int maximumTextureSize = 0; 
// Iterate through all the configurations to located the maximum texture size 
for (int i = 0; i < totalConfigurations[0]; i++) { 
// Only need to check for width since opengl textures are always squared 
egl.eglGetConfigAttrib(display, configurationsList[i], EGL10.EGL_MAX_PBUFFER_WIDTH, textureSize); 
// Keep track of the maximum texture size 
if (maximumTextureSize < textureSize[0]) { 
maximumTextureSize = textureSize[0]; 
} 
} 
// Release 
egl.eglTerminate(display); 
return maximumTextureSize; 
} 
@Override 
public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) { 
mDisplayW = right - left; 
mDisplayH = bottom - top; 
if (getDrawable() != null && ((BitmapDrawable) getDrawable()).getBitmap() != null) { 
calculateProperties(((BitmapDrawable) getDrawable()).getBitmap()); 
} 
} 
private void init() { 
mScaleGestureDetector = new ScaleGestureDetector(getContext(), this); 
mBaseMatrix = new Matrix(); 
mDrawMatrix = new Matrix(); 
mSupportMatrix = new Matrix(); 
mLineWidth = (int) dipToPixels(LINE_WIDTH_IN_DP); 
mPaint = new Paint(); 
// 表示第一个实线段长dashOnWidth,第一个虚线段长dashOffWidth 
mPath = new Path(); 
mCropRect = new RectF(); 
mBoundaryRect = new RectF(); 
setScaleType(ScaleType.MATRIX); 
setClickable(true); 
} 
private float dipToPixels(float dip) { 
return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dip, 
getResources().getDisplayMetrics()); 
} 
@Override 
protected void onAttachedToWindow() { 
super.onAttachedToWindow(); 
addOnLayoutChangeListener(this); 
} 
@Override 
protected void onDetachedFromWindow() { 
super.onDetachedFromWindow(); 
removeOnLayoutChangeListener(this); 
} 
 
public void setCropRatio(final float ratio) { 
if (mRatio == ratio) { 
return; 
} 
mRatio = ratio; 
//重新选择比例后,恢复旋转角度 
//setImageRotation(0); 
if (getDrawable() == null) { 
return; 
} 
calculateProperties(((BitmapDrawable) getDrawable()).getBitmap()); 
postInvalidate(); 
} 
public void setImageRotation(int rotation) { 
if (mRotation == rotation) { 
return; 
} 
mRotation = rotation; 
if (getDrawable() == null) { 
return; 
} 
calculateProperties(((BitmapDrawable) getDrawable()).getBitmap()); 
postInvalidate(); 
} 
public void setCropRectPadding(float padding) { 
RectFPadding = padding; 
} 
public void setImagePath(final String path) { 
if (TextUtils.isEmpty(path)) { 
return; 
} 
if (iBitmapLoading != null) { 
iBitmapLoading.onLoadPrepare(); 
} 
decodeImageForCropping(path, new IDecodeCallback() { 
@Override 
public void onDecoded(final int rotation, final Bitmap bitmap) { 
post(new Runnable() { 
@Override 
public void run() { 
mRotation = rotation; 
setImageBitmap(bitmap); 
if (iBitmapLoading != null) { 
iBitmapLoading.onLoadFinish(); 
} 
} 
}); 
} 
}); 
} 
@Override 
public void setImageBitmap(Bitmap bm) { 
calculateProperties(bm); 
super.setImageBitmap(bm); 
} 
public void setBitmapLoadingListener(onBitmapLoadListener iBitmapLoad) { 
iBitmapLoading = iBitmapLoad; 
} 
protected void calculateProperties(Bitmap bm) { 
mSupportMatrix.reset(); 
mBaseMatrix.reset(); 
int widthSize = mDisplayW; 
int heightSize = mDisplayH; 
generateCropRect(widthSize, heightSize); 
mImageWidth = bm.getWidth(); 
mImageHeight = bm.getHeight(); 
final boolean rotated = isImageRotated(); 
final int bitmapWidth = rotated ? mImageHeight : mImageWidth; 
final int bitmapHeight = rotated ? mImageWidth : mImageHeight; 
mBoundaryRect.set(0, 0, bitmapWidth, bitmapHeight); 
final float widthScale = mCropRect.width() / bitmapWidth; 
final float heightScale = mCropRect.height() / bitmapHeight; 
final float scale = Math.max(widthScale, heightScale); 
final float scaledHeight = scale * bitmapHeight; 
final float scaledWidth = scale * bitmapWidth; 
// 移动到中心点 
final int translateX = (int) (mCropRect.left + mCropRect.width() / 2 - scaledWidth / 2); 
final int translateY = (int) (mCropRect.top + mCropRect.height() / 2 - scaledHeight / 2); 
mBaseMatrix.setScale(scale, scale); 
mBaseMatrix.postTranslate(translateX, translateY); 
mBaseMatrix.mapRect(mBoundaryRect); 
setImageMatrix(getDrawMatrix()); 
} 
private boolean isImageRotated() { 
return ((mRotation % 360) == 90) || ((mRotation % 360) == 270); 
} 
private void generateCropRect(int boundaryWidth, int boundaryHeight) { 
//RectFPadding是适应产品需求,给裁剪框mCropRect设置一下padding -- chenglin 2016年04月18日 
boundaryWidth = boundaryWidth - (int)(RectFPadding * 2); 
boundaryHeight = boundaryHeight - (int)(RectFPadding * 2); 
int left; 
int top; 
int right; 
int bottom; 
boolean vertical; 
// 宽/高 大于比例的话,说明裁剪框是“竖直”的 
vertical = (float) boundaryWidth / boundaryHeight > mRatio; 
final int rectH = (int) (boundaryWidth / mRatio); 
final int rectW = (int) (boundaryHeight * mRatio); 
if (vertical) { 
left = (boundaryWidth - rectW) / 2; 
top = 0; 
right = (boundaryWidth + rectW) / 2; 
bottom = boundaryHeight; 
} else { 
left = 0; 
top = (boundaryHeight - rectH) / 2; 
right = boundaryWidth; 
bottom = (boundaryHeight + rectH) / 2; 
} 
//RectFPadding是适应产品需求,给裁剪框mCropRect设置一下padding -- chenglin 2016年04月18日 
mCropRect.set(left + RectFPadding, top + RectFPadding, right + RectFPadding, bottom + RectFPadding); 
} 
@Override 
protected void onDraw(Canvas canvas) { 
super.onDraw(canvas); 
if (!mEnableDrawCropWidget) { 
return; 
} 
if (getDrawable() == null) { 
return; 
} 
mPaint.reset(); 
mPaint.setAntiAlias(true); 
mPaint.setColor(LINE_COLOR); 
mPaint.setStrokeWidth(mLineWidth); 
mPaint.setStyle(Paint.Style.STROKE); 
mPath.reset(); 
// 上 
mPath.moveTo(mCropRect.left, mCropRect.top); 
mPath.lineTo(mCropRect.right, mCropRect.top); 
// 左 
mPath.moveTo(mCropRect.left, mCropRect.top); 
mPath.lineTo(mCropRect.left, mCropRect.bottom); 
// 右 
mPath.moveTo(mCropRect.right, mCropRect.top); 
mPath.lineTo(mCropRect.right, mCropRect.bottom); 
// 下 
mPath.moveTo(mCropRect.right, mCropRect.bottom); 
mPath.lineTo(mCropRect.left, mCropRect.bottom); 
canvas.drawPath(mPath, mPaint); 
// 绘制外部阴影部分 
mPaint.reset(); 
mPaint.setAntiAlias(true); 
mPaint.setColor(Color.parseColor("#B3333333")); 
mPaint.setStyle(Paint.Style.FILL); 
//下面的四个矩形是装饰性的,就是裁剪框四周的四个阴影 
final int lineOffset = mLineWidth; 
if (mCropRect.top > 0) { 
canvas.drawRect(0, 0, getMeasuredWidth(), mCropRect.top - lineOffset, mPaint); 
} 
if (mCropRect.left > 0) { 
canvas.drawRect(mCropRect.top - lineOffset - RectFPadding, RectFPadding - lineOffset, mCropRect.left - lineOffset, mCropRect.bottom + lineOffset, mPaint); 
} 
if (mCropRect.right < getMeasuredWidth()) { 
canvas.drawRect(mCropRect.right + lineOffset, mCropRect.top - lineOffset, getMeasuredWidth(), mCropRect.bottom + lineOffset, mPaint); 
} 
if (mCropRect.bottom < getMeasuredHeight()) { 
canvas.drawRect(0, mCropRect.bottom + lineOffset, getMeasuredWidth(), getMeasuredHeight(), mPaint); 
} 
} 
public boolean onTouchEvent(MotionEvent ev) { 
if (ev.getPointerCount() > 1) { 
mOperation = OPERATION.SCALE; 
return mScaleGestureDetector.onTouchEvent(ev); 
} 
final int action = ev.getActionMasked(); 
final int x = (int) ev.getX(); 
final int y = (int) ev.getY(); 
switch (action) { 
case MotionEvent.ACTION_DOWN: 
mOperation = OPERATION.DRAG; 
mLastX = x; 
mLastY = y; 
break; 
case MotionEvent.ACTION_MOVE: 
if (mOperation == OPERATION.DRAG) { 
int deltaX = x - mLastX; 
int deltaY = y - mLastY; 
RectF boundary = getDrawBoundary(getDrawMatrix()); 
if (boundary.left + deltaX > mCropRect.left) { 
deltaX = (int) (mCropRect.left - boundary.left); 
} else if (boundary.right + deltaX < mCropRect.right) { 
deltaX = (int) (mCropRect.right - boundary.right); 
} 
if (boundary.top + deltaY > mCropRect.top) { 
deltaY = (int) (mCropRect.top - boundary.top); 
} else if (boundary.bottom + deltaY < mCropRect.bottom) { 
deltaY = (int) (mCropRect.bottom - boundary.bottom); 
} 
mSupportMatrix.postTranslate(deltaX, deltaY); 
setImageMatrix(getDrawMatrix()); 
mLastX = x; 
mLastY = y; 
} 
break; 
case MotionEvent.ACTION_CANCEL: 
case MotionEvent.ACTION_POINTER_UP: 
case MotionEvent.ACTION_UP: 
mLastX = 0; 
mLastY = 0; 
mOperation = null; 
break; 
} 
return super.onTouchEvent(ev); 
} 
public Bitmap getOriginBitmap() { 
BitmapDrawable drawable = (BitmapDrawable) getDrawable(); 
return drawable == null ? null : drawable.getBitmap(); 
} 
 
public Bitmap saveCrop() throws OutOfMemoryError { 
if (getDrawable() == null) { 
return null; 
} 
Bitmap origin = getOriginBitmap(); 
Matrix drawMatrix = getDrawMatrix(); 
// 反转一下矩阵 
Matrix inverse = new Matrix(); 
drawMatrix.invert(inverse); 
// 把裁剪框对应到原图上去 
RectF cropMapped = new RectF(); 
inverse.mapRect(cropMapped, mCropRect); 
clampCropRect(cropMapped, origin.getWidth(), origin.getHeight()); 
// 如果产生了旋转,需要一个旋转矩阵 
Matrix rotationM = new Matrix(); 
if (mRotation % 360 != 0) { 
rotationM.postRotate(mRotation, origin.getWidth() / 2, origin.getHeight() / 2); 
} 
Bitmap cropped = Bitmap.createBitmap( 
origin, (int) cropMapped.left, (int) cropMapped.top, (int) cropMapped.width(), (int) cropMapped.height(), rotationM, true 
); 
return cropped; 
} 
private void clampCropRect(RectF cropRect, int borderW, int borderH) { 
if (cropRect.left < 0) { 
cropRect.left = 0; 
} 
if (cropRect.top < 0) { 
cropRect.top = 0; 
} 
if (cropRect.right > borderW) { 
cropRect.right = borderW; 
} 
if (cropRect.bottom > borderH) { 
cropRect.bottom = borderH; 
} 
} 
@Override 
public boolean onScale(ScaleGestureDetector detector) { 
float scale = detector.getScaleFactor(); 
if (scale == 1.0f) { 
return true; 
} 
final float currentScale = getScale(mSupportMatrix); 
final float centerX = detector.getFocusX(); 
final float centerY = detector.getFocusY(); 
if ((currentScale <= 1.0f && scale < 1.0f) 
|| (currentScale >= mScaleMax && scale > 1.0f)) { 
return true; 
} 
if (currentScale * scale < 1.0f) { 
scale = 1.0f / currentScale; 
} else if (currentScale * scale > mScaleMax) { 
scale = mScaleMax / currentScale; 
} 
mSupportMatrix.postScale(scale, scale, centerX, centerY); 
RectF boundary = getDrawBoundary(getDrawMatrix()); 
float translateX = 0; 
if (boundary.left > mCropRect.left) { 
translateX = mCropRect.left - boundary.left; 
} else if (boundary.right < mCropRect.right) { 
translateX = mCropRect.right - boundary.right; 
} 
Log.d("scale", "x==>" + translateX); 
float translateY = 0; 
if (boundary.top > mCropRect.top) { 
translateY = mCropRect.top - boundary.top; 
} else if (boundary.bottom < mCropRect.bottom) { 
translateY = mCropRect.bottom - boundary.bottom; 
} 
mSupportMatrix.postTranslate(translateX, translateY); 
setImageMatrix(getDrawMatrix()); 
return true; 
} 
protected Matrix getDrawMatrix() { 
mDrawMatrix.reset(); 
if (mRotation % 360 != 0) { 
final boolean rotated = isImageRotated(); 
final int width = rotated ? mImageHeight : mImageWidth; 
final int height = rotated ? mImageWidth : mImageHeight; 
mDrawMatrix.postRotate(mRotation, mImageWidth / 2, mImageHeight / 2); 
if (rotated) { 
final int translateX = (width - mImageWidth) / 2; 
final int translateY = (height - mImageHeight) / 2; 
mDrawMatrix.postTranslate(translateX, translateY); 
} 
} 
mDrawMatrix.postConcat(mBaseMatrix); 
mDrawMatrix.postConcat(mSupportMatrix); 
return mDrawMatrix; 
} 
@Override 
public boolean onScaleBegin(ScaleGestureDetector detector) { 
return true; 
} 
@Override 
public void onScaleEnd(ScaleGestureDetector detector) { 
final float currentScale = getScale(mSupportMatrix); 
if (currentScale < 1.0f) { 
Log.e("onScaleEnd", "currentScale==>" + currentScale); 
RectF boundary = getDrawBoundary(getDrawMatrix()); 
post(new AnimatedZoomRunnable(currentScale, 1.0f, boundary.centerX(), boundary.centerY())); 
} 
} 
protected RectF getDrawBoundary(Matrix matrix) { 
Drawable drawable = getDrawable(); 
if (drawable == null) { 
return mBoundaryRect; 
} 
final int bitmapWidth = drawable.getIntrinsicWidth(); 
final int bitmapHeight = drawable.getIntrinsicHeight(); 
mBoundaryRect.set(0, 0, bitmapWidth, bitmapHeight); 
matrix.mapRect(mBoundaryRect); 
return mBoundaryRect; 
} 
public float getScale(Matrix matrix) { 
return (float) Math.sqrt((float) Math.pow(getValue(matrix, Matrix.MSCALE_X), 2) + (float) Math.pow(getValue(matrix, Matrix.MSKEW_Y), 2)); 
} 
 
private float getValue(Matrix matrix, int whichValue) { 
matrix.getValues(mMatrixValues); 
return mMatrixValues[whichValue]; 
} 
public void enableDrawCropWidget(boolean enable) { 
mEnableDrawCropWidget = enable; 
} 
protected enum OPERATION { 
DRAG, SCALE 
} 
public enum Type { 
CENTER_CROP, CENTER_INSIDE 
} 
public interface IDecodeCallback { 
void onDecoded(final int rotation, final Bitmap bitmap); 
} 
//setImagePath这个方法耗时,需要显示进度条,这个是监听 
public interface onBitmapLoadListener { 
void onLoadPrepare(); 
void onLoadFinish(); 
} 
private class AnimatedZoomRunnable implements Runnable { 
private final float mFocalX, mFocalY; 
private final long mStartTime; 
private final float mZoomStart, mZoomEnd; 
public AnimatedZoomRunnable(final float currentZoom, final float targetZoom, 
final float focalX, final float focalY) { 
mFocalX = focalX; 
mFocalY = focalY; 
mStartTime = System.currentTimeMillis(); 
mZoomStart = currentZoom; 
mZoomEnd = targetZoom; 
} 
@Override 
public void run() { 
float t = interpolate(); 
float scale = mZoomStart + t * (mZoomEnd - mZoomStart); 
float deltaScale = scale / getScale(mSupportMatrix); 
mSupportMatrix.postScale(deltaScale, deltaScale, mFocalX, mFocalY); 
setImageMatrix(getDrawMatrix()); 
// We haven't hit our target scale yet, so post ourselves again 
if (t < 1f) { 
postOnAnimation(this); 
} 
} 
private float interpolate() { 
float t = 1f * (System.currentTimeMillis() - mStartTime) / 200; 
t = Math.min(1f, t); 
t = sInterpolator.getInterpolation(t); 
return t; 
} 
} 
} 
<declare-styleable name="Life_CropImage"> 
<attr name="life_Crop_ratio" format="float" /> 
<attr name="life_Crop_scale_type" format="enum"> 
<enum name="life_center_crop" value="0" /> 
<enum name="life_center_inside" value="1" /> 
</attr> 
</declare-styleable>

1、让这个裁剪框显示图片:


mSeniorImageView.setImagePath(path);

2、保存裁剪后的图片:


Bitmap imageViewBitmap = null; 
try { 
imageViewBitmap = mSeniorImageView.saveCrop(); 
} catch (OutOfMemoryError e) { 
imageViewBitmap = mSeniorImageView.getOriginBitmap(); 
PinkToast.makeText(mActivity, R.string.life_image_crop_topbar_crop_error, Toast.LENGTH_LONG).show(); 
}

3、设置裁剪比例:


mSeniorImageView.setCropRatio(3f / 4f);

4、设置裁剪框的padding:


mSeniorImageView.setCropRectPadding(0f);

5、setImagePath这个方法比较耗时,需要显示进度条,这个是监听:


mSeniorImageView.setBitmapLoadingListener(new SeniorCropImageView.onBitmapLoadListener() { 
@Override 
public void onLoadPrepare() { 
mActivity.showProgress(); 
} 
@Override 
public void onLoadFinish() { 
mActivity.hideProgress(); 
} 
});

以上所述是小编给大家带来的Android 以任意比例裁剪图片代码分享,希望对大家有所帮助

您可能感兴趣的文章:android系统拍照结合android-crop裁剪图片Android调用系统拍照裁剪图片模糊的解决方法Android实现拍照、选择相册图片并裁剪功能Android图片裁剪功能实现代码Android实现相机拍摄、选择、图片裁剪功能Android拍照或从图库选择图片并裁剪Android实现拍照、选择图片并裁剪图片功能Android实现从本地图库/相机拍照后裁剪图片并设置头像解决Android从相册中获取图片出错图片却无法裁剪问题的方法Android开发从相机或相册获取图片裁剪Android裁剪图片为圆形图片的实现原理与代码Android编程实现调用系统图库与裁剪图片功能


阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-移动开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯