本文介紹一個Android自定義的圖片剪裁控件
該控件由另一篇文章:Android 圖片拖拽、放大縮小的自定義控件 擴展而來 http://www.linuxidc.com/Linux/2014-12/110763.htm
如圖:
思路:在一個自定義View上繪制一張圖片(參照前面提到的另一篇文章),在該自定義View上繪制一個自定義的FloatDrawable,也就是圖中的浮層。繪制圖片和FloatDrawable的交集的補集部分灰色陰影(這個其實很簡單,就一句話)。在自定義View的touch中去處理具體的拖動事件和FloatDrawable的變換。圖片的繪制和FloatDrawable的繪制以及變換最終其實就是在操作各自的Rect而已,Rect就是一個有矩形,有四個坐標,圖片和FloatDrawable就是按照坐標去繪制的。
CropImageView.java
該類繼承View
功能:在onDraw方法中畫圖片、浮層,處理touch事件,最後根據坐標對圖片進行剪裁。
public class CropImageView extends View {
// 在touch重要用到的點,
private float mX_1 = 0;
private float mY_1 = 0;
// 觸摸事件判斷
private final int STATUS_SINGLE = 1;
private final int STATUS_MULTI_START = 2;
private final int STATUS_MULTI_TOUCHING = 3;
// 當前狀態
private int mStatus = STATUS_SINGLE;
// 默認裁剪的寬高
private int cropWidth;
private int cropHeight;
// 浮層Drawable的四個點
private final int EDGE_LT = 1;
private final int EDGE_RT = 2;
private final int EDGE_LB = 3;
private final int EDGE_RB = 4;
private final int EDGE_MOVE_IN = 5;
private final int EDGE_MOVE_OUT = 6;
private final int EDGE_NONE = 7;
public int currentEdge = EDGE_NONE;
protected float oriRationWH = 0;
protected final float maxZoomOut = 5.0f;
protected final float minZoomIn = 0.333333f;
protected Drawable mDrawable;
protected FloatDrawable mFloatDrawable;
protected Rect mDrawableSrc = new Rect();// 圖片Rect變換時的Rect
protected Rect mDrawableDst = new Rect();// 圖片Rect
protected Rect mDrawableFloat = new Rect();// 浮層的Rect
protected boolean isFrist = true;
private boolean isTouchInSquare = true;
protected Context mContext;
public CropImageView(Context context) {
super(context);
init(context);
}
public CropImageView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public CropImageView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context);
}
@SuppressLint("NewApi")
private void init(Context context) {
this.mContext = context;
try {
if (android.os.Build.VERSION.SDK_INT >= 11) {
this.setLayerType(LAYER_TYPE_SOFTWARE, null);
}
} catch (Exception e) {
e.printStackTrace();
}
mFloatDrawable = new FloatDrawable(context);
}
public void setDrawable(Drawable mDrawable, int cropWidth, int cropHeight) {
this.mDrawable = mDrawable;
this.cropWidth = cropWidth;
this.cropHeight = cropHeight;
this.isFrist = true;
invalidate();
}
@SuppressLint("ClickableViewAccessibility")
@Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getPointerCount() > 1) {
if (mStatus == STATUS_SINGLE) {
mStatus = STATUS_MULTI_START;
} else if (mStatus == STATUS_MULTI_START) {
mStatus = STATUS_MULTI_TOUCHING;
}
} else {
if (mStatus == STATUS_MULTI_START
|| mStatus == STATUS_MULTI_TOUCHING) {
mX_1 = event.getX();
mY_1 = event.getY();
}
mStatus = STATUS_SINGLE;
}
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mX_1 = event.getX();
mY_1 = event.getY();
currentEdge = getTouch((int) mX_1, (int) mY_1);
isTouchInSquare = mDrawableFloat.contains((int) event.getX(),
(int) event.getY());
break;
case MotionEvent.ACTION_UP:
checkBounds();
break;
case MotionEvent.ACTION_POINTER_UP:
currentEdge = EDGE_NONE;
break;
case MotionEvent.ACTION_MOVE:
if (mStatus == STATUS_MULTI_TOUCHING) {
} else if (mStatus == STATUS_SINGLE) {
int dx = (int) (event.getX() - mX_1);
int dy = (int) (event.getY() - mY_1);
mX_1 = event.getX();
mY_1 = event.getY();
// 根據得到的那一個角,並且變換Rect
if (!(dx == 0 && dy == 0)) {
switch (currentEdge) {
case EDGE_LT:
mDrawableFloat.set(mDrawableFloat.left + dx,
mDrawableFloat.top + dy, mDrawableFloat.right,
mDrawableFloat.bottom);
break;
case EDGE_RT:
mDrawableFloat.set(mDrawableFloat.left,
mDrawableFloat.top + dy, mDrawableFloat.right
+ dx, mDrawableFloat.bottom);
break;
case EDGE_LB:
mDrawableFloat.set(mDrawableFloat.left + dx,
mDrawableFloat.top, mDrawableFloat.right,
mDrawableFloat.bottom + dy);
break;
case EDGE_RB:
mDrawableFloat.set(mDrawableFloat.left,
mDrawableFloat.top, mDrawableFloat.right + dx,
mDrawableFloat.bottom + dy);
break;
case EDGE_MOVE_IN:
if (isTouchInSquare) {
mDrawableFloat.offset((int) dx, (int) dy);
}
break;
case EDGE_MOVE_OUT:
break;
}
mDrawableFloat.sort();
invalidate();
}
}
break;
}
return true;
}
// 根據初觸摸點判斷是觸摸的Rect哪一個角
public int getTouch(int eventX, int eventY) {
if (mFloatDrawable.getBounds().left <= eventX
&& eventX < (mFloatDrawable.getBounds().left + mFloatDrawable
.getBorderWidth())
&& mFloatDrawable.getBounds().top <= eventY
&& eventY < (mFloatDrawable.getBounds().top + mFloatDrawable
.getBorderHeight())) {
return EDGE_LT;
} else if ((mFloatDrawable.getBounds().right - mFloatDrawable
.getBorderWidth()) <= eventX
&& eventX < mFloatDrawable.getBounds().right
&& mFloatDrawable.getBounds().top <= eventY
&& eventY < (mFloatDrawable.getBounds().top + mFloatDrawable
.getBorderHeight())) {
return EDGE_RT;
} else if (mFloatDrawable.getBounds().left <= eventX
&& eventX < (mFloatDrawable.getBounds().left + mFloatDrawable
.getBorderWidth())
&& (mFloatDrawable.getBounds().bottom - mFloatDrawable
.getBorderHeight()) <= eventY
&& eventY < mFloatDrawable.getBounds().bottom) {
return EDGE_LB;
} else if ((mFloatDrawable.getBounds().right - mFloatDrawable
.getBorderWidth()) <= eventX
&& eventX < mFloatDrawable.getBounds().right
&& (mFloatDrawable.getBounds().bottom - mFloatDrawable
.getBorderHeight()) <= eventY
&& eventY < mFloatDrawable.getBounds().bottom) {
return EDGE_RB;
} else if (mFloatDrawable.getBounds().contains(eventX, eventY)) {
return EDGE_MOVE_IN;
}
return EDGE_MOVE_OUT;
}
@Override
protected void onDraw(Canvas canvas) {
if (mDrawable == null) {
return;
}
if (mDrawable.getIntrinsicWidth() == 0
|| mDrawable.getIntrinsicHeight() == 0) {
return;
}
configureBounds();
// 在畫布上花圖片
mDrawable.draw(canvas);
canvas.save();
// 在畫布上畫浮層FloatDrawable,Region.Op.DIFFERENCE是表示Rect交集的補集
canvas.clipRect(mDrawableFloat, Region.Op.DIFFERENCE);
// 在交集的補集上畫上灰色用來區分
canvas.drawColor(Color.parseColor("#a0000000"));
canvas.restore();
// 畫浮層
mFloatDrawable.draw(canvas);
}
protected void configureBounds() {
// configureBounds在onDraw方法中調用
// isFirst的目的是下面對mDrawableSrc和mDrawableFloat只初始化一次,
// 之後的變化是根據touch事件來變化的,而不是每次執行重新對mDrawableSrc和mDrawableFloat進行設置
if (isFrist) {
oriRationWH = ((float) mDrawable.getIntrinsicWidth())
/ ((float) mDrawable.getIntrinsicHeight());
final float scale = mContext.getResources().getDisplayMetrics().density;
int w = Math.min(getWidth(), (int) (mDrawable.getIntrinsicWidth()
* scale + 0.5f));
int h = (int) (w / oriRationWH);
int left = (getWidth() - w) / 2;
int top = (getHeight() - h) / 2;
int right = left + w;
int bottom = top + h;
mDrawableSrc.set(left, top, right, bottom);
mDrawableDst.set(mDrawableSrc);
int floatWidth = dipTopx(mContext, cropWidth);
int floatHeight = dipTopx(mContext, cropHeight);
if (floatWidth > getWidth()) {
floatWidth = getWidth();
floatHeight = cropHeight * floatWidth / cropWidth;
}
if (floatHeight > getHeight()) {
floatHeight = getHeight();
floatWidth = cropWidth * floatHeight / cropHeight;
}
int floatLeft = (getWidth() - floatWidth) / 2;
int floatTop = (getHeight() - floatHeight) / 2;
mDrawableFloat.set(floatLeft, floatTop, floatLeft + floatWidth,
floatTop + floatHeight);
isFrist = false;
}
mDrawable.setBounds(mDrawableDst);
mFloatDrawable.setBounds(mDrawableFloat);
}
// 在up事件中調用了該方法,目的是檢查是否把浮層拖出了屏幕
protected void checkBounds() {
int newLeft = mDrawableFloat.left;
int newTop = mDrawableFloat.top;
boolean isChange = false;
if (mDrawableFloat.left < getLeft()) {
newLeft = getLeft();
isChange = true;
}
if (mDrawableFloat.top < getTop()) {
newTop = getTop();
isChange = true;
}
if (mDrawableFloat.right > getRight()) {
newLeft = getRight() - mDrawableFloat.width();
isChange = true;
}
if (mDrawableFloat.bottom > getBottom()) {
newTop = getBottom() - mDrawableFloat.height();
isChange = true;
}
mDrawableFloat.offsetTo(newLeft, newTop);
if (isChange) {
invalidate();
}
}
// 進行圖片的裁剪,所謂的裁剪就是根據Drawable的新的坐標在畫布上創建一張新的圖片
public Bitmap getCropImage() {
Bitmap tmpBitmap = Bitmap.createBitmap(getWidth(), getHeight(),
Config.RGB_565);
Canvas canvas = new Canvas(tmpBitmap);
mDrawable.draw(canvas);
Matrix matrix = new Matrix();
float scale = (float) (mDrawableSrc.width())
/ (float) (mDrawableDst.width());
matrix.postScale(scale, scale);
Bitmap ret = Bitmap.createBitmap(tmpBitmap, mDrawableFloat.left,
mDrawableFloat.top, mDrawableFloat.width(),
mDrawableFloat.height(), matrix, true);
tmpBitmap.recycle();
tmpBitmap = null;
return ret;
}
public int dipTopx(Context context, float dpValue) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (dpValue * scale + 0.5f);
}
}
FloatDrawable.java
繼承自Drawable
功能:圖片上面的浮動框,通過拖動確定位置
public class FloatDrawable extends Drawable {
private Context mContext;
private int offset = 50;
private Paint mLinePaint = new Paint();
private Paint mLinePaint2 = new Paint();
{
mLinePaint.setARGB(200, 50, 50, 50);
mLinePaint.setStrokeWidth(1F);
mLinePaint.setStyle(Paint.Style.STROKE);
mLinePaint.setAntiAlias(true);
mLinePaint.setColor(Color.WHITE);
//
mLinePaint2.setARGB(200, 50, 50, 50);
mLinePaint2.setStrokeWidth(7F);
mLinePaint2.setStyle(Paint.Style.STROKE);
mLinePaint2.setAntiAlias(true);
mLinePaint2.setColor(Color.WHITE);
}
public FloatDrawable(Context context) {
super();
this.mContext = context;
}
public int getBorderWidth() {
return dipTopx(mContext, offset);//根據dip計算的像素值,做適配用的
}
public int getBorderHeight() {
return dipTopx(mContext, offset);
}
@Override
public void draw(Canvas canvas) {
int left = getBounds().left;
int top = getBounds().top;
int right = getBounds().right;
int bottom = getBounds().bottom;
Rect mRect = new Rect(left + dipTopx(mContext, offset) / 2, top
+ dipTopx(mContext, offset) / 2, right
- dipTopx(mContext, offset) / 2, bottom
- dipTopx(mContext, offset) / 2);
//畫默認的選擇框
canvas.drawRect(mRect, mLinePaint);
//畫四個角的四個粗拐角、也就是八條粗線
canvas.drawLine((left + dipTopx(mContext, offset) / 2 - 3.5f), top
+ dipTopx(mContext, offset) / 2,
left + dipTopx(mContext, offset) - 8f,
top + dipTopx(mContext, offset) / 2, mLinePaint2);
canvas.drawLine(left + dipTopx(mContext, offset) / 2,
top + dipTopx(mContext, offset) / 2,
left + dipTopx(mContext, offset) / 2,
top + dipTopx(mContext, offset) / 2 + 30, mLinePaint2);
canvas.drawLine(right - dipTopx(mContext, offset) + 8f,
top + dipTopx(mContext, offset) / 2,
right - dipTopx(mContext, offset) / 2,
top + dipTopx(mContext, offset) / 2, mLinePaint2);
canvas.drawLine(right - dipTopx(mContext, offset) / 2,
top + dipTopx(mContext, offset) / 2 - 3.5f,
right - dipTopx(mContext, offset) / 2,
top + dipTopx(mContext, offset) / 2 + 30, mLinePaint2);
canvas.drawLine((left + dipTopx(mContext, offset) / 2 - 3.5f), bottom
- dipTopx(mContext, offset) / 2,
left + dipTopx(mContext, offset) - 8f,
bottom - dipTopx(mContext, offset) / 2, mLinePaint2);
canvas.drawLine((left + dipTopx(mContext, offset) / 2), bottom
- dipTopx(mContext, offset) / 2,
(left + dipTopx(mContext, offset) / 2),
bottom - dipTopx(mContext, offset) / 2 - 30f, mLinePaint2);
canvas.drawLine((right - dipTopx(mContext, offset) + 8f), bottom
- dipTopx(mContext, offset) / 2,
right - dipTopx(mContext, offset) / 2,
bottom - dipTopx(mContext, offset) / 2, mLinePaint2);
canvas.drawLine((right - dipTopx(mContext, offset) / 2), bottom
- dipTopx(mContext, offset) / 2 - 30f,
right - dipTopx(mContext, offset) / 2,
bottom - dipTopx(mContext, offset) / 2 + 3.5f, mLinePaint2);
}
@Override
public void setBounds(Rect bounds) {
super.setBounds(new Rect(bounds.left - dipTopx(mContext, offset) / 2,
bounds.top - dipTopx(mContext, offset) / 2, bounds.right
+ dipTopx(mContext, offset) / 2, bounds.bottom
+ dipTopx(mContext, offset) / 2));
}
@Override
public void setAlpha(int alpha) {
}
@Override
public void setColorFilter(ColorFilter cf) {
}
@Override
public int getOpacity() {
return 0;
}
public int dipTopx(Context context, float dpValue) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (dpValue * scale + 0.5f);
}
}
使用
布局中:
<RelativeLayout 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" >
<com.onehead.cropimage.CropImageView
android:id="@+id/cropimage"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</RelativeLayout>
Activity中:
public class MainActivity extends ActionBarActivity {
private CropImageView mView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mView = (CropImageView) findViewById(R.id.cropimage);
//設置資源和默認長寬
mView.setDrawable(getResources().getDrawable(R.drawable.test2), 300,
300);
//調用該方法得到剪裁好的圖片
Bitmap mBitmap= mView.getCropImage();
}
}
最簡單的Ubuntu Touch & Android 雙系統安裝方式 http://www.linuxidc.com/Linux/2014-01/94881.htm
在Nexus上實現Ubuntu和Android 4.4.2 雙啟動 http://www.linuxidc.com/Linux/2014-05/101849.htm
Ubuntu 14.04 配置 Android SDK 開發環境 http://www.linuxidc.com/Linux/2014-05/101039.htm
64位Ubuntu 11.10下Android開發環境的搭建(JDK+Eclipse+ADT+Android SDK詳細) http://www.linuxidc.com/Linux/2013-06/85303.htm
Ubuntu 14.04 x64配置Android 4.4 kitkat編譯環境的方法 http://www.linuxidc.com/Linux/2014-04/101148.htm
Ubuntu 12.10 x64 安裝 Android SDK http://www.linuxidc.com/Linux/2013-03/82005.htm
更多Android相關信息見Android 專題頁面 http://www.linuxidc.com/topicnews.aspx?tid=11