編輯:關於Android編程
大多圖片裁剪大多兩種操作:改變裁剪區圖片不能縮放、裁剪區固定圖片縮放,兩種方法都可以裁剪到不同圖片,本次介紹的是可變裁剪區同時能縮放圖片,同時記錄自己的開發項目過程。
裁剪視圖一共三個view,最底層的縮放CilpImageView ,中間是可變裁剪區CilpBorderView,還有最頂層的CilpTouchView。監聽CilpTouchView的OnTouch事件,通過判斷down手勢是否落在拉伸裁剪區的按鈕內分發給CilpBorderView或者CilpImageView 。
Options類是默認裁剪配置。
CilpBorderView類:
public class CilpBorderView extends View { private int borderColor = Color.parseColor("#FFFFFF"); private int outSideColor = Color.parseColor("#20000000"); private float borderWidth = 2; private float lineWidth = 1; private Rect[] rects=new Rect[2]; private Paint cutPaint; private Paint outSidePaint; private RectF cilpRectF; //圖片右坐標 private int iconRight=0; //圖片左坐標 private int iconLeft=0; private Bitmap bitmap; private int iconOffset; private int width=0; private int height=0; private int verLine1; private int verLine2; private Options options; private static final int TOP_ICON_ACTION=1; private static final int BOTTOM_ICON_ACTION=2; private int action=-1; private float actionY; public CilpBorderView(Context context) { super(context); initView(); } public CilpBorderView(Context context, AttributeSet attrs) { super(context, attrs); initView(); } public CilpBorderView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); initView(); } private void initView() { cutPaint = new Paint(); cutPaint.setColor(borderColor); cutPaint.setStrokeWidth(borderWidth); cutPaint.setStyle(Paint.Style.STROKE); outSidePaint=new Paint(); outSidePaint.setAntiAlias(true); outSidePaint.setColor(outSideColor); outSidePaint.setStyle(Paint.Style.FILL); bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.ic_crop_drag_y); iconOffset = bitmap.getWidth()/2; options = new Options(); } @Override protected void onDraw(Canvas canvas) { if (cilpRectF == null) { cilpRectF = new RectF(options.paddingWidth, (getHeight() - options.cilpHeight) / 2, getWidth() - options.paddingWidth, (getHeight() + options.cilpHeight) / 2); verLine1 = (int) (cilpRectF.left + cilpRectF.width() / 3); verLine2 = (int) (cilpRectF.left + cilpRectF.width() * 2 / 3); } if (width == 0) width = getWidth(); if (height == 0) height = getHeight(); canvas.save(); drawLine(canvas); drawRound(canvas); drawIcon(canvas); canvas.restore(); super.onDraw(canvas); } private void drawIcon(Canvas canvas) { if (iconLeft == 0 && iconRight == 0) { iconLeft = width / 2 - iconOffset; iconRight=width / 2 + iconOffset; } canvas.drawBitmap(bitmap, iconLeft, cilpRectF.top-iconOffset, null); canvas.drawBitmap(bitmap, iconLeft, cilpRectF.bottom-iconOffset, null); Rect rect=new Rect(iconLeft-options.iconClick, (int)(cilpRectF.top-iconOffset)-options.iconClick,iconRight+options.iconClick, (int)(cilpRectF.top+iconOffset)+options.iconClick); rects[0]=rect; rect=new Rect(iconLeft-options.iconClick,(int)(cilpRectF.bottom-iconOffset)-options.iconClick,iconRight+options.iconClick, (int)(cilpRectF.bottom+iconOffset)+options.iconClick); rects[1]=rect; } private void drawLine(Canvas canvas){ cutPaint.setStrokeWidth(lineWidth); float p = cilpRectF.top + cilpRectF.height() / 3; //橫線 canvas.drawLine(options.paddingWidth, p, width-options.paddingWidth, p, cutPaint); p = cilpRectF.top + cilpRectF.height() * 2 / 3; canvas.drawLine(options.paddingWidth, p, width-options.paddingWidth, p, cutPaint); //豎線 canvas.drawLine(verLine1, cilpRectF.top, verLine1, cilpRectF.bottom, cutPaint); canvas.drawLine(verLine2, cilpRectF.top, verLine2, cilpRectF.bottom, cutPaint); } private void drawRound(Canvas canvas) { //繪制邊框 cutPaint.setStrokeWidth(borderWidth); canvas.drawRect(cilpRectF, cutPaint); //繪制外區域 //左中框 canvas.drawRect(0, cilpRectF.top, options.paddingWidth, cilpRectF.bottom, outSidePaint); //上框 canvas.drawRect(0, 0, width, cilpRectF.top, outSidePaint); //右中框 canvas.drawRect(cilpRectF.right, cilpRectF.top, width, cilpRectF.bottom, outSidePaint); //下框 canvas.drawRect(0, cilpRectF.bottom, width, height, outSidePaint); } public void setOptions(Options options) { this.options = options; } /** * 根據手勢做拉伸 */ public boolean iconOntouch(MotionEvent event,RectF imgRect){ switch (event.getAction()){ case MotionEvent.ACTION_DOWN: if (rects[0].contains((int)event.getX(),(int)event.getY())) { action=TOP_ICON_ACTION; } if (rects[1].contains((int)event.getX(),(int)event.getY())) { action=BOTTOM_ICON_ACTION; } actionY=event.getY(); break; case MotionEvent.ACTION_MOVE: float y=actionY-event.getY(); switch (action){ case TOP_ICON_ACTION: cilpRectF.top=cilpRectF.top-y; break; case BOTTOM_ICON_ACTION: cilpRectF.bottom=cilpRectF.bottom-y; break; } checkBroad(imgRect); actionY=event.getY(); invalidate(); break; case MotionEvent.ACTION_UP: action=-1; break; } return true; } /** * @description 邊界校驗 * @param imgRect */ private void checkBroad(RectF imgRect) { if ((cilpRectF.bottom-cilpRectF.top) < options.min_height){//高度少於最小高度 switch (action) { case TOP_ICON_ACTION: cilpRectF.top=cilpRectF.bottom - options.min_height; break; case BOTTOM_ICON_ACTION: cilpRectF.bottom=cilpRectF.top + options.min_height; break; } } else if ((cilpRectF.bottom-cilpRectF.top) > options.max_height){//高度大於最大高度 switch (action) { case TOP_ICON_ACTION: cilpRectF.top=cilpRectF.bottom - options.max_height; break; case BOTTOM_ICON_ACTION: cilpRectF.bottom=cilpRectF.top + options.max_height; break; } } if (cilpRectF.top < options.paddingHeight) { cilpRectF.top = options.paddingHeight; } if (cilpRectF.bottom > height-options.paddingHeight){ cilpRectF.bottom = height-options.paddingHeight; } if ( cilpRectF.top < imgRect.top) { cilpRectF.top = imgRect.top; } if ( cilpRectF.bottom > imgRect.bottom) { cilpRectF.bottom = imgRect.bottom; } } //判斷手勢down事件是否落在拉伸按鈕區域內 public boolean isIconClick(MotionEvent event){ if (rects[0].contains((int)event.getX(), (int)event.getY())) { System.out.println("點擊頂部圖標"); action=TOP_ICON_ACTION; return true; } if (rects[1].contains((int)event.getX(), (int)event.getY())) { System.out.println("點擊底部圖標"); action=BOTTOM_ICON_ACTION; return true; } actionY=event.getY(); return false; } public RectF getCilpRectF() { return cilpRectF; } public void setCilpRectF(RectF cilpRectF) { this.cilpRectF = cilpRectF; invalidate(); }
CilpImageView 類:
public class CilpImageView extends ImageView implements ScaleGestureDetector.OnScaleGestureListener, ViewTreeObserver.OnGlobalLayoutListener{ private static float SCALE_MID = 0.1f; /** * 初始化時的縮放比例,如果圖片寬或高大於屏幕,此值將小0 */ private float initScale = 1.0f; /** * 縮放的手勢檢測 */ private ScaleGestureDetector scaleGestureDetector = null; private final Matrix scaleMatrix = new Matrix(); private final float[] matrixValues = new float[9]; /** * 用於雙擊縮放 */ private GestureDetector gestureDetector; //是否自動縮放任務 private boolean isAutoScale; private float mLastX; private float mLastY; private float centerX; private float centerY; //圖片原始寬高 private int drawableW; private int drawableH; private boolean isCanDrag; private int lastPointerCount; private RectF borderRectF; private CilpRectFChangeListener cilpRectFChangeListener; private Options options; public CilpImageView(Context context) { super(context); initView(context); } public CilpImageView(Context context, AttributeSet attrs) { super(context, attrs); initView(context); } public CilpImageView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); initView(context); } private void initView(Context context) { options = new Options(); setScaleType(ScaleType.MATRIX); setBackgroundColor(Color.BLACK); gestureDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() { @Override public boolean onDoubleTap(MotionEvent e) {//雙擊 if (isAutoScale) return true; float x=e.getX(); float y=e.getY(); if (getScale() != initScale) { CilpImageView.this.postDelayed(new AutoScaleRunnable(initScale, x, y),16); } else { CilpImageView.this.postDelayed(new AutoScaleRunnable(initScale * 2, x, y),16); } isAutoScale=true; return true; } }); scaleGestureDetector=new ScaleGestureDetector(context,this); } /** * 獲得當前的縮放比例 * * @return */ public final float getScale() { scaleMatrix.getValues(matrixValues); return matrixValues[Matrix.MSCALE_X]; } /** * 邊界校驗 */ private void checkBorder(){ RectF rectF=getMatrixRectF(); float deltaX = 0; float deltaY = 0; int width=getWidth(); // 如果寬或高大於屏幕,則控制范圍,這裡的0.001是因為精度丟失會產生問題,但是誤差一般很小,所以直接加了一個0.01 if (rectF.width() + 0.01 >= width - 2 * options.paddingWidth) { if (rectF.left > options.paddingWidth) { deltaX = -rectF.left + options.paddingWidth; } if (rectF.right < width - options.paddingWidth){ deltaX = width - options.paddingWidth - rectF.right; } } if (rectF.height() +0.01 >= borderRectF.height()){ if (rectF.top > borderRectF.top){ deltaY = -rectF.top + borderRectF.top; } if (rectF.bottom < borderRectF.bottom){ deltaY = borderRectF.bottom-rectF.bottom; } } scaleMatrix.postTranslate(deltaX,deltaY); } /** * 根據當前圖片的Matrix獲得圖片的范圍 */ public RectF getMatrixRectF() { Matrix matrix = scaleMatrix; RectF rect = new RectF(); Drawable d = getDrawable(); if (null != d) { rect.set(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight()); matrix.mapRect(rect); } return rect; } @Override public boolean onScale(ScaleGestureDetector detector) { if (getDrawable() == null) { return true; } float scaleFactor=detector.getScaleFactor(); scaleMatrix.postScale(scaleFactor, scaleFactor, detector.getFocusX(), detector.getFocusY()); checkBorder(); setImageMatrix(scaleMatrix); setCilpRectFIfNeed(); return true; } @Override public boolean onScaleBegin(ScaleGestureDetector detector) { return true; } @Override public void onScaleEnd(ScaleGestureDetector detector) { } /** * 根據手勢做縮放 */ public boolean onImageTouch(MotionEvent event, RectF borderRect) { this.borderRectF=borderRect; if (gestureDetector.onTouchEvent(event)){ return true; } scaleGestureDetector.onTouchEvent(event); float x = 0, y = 0; // 拿到觸摸點的個數 final int pointerCount = event.getPointerCount(); // 得到多個觸摸點的x與y for (int i = 0; i < pointerCount; i++) { x += event.getX(i); y += event.getY(i); } x = x / pointerCount; y = y / pointerCount; /** * 每當觸摸點發生變化時,重置mLasX , mLastY */ if (pointerCount != lastPointerCount) { isCanDrag = false; mLastX = x; mLastY = y; } lastPointerCount = pointerCount; switch (event.getAction()) { case MotionEvent.ACTION_DOWN: resetMidScale(); mLastX = x; mLastY = y; break; case MotionEvent.ACTION_MOVE: float dx = x - mLastX; float dy = y - mLastY; if (!isCanDrag) { isCanDrag = isCanDrag(dx, dy); } if (isCanDrag) { if (getDrawable() != null) { RectF rectF = getMatrixRectF(); // 如果寬度小於屏幕寬度,則禁止左右移動 if (rectF.width() <= borderRectF.width()) { dx = 0; } // 如果高度小於屏幕高度,則禁止上下移動 if (rectF.height() <= borderRectF.height()) { dy = 0; } scaleMatrix.postTranslate(dx, dy); checkBorder(); setImageMatrix(scaleMatrix); } } mLastX = x; mLastY = y; break; case MotionEvent.ACTION_UP: if (SCALE_MID>getScale()) { postDelayed( new AutoScaleRunnable(SCALE_MID, getWidth()/2, event.getY()), 1); } break; case MotionEvent.ACTION_CANCEL: lastPointerCount = 0; break; } return true; } /** * 是否是拖動行為 */ private boolean isCanDrag(float dx, float dy) { return Math.sqrt((dx * dx) + (dy * dy)) >= 0; } @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); getViewTreeObserver().addOnGlobalLayoutListener(this); } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { getViewTreeObserver().removeOnGlobalLayoutListener(this); } else { getViewTreeObserver().removeGlobalOnLayoutListener(this); } } @Override public void onGlobalLayout() { if (0!=getHeight()) { if (borderRectF==null) borderRectF=new RectF(options.paddingWidth,(getHeight() - options.cilpHeight)/2, getWidth()-options.paddingWidth,(getHeight() + options.cilpHeight)/2); int height=getHeight(); Drawable d = getDrawable(); if (d == null) return; int width = getWidth(); // 拿到圖片的寬和高 drawableW = d.getIntrinsicWidth(); drawableH = d.getIntrinsicHeight(); float scale ; scale= borderRectF.width() /drawableW; scaleMatrix.reset(); initScale = scale; SCALE_MID=initScale; centerX=(width - (borderRectF.width())) / 2; centerY=(height - drawableH*scale) / 2; scaleMatrix.postTranslate(centerX, centerY); scaleMatrix.postScale(scale, scale,centerX,centerY); // 圖片移動至屏幕中心 setImageMatrix(scaleMatrix); setCilpRectFIfNeed(); } } //校驗裁剪邊界 private void setCilpRectFIfNeed(){ RectF rectF=getMatrixRectF(); if (cilpRectFChangeListener!=null && (rectF.height() < borderRectF.height())){ borderRectF.top = rectF.top; borderRectF.bottom = rectF.bottom; rectF.right = borderRectF.right; rectF.left = borderRectF.left; cilpRectFChangeListener.onChange(rectF); } } /** * 計算最小縮放值 */ private void resetMidScale(){ int distanceW = (int) (drawableW - borderRectF.width()); int distanceH = (int) (drawableH - borderRectF.height()); if (distanceH < distanceW) {//按高度算最小縮放比例 SCALE_MID= borderRectF.height() /drawableH; } else { SCALE_MID= borderRectF.width() /drawableW; } } /** * 剪切圖片,返回剪切後的bitmap對象 * * @return */ public Bitmap clip(RectF rectF) { Bitmap bitmap = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(bitmap); draw(canvas); bitmap=Bitmap.createBitmap(bitmap, (int)rectF.left, (int)rectF.top,(int)rectF.width(),(int)rectF.height()); return bitmap; } /** * 自動縮放任務 */ private class AutoScaleRunnable implements Runnable{ static final float BIGGER=1.07f; static final float SMALLER=0.93f; private float tarScale; private float tmpScale; /** * 縮放的中中心 */ private float x; private float y; AutoScaleRunnable(float tarScale, float x, float y) { this.tarScale=tarScale; this.x=x; this.y=y; if (getScale() < tarScale){ tmpScale=BIGGER; } else { tmpScale=SMALLER; } } @Override public void run() { scaleMatrix.postScale(tmpScale, tmpScale, x, y); checkBorder(); setImageMatrix(scaleMatrix); final float currentScale = getScale(); // 如果值在合法范圍內,繼續縮放 if ( ((tmpScale > 1f) && (currentScale < tarScale)) || ((tmpScale < 1f) && (tarScale1f) && (currentScale tarScale){ tmpScale = tarScale / currentScale; } } if (((tmpScale < 1f) && (currentScale > tarScale))){//縮小 if (nextScale < tarScale){ tmpScale = tarScale / currentScale; } } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { CilpImageView.this.postOnAnimation(this); }else{ CilpImageView.this.postDelayed(this,1); } } else { setCilpRectFIfNeed(); isAutoScale=false; } } } public void setCilpRectFChangeListener(CilpRectFChangeListener cilpRectFChangeListener) { this.cilpRectFChangeListener = cilpRectFChangeListener; } public void setOptions(Options options) { this.options = options; } public interface CilpRectFChangeListener{ void onChange(RectF rectF); } }
CilpTouchView類:
public class CilpTouchView extends View implements View.OnTouchListener{ private CilpImageView imageView; private CilpBorderView borderView; private boolean iconClick; private RectF changeRect; public CilpTouchView(Context context, CilpBorderView borderView, final CilpImageView imageView) { super(context); if (borderView == null || imageView == null) { throw new NullPointerException("view is null"); } this.borderView=borderView; this.imageView=imageView; imageView.setCilpRectFChangeListener(new CilpImageView.CilpRectFChangeListener() { @Override public void onChange(RectF rectF) { CilpTouchView.this.borderView.setCilpRectF(rectF); } }); setOnTouchListener(this); } @Override public boolean onTouch(View v, MotionEvent event) { if (event.getAction() == MotionEvent.ACTION_DOWN) { if (borderView.isIconClick(event)){ iconClick = true; changeRect= imageView.getMatrixRectF(); } else { iconClick = false; changeRect=borderView.getCilpRectF(); } } if (iconClick){ borderView.iconOntouch(event, changeRect); } else { imageView.onImageTouch(event, changeRect); } if (event.getAction() == MotionEvent.ACTION_UP) { iconClick=false; } return true; } }
效果圖:
例子下載:https://github.com/gdflk/ariableCilpImageView
是什麼BroadCastReceiver是四大組件之一,相當於一個全局的監聽器,用於監聽系統全局的廣播。怎麼樣由於BroadCastReceiver是全局監聽器,因此它可
本文總結分析了Android編程獲取控件寬和高的方法。分享給大家供大家參考,具體如下:我們都知道在onCreate()裡面獲取控件的高度是0,這是為什麼呢?我們來
好久沒有發博客了,現在工作忙了,底層代碼跟蹤學習的東西很久沒有做成文檔了,雖然博客寫的爛,但是再寫的過程中,能更清晰的認識到自己那個地方還不清晰,不明白。這樣能更好的嘴一
全局變量顧名思義就是在整個的類中或者可在多個函數中調用的變量。也稱為外部變量。局部變量則是特定過程或函數中可以訪問的變量。聲明一個變量是很 容易的,但是講到使用的時候,卻