編輯:關於Android編程
安卓實現方形頭像裁剪
實現思路,界面可見區域為2層View
最頂層的View是顯示層,主要繪制半透明邊框區域和白色裁剪區域,代碼比較容易。
第二層繼承ImageView,使用ImageView的Matrix實現顯示部分圖片,及挪動,放大縮小等操作。
比較復雜的地方在於多指操作對ImageView的影響,詳見代碼:
ClipSquareImageView.java
package com.h3c.androidclipsquare; import android.annotation.TargetApi; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Matrix; import android.graphics.RectF; import android.graphics.drawable.Drawable; import android.os.Build; import android.util.AttributeSet; import android.view.GestureDetector; import android.view.MotionEvent; import android.view.ScaleGestureDetector; import android.view.VelocityTracker; import android.view.View; import android.view.ViewConfiguration; import android.view.ViewTreeObserver; import android.widget.ImageView; /** * Created by H3c on 12/13/14. */ public class ClipSquareImageView extends ImageView implements View.OnTouchListener, ViewTreeObserver.OnGlobalLayoutListener { private static final int BORDERDISTANCE = ClipSquareView.BORDERDISTANCE; public static final float DEFAULT_MAX_SCALE = 4.0f; public static final float DEFAULT_MID_SCALE = 2.0f; public static final float DEFAULT_MIN_SCALE = 1.0f; private float minScale = DEFAULT_MIN_SCALE; private float midScale = DEFAULT_MID_SCALE; private float maxScale = DEFAULT_MAX_SCALE; private MultiGestureDetector multiGestureDetector; private boolean isIniting;// 正在初始化 private Matrix defaultMatrix = new Matrix();// 初始化的圖片矩陣,控制圖片撐滿屏幕及顯示區域 private Matrix dragMatrix = new Matrix();// 拖拽放大過程中動態的矩陣 private Matrix finalMatrix = new Matrix();// 最終顯示的矩陣 private final RectF displayRect = new RectF();// 圖片的真實大小 private final float[] matrixValues = new float[9]; private int borderlength; public ClipSquareImageView(Context context, AttributeSet attrs) { super(context, attrs); super.setScaleType(ScaleType.MATRIX); setOnTouchListener(this); multiGestureDetector = new MultiGestureDetector(context); } @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); getViewTreeObserver().addOnGlobalLayoutListener(this); } @SuppressWarnings(deprecation) @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); getViewTreeObserver().removeGlobalOnLayoutListener(this); } @Override public void onGlobalLayout() { if(isIniting) { return; } // 調整視圖位置 initBmpPosition(); } /** * 初始化圖片位置 */ private void initBmpPosition() { isIniting = true; super.setScaleType(ScaleType.MATRIX); Drawable drawable = getDrawable(); if(drawable == null) { return; } final float viewWidth = getWidth(); final float viewHeight = getHeight(); final int drawableWidth = drawable.getIntrinsicWidth(); final int drawableHeight = drawable.getIntrinsicHeight(); if(viewWidth < viewHeight) { borderlength = (int) (viewWidth - 2 * BORDERDISTANCE); } else { borderlength = (int) (viewHeight - 2 * BORDERDISTANCE); } float screenScale = 1f; // 小於屏幕的圖片會被撐滿屏幕 if(drawableWidth <= drawableHeight) {// 豎圖片 screenScale = (float) borderlength / drawableWidth; } else {// 橫圖片 screenScale = (float) borderlength / drawableHeight; } defaultMatrix.setScale(screenScale, screenScale); if(drawableWidth <= drawableHeight) {// 豎圖片 float heightOffset = (viewHeight - drawableHeight * screenScale) / 2.0f; if(viewWidth <= viewHeight) {// 豎照片豎屏幕 defaultMatrix.postTranslate(BORDERDISTANCE, heightOffset); } else {// 豎照片橫屏幕 defaultMatrix.postTranslate((viewWidth - borderlength) / 2.0f, heightOffset); } } else { float widthOffset = (viewWidth - drawableWidth * screenScale) / 2.0f; if(viewWidth <= viewHeight) {// 橫照片,豎屏幕 defaultMatrix.postTranslate(widthOffset, (viewHeight - borderlength) / 2.0f); } else {// 橫照片,橫屏幕 defaultMatrix.postTranslate(widthOffset, BORDERDISTANCE); } } resetMatrix(); } /** * Resets the Matrix back to FIT_CENTER, and then displays it.s */ private void resetMatrix() { if(dragMatrix == null) { return; } dragMatrix.reset(); setImageMatrix(getDisplayMatrix()); } private Matrix getDisplayMatrix() { finalMatrix.set(defaultMatrix); finalMatrix.postConcat(dragMatrix); return finalMatrix; } @Override public boolean onTouch(View view, MotionEvent motionEvent) { return multiGestureDetector.onTouchEvent(motionEvent); } private class MultiGestureDetector extends GestureDetector.SimpleOnGestureListener implements ScaleGestureDetector.OnScaleGestureListener { private final ScaleGestureDetector scaleGestureDetector; private final GestureDetector gestureDetector; private final float scaledTouchSlop; private VelocityTracker velocityTracker; private boolean isDragging; private float lastTouchX; private float lastTouchY; private float lastPointerCount;// 上一次是幾個手指事件 public MultiGestureDetector(Context context) { scaleGestureDetector = new ScaleGestureDetector(context, this); gestureDetector = new GestureDetector(context, this); gestureDetector.setOnDoubleTapListener(this); final ViewConfiguration configuration = ViewConfiguration.get(context); scaledTouchSlop = configuration.getScaledTouchSlop(); } @Override public boolean onScale(ScaleGestureDetector scaleGestureDetector) { float scale = getScale(); float scaleFactor = scaleGestureDetector.getScaleFactor(); if(getDrawable() != null && ((scale < maxScale && scaleFactor > 1.0f) || (scale > minScale && scaleFactor < 1.0f))){ if(scaleFactor * scale < minScale){ scaleFactor = minScale / scale; } if(scaleFactor * scale > maxScale){ scaleFactor = maxScale / scale; } dragMatrix.postScale(scaleFactor, scaleFactor, getWidth() / 2, getHeight() / 2); checkAndDisplayMatrix(); } return true; } @Override public boolean onScaleBegin(ScaleGestureDetector scaleGestureDetector) { return true; } @Override public void onScaleEnd(ScaleGestureDetector scaleGestureDetector) {} public boolean onTouchEvent(MotionEvent event) { if (gestureDetector.onTouchEvent(event)) { return true; } scaleGestureDetector.onTouchEvent(event); /* * Get the center x, y of all the pointers */ float x = 0, y = 0; final int pointerCount = event.getPointerCount(); for (int i = 0; i < pointerCount; i++) { x += event.getX(i); y += event.getY(i); } x = x / pointerCount; y = y / pointerCount; /* * If the pointer count has changed cancel the drag */ if (pointerCount != lastPointerCount) { isDragging = false; if (velocityTracker != null) { velocityTracker.clear(); } lastTouchX = x; lastTouchY = y; lastPointerCount = pointerCount; } switch (event.getAction()) { case MotionEvent.ACTION_DOWN: if (velocityTracker == null) { velocityTracker = VelocityTracker.obtain(); } else { velocityTracker.clear(); } velocityTracker.addMovement(event); lastTouchX = x; lastTouchY = y; isDragging = false; break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: lastPointerCount = 0; if (velocityTracker != null) { velocityTracker.recycle(); velocityTracker = null; } break; case MotionEvent.ACTION_MOVE: { final float dx = x - lastTouchX, dy = y - lastTouchY; if (isDragging == false) { // Use Pythagoras to see if drag length is larger than // touch slop isDragging = Math.sqrt((dx * dx) + (dy * dy)) >= scaledTouchSlop; } if (isDragging) { if (getDrawable() != null) { dragMatrix.postTranslate(dx, dy); checkAndDisplayMatrix(); } lastTouchX = x; lastTouchY = y; if (velocityTracker != null) { velocityTracker.addMovement(event); } } break; } } return true; } @Override public boolean onDoubleTap(MotionEvent event) { try { float scale = getScale(); float x = getWidth() / 2; float y = getHeight() / 2; if (scale < midScale) { post(new AnimatedZoomRunnable(scale, midScale, x, y)); } else if ((scale >= midScale) && (scale < maxScale)) { post(new AnimatedZoomRunnable(scale, maxScale, x, y)); } else {// 雙擊縮小小於最小值 post(new AnimatedZoomRunnable(scale, minScale, x, y)); } } catch (Exception e) { // Can sometimes happen when getX() and getY() is called } return true; } } private class AnimatedZoomRunnable implements Runnable { // These are 'postScale' values, means they're compounded each iteration static final float ANIMATION_SCALE_PER_ITERATION_IN = 1.07f; static final float ANIMATION_SCALE_PER_ITERATION_OUT = 0.93f; private final float focalX, focalY; private final float targetZoom; private final float deltaScale; public AnimatedZoomRunnable(final float currentZoom, final float targetZoom, final float focalX, final float focalY) { this.targetZoom = targetZoom; this.focalX = focalX; this.focalY = focalY; if (currentZoom < targetZoom) { deltaScale = ANIMATION_SCALE_PER_ITERATION_IN; } else { deltaScale = ANIMATION_SCALE_PER_ITERATION_OUT; } } @Override public void run() { dragMatrix.postScale(deltaScale, deltaScale, focalX, focalY); checkAndDisplayMatrix(); final float currentScale = getScale(); if (((deltaScale > 1f) && (currentScale < targetZoom)) || ((deltaScale < 1f) && (targetZoom < currentScale))) { // We haven't hit our target scale yet, so post ourselves // again postOnAnimation(ClipSquareImageView.this, this); } else { // We've scaled past our target zoom, so calculate the // necessary scale so we're back at target zoom final float delta = targetZoom / currentScale; dragMatrix.postScale(delta, delta, focalX, focalY); checkAndDisplayMatrix(); } } } @TargetApi(Build.VERSION_CODES.JELLY_BEAN) private void postOnAnimation(View view, Runnable runnable) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { view.postOnAnimation(runnable); } else { view.postDelayed(runnable, 16); } } /** * Returns the current scale value * * @return float - current scale value */ public final float getScale() { dragMatrix.getValues(matrixValues); return matrixValues[Matrix.MSCALE_X]; } /** * Helper method that simply checks the Matrix, and then displays the result */ private void checkAndDisplayMatrix() { checkMatrixBounds(); setImageMatrix(getDisplayMatrix()); } private void checkMatrixBounds() { final RectF rect = getDisplayRect(getDisplayMatrix()); if (null == rect) { return; } float deltaX = 0, deltaY = 0; final float viewWidth = getWidth(); final float viewHeight = getHeight(); // 判斷移動或縮放後,圖片顯示是否超出裁剪框邊界 final float heightBorder = (viewHeight - borderlength) / 2; final float weightBorder = (viewWidth - borderlength) / 2; if(rect.top > heightBorder){ deltaY = heightBorder - rect.top; } if(rect.bottom < (viewHeight - heightBorder)){ deltaY = viewHeight - heightBorder - rect.bottom; } if(rect.left > weightBorder){ deltaX = weightBorder - rect.left; } if(rect.right < viewWidth - weightBorder){ deltaX = viewWidth - weightBorder - rect.right; } // Finally actually translate the matrix dragMatrix.postTranslate(deltaX, deltaY); } /** * 獲取圖片相對Matrix的距離 * * @param matrix * - Matrix to map Drawable against * @return RectF - Displayed Rectangle */ private RectF getDisplayRect(Matrix matrix) { Drawable d = getDrawable(); if (null != d) { displayRect.set(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight()); matrix.mapRect(displayRect); return displayRect; } return null; } /** * 剪切圖片,返回剪切後的bitmap對象 * * @return */ public Bitmap clip(){ Bitmap bitmap = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(bitmap); draw(canvas); return Bitmap.createBitmap(bitmap, (getWidth() - borderlength) / 2, (getHeight() - borderlength) / 2, borderlength, borderlength); } }
效果預覽簡要說明現在android程序網絡請求操作是必不可少的,然而擁有好的交互體驗的程序對網絡耗時操作的處理尤為重要。代碼說明:dialog_loading.xml&l
前言在Android開發過程中,我發現很多安卓源代碼裡應用了設計模式,比較常用的有適配器模式(各種adapter),建造者模式(Alert Dialog的構建)等等。雖然
寫在前面最近一直在做畢設項目的准備工作,考慮到可能要用到一個模糊的效果,所以就學習了一些高斯模糊效果的實現。比較有名的就是 FastBlur 以及它衍生的一些優化方案,還
Android提供了一個工具類:AsyncTask,它使創建需要與用戶界面交互的長時間運行的任務變得更簡單。相對Handler來說AsyncTask更輕量級一些,適用於簡