天天看點

Android控制圖檔在螢幕内縮放和移動一、需求二、自定義ZoomImageView三、ImageLoadUtils圖檔加載類四、調用五、最終效果

通常我們遇到的圖檔縮放需求,都是圖檔基于螢幕自适應後,進行縮放和移動,且圖檔最小隻能是自适應的大小。最近遇到一個需求,要求圖檔隻能在螢幕内縮放和移動,不能超出螢幕。

一、需求

在螢幕中加載一張圖檔,圖檔可以手勢縮放移動。但是圖檔最大隻能縮放到螢幕大小,也隻允許在螢幕内移動。可以從系統中讀取圖檔(通過絕對路徑),也可以從資源檔案中讀取圖檔。

Android控制圖檔在螢幕内縮放和移動一、需求二、自定義ZoomImageView三、ImageLoadUtils圖檔加載類四、調用五、最終效果

二、自定義ZoomImageView

螢幕内手勢縮放圖檔與普通的圖檔縮放相比,比較麻煩的是,需要計算圖檔的精确位置。不同于普通縮放的圖檔充滿螢幕,屏内縮放的圖檔隻占據螢幕的一部分,我們需要判斷手指是否點在圖檔内,才能進行各種操作。

/**
     * 判斷手指是否點在圖檔内(單指)
     */
    private void isClickInImage(){
        if (translationX <= mFirstX && mFirstX <= (translationX + currentW)
                && translationY <= mFirstY && mFirstY <= (translationY + currentH)){
            isClickInImage = true;
        }else {
            isClickInImage = false;
        }
    }

    /**
     * 判斷手指是否點在圖檔内(雙指)
     * 隻要有一隻手指在圖檔内就為true
     * @param event
     */
    private void isClickInImage(MotionEvent event){
        if (translationX <= event.getX(0) && event.getX(0) <= (translationX + currentW)
                && translationY <= event.getY(0) && event.getY(0) <= (translationY + currentH)){
            isClickInImage = true;
        }else if (translationX <= event.getX(1) && event.getX(1) <= (translationX + currentW)
                && translationY <= event.getY(1) && event.getY(1) <= (translationY + currentH)){
            isClickInImage = true;
        }else {
            isClickInImage = false;
        }
    }
           

其他的各種操作,之于縮放,移動,邊界檢查等,和普通的圖檔縮放沒有太多差別。完整代碼如下:

package com.uni.myapplication;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;

import java.io.File;

/**
 * Created by newcboy on 2018/3/9.
 */

public class ZoomImageView extends View {

    public static final int IMAGE_MAX_SIZE = 1000;//加載圖檔允許的最大size,機關kb
    private float minimal = 100.0f;

    private float screenW;//螢幕寬度
    private float screenH;//螢幕高度

    //單指按下的坐标
    private float mFirstX = 0.0f;
    private float mFirstY = 0.0f;

    //單指離開的坐标
    private float lastMoveX =-1f;
    private float lastMoveY =-1f;

    //兩指的中點坐标
    private float centPointX;
    private float centPointY;

    //圖檔的繪制坐标
    private float translationX = 0.0f;
    private float translationY = 0.0f;

    //圖檔的原始寬高
    private float primaryW;
    private float primaryH;

    //圖檔目前寬高
    private float currentW;
    private float currentH;

    private float scale = 1.0f;
    private float maxScale, minScale;
    private Bitmap bitmap;
    private Matrix matrix;

    private int mLocker = 0;
    private float fingerDistance = 0.0f;

    private boolean isLoaded = false;
    private boolean isClickInImage = false;

    public ZoomImageView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }


    /**
     * 從資源檔案中讀取圖檔
     * @param context
     * @param imageId
     */
    public void setResourceBitmap(Context context, int imageId){
        bitmap = BitmapFactory.decodeResource(context.getResources(), imageId);
        isLoaded = true;
        primaryW = bitmap.getWidth();
        primaryH = bitmap.getHeight();
        matrix = new Matrix();
    }

    /**
     * 根據路徑添加圖檔
     * @param path
     * @param scale
     */
    public void setImagePathBitmap(String path, float scale){
        this.scale = scale;
        setImageBitmap(path);
    }

    private void setImageBitmap(String path){
        File file = new File(path);
        if (file.exists()){
            isLoaded = true;
            bitmap = ImageLoadUtils.getImageLoadBitmap(path, IMAGE_MAX_SIZE);
            primaryW = bitmap.getWidth();
            primaryH = bitmap.getHeight();
            matrix = new Matrix();
        }else {
            isLoaded = false;
        }
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        if (changed){
            screenW = getWidth();
            screenH = getHeight();
            translationX = (screenW - bitmap.getWidth() * scale)/  2;
            translationY = (screenH - bitmap.getHeight() * scale) / 2;
            setMaxMinScale();
        }
    }

    /**
     *
     */
    private void setMaxMinScale(){
        float xScale, yScale;

        xScale = minimal / primaryW;
        yScale = minimal / primaryH;
        minScale = xScale > yScale ? xScale : yScale;

        xScale = primaryW / screenW;
        yScale = primaryH / screenH;
        if (xScale > 1 || yScale > 1 ) {
            if (xScale > yScale) {
                maxScale = 1/xScale;
            }else {
                maxScale = 1/yScale;
            }
        }else {
            if (xScale > yScale) {
                maxScale = 1/xScale;
            }else {
                maxScale = 1/yScale;
            }
        }
        if (isScaleError()){
            restoreAction();
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (!isLoaded){
            return true;
        }
        switch (event.getActionMasked()){
            case MotionEvent.ACTION_DOWN:
                mFirstX = event.getX();
                mFirstY = event.getY();
                isClickInImage();
                break;
            case MotionEvent.ACTION_POINTER_DOWN:
                fingerDistance = getFingerDistance(event);
                isClickInImage(event);
                break;
            case MotionEvent.ACTION_MOVE:
                float fingerNum = event.getPointerCount();
                if (fingerNum == 1 && mLocker == 0 && isClickInImage){
                    movingAction(event);
                }else if (fingerNum == 2 && isClickInImage){
                    zoomAction(event);
                }
                break;
            case MotionEvent.ACTION_POINTER_UP:
                mLocker = 1;
                if (isScaleError()){
                    translationX = (event.getX(1) + event.getX(0)) / 2;
                    translationY = (event.getY(1) + event.getY(0)) / 2;
                }
                break;
            case MotionEvent.ACTION_UP:
                lastMoveX = -1;
                lastMoveY = -1;
                mLocker = 0;
                if (isScaleError()){
                    restoreAction();
                }
                break;
        }
        return true;
    }


    /**
     * 移動操作
     * @param event
     */
    private void movingAction(MotionEvent event){
        float moveX = event.getX();
        float moveY = event.getY();
        if (lastMoveX == -1 || lastMoveY == -1) {
            lastMoveX = moveX;
            lastMoveY = moveY;
        }
        float moveDistanceX = moveX - lastMoveX;
        float moveDistanceY = moveY - lastMoveY;
        translationX = translationX + moveDistanceX;
        translationY = translationY + moveDistanceY;
        lastMoveX = moveX;
        lastMoveY = moveY;
        invalidate();
    }

    /**
     * 縮放操作
     * @param event
     */
    private void zoomAction(MotionEvent event){
        midPoint(event);
        float currentDistance = getFingerDistance(event);
        if (Math.abs(currentDistance - fingerDistance) > 1f) {
            float moveScale = currentDistance / fingerDistance;
            scale = scale * moveScale;
            translationX = translationX * moveScale + centPointX * (1-moveScale);
            translationY = translationY * moveScale + centPointY * (1-moveScale);
            fingerDistance = currentDistance;
            invalidate();
        }
    }

    /**
     * 圖檔恢複到指定大小
     */
    private void restoreAction(){
        if (scale < minScale){
            scale  = minScale;
        }else if (scale > maxScale){
            scale = maxScale;
        }
        translationX = translationX - bitmap.getWidth()*scale / 2;
        translationY = translationY - bitmap.getHeight()*scale / 2;
        invalidate();
    }


    /**
     * 判斷手指是否點在圖檔内(單指)
     */
    private void isClickInImage(){
        if (translationX <= mFirstX && mFirstX <= (translationX + currentW)
                && translationY <= mFirstY && mFirstY <= (translationY + currentH)){
            isClickInImage = true;
        }else {
            isClickInImage = false;
        }
    }

    /**
     * 判斷手指是否點在圖檔内(雙指)
     * 隻要有一隻手指在圖檔内就為true
     * @param event
     */
    private void isClickInImage(MotionEvent event){
        if (translationX <= event.getX(0) && event.getX(0) <= (translationX + currentW)
                && translationY <= event.getY(0) && event.getY(0) <= (translationY + currentH)){
            isClickInImage = true;
        }else if (translationX <= event.getX(1) && event.getX(1) <= (translationX + currentW)
                && translationY <= event.getY(1) && event.getY(1) <= (translationY + currentH)){
            isClickInImage = true;
        }else {
            isClickInImage = false;
        }
    }


    /**
     * 擷取兩指間的距離
     * @param event
     * @return
     */
    private float getFingerDistance(MotionEvent event){
        float x = event.getX(1) - event.getX(0);
        float y = event.getY(1) - event.getY(0);
        return (float) Math.sqrt(x * x + y * y);
    }

    /**
     * 判斷圖檔大小是否符合要求
     * @return
     */
    private boolean isScaleError(){
        if (scale > maxScale
                || scale < minScale){
            return true;
        }
        return false;
    }


    /**
     * 擷取兩指間的中點坐标
     * @param event
     */
    private void midPoint(MotionEvent event){
        centPointX = (event.getX(1) + event.getX(0))/2;
        centPointY = (event.getY(1) + event.getY(0))/2;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (isLoaded){
            imageZoomView(canvas);
        }
    }

    private void imageZoomView(Canvas canvas){
        currentW = primaryW * scale;
        currentH = primaryH * scale;
        matrix.reset();
        matrix.postScale(scale, scale);//x軸y軸縮放
        peripheryJudge();
        matrix.postTranslate(translationX, translationY);//中點坐标移動
        canvas.drawBitmap(bitmap, matrix, null);
    }

    /**
     * 圖檔邊界檢查
     * (隻在螢幕内)
     */
    private void peripheryJudge(){
        if (translationX < 0){
            translationX = 0;
        }
        if (translationY < 0){
            translationY = 0;
        }
        if ((translationX + currentW) > screenW){
            translationX = screenW - currentW;
        }
        if ((translationY + currentH) > screenH){
            translationY = screenH - currentH;
        }
    }

}
           

實際上,用Bitmap繪制圖檔時,可以通過Paint設定圖檔透明度。

Paint paint = new Paint();
paint.setStyle( Paint.Style.STROKE);
paint.setAlpha(150);
           

在setAlpha()中傳入一個0~255的整數。數字越大,透明度越低。

然後在繪制圖檔時

canvas.drawBitmap(bitmap, matrix, paint);

三、ImageLoadUtils圖檔加載類

這個類是對傳入的圖檔進行壓縮處理的類,在應用從系統中讀取圖檔時用到。在寫這個類時,發現一些和網上說法不一樣的地方。

options.inSampleSize這個屬性,網上的說法是必須是2的幂次方,但實際上,我的驗證結果是所有的整數都可以。

這裡采用的壓縮方法是,擷取系統剩餘記憶體和圖檔大小,然後将圖檔壓縮到合适的大小。

package com.uni.myapplication;

import android.app.ActivityManager;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.BitmapFactory.Options;
import android.net.Uri;

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

/**
 * 圖檔加載工具類
 *
 * Created by newcboy on 2018/1/25.
 */

public class ImageLoadUtils {

    /**
     * 原圖加載,根據傳入的指定圖檔大小。
     * @param imagePath
     * @param maxSize
     * @return
     */
    public static Bitmap getImageLoadBitmap(String imagePath, int maxSize){
        int fileSize = 1;
        Bitmap bitmap = null;
        int simpleSize = 1;
        File file = new File(imagePath);
        if (file.exists()) {
            Uri imageUri = Uri.parse(imagePath);
            try {
                fileSize = (int) (getFileSize(file) / 1024);
            } catch (Exception e) {
                e.printStackTrace();
            }
            Options options = new Options();
            if (fileSize > maxSize){
                for (simpleSize = 2; fileSize>= maxSize; simpleSize++){
                    fileSize = fileSize / simpleSize;
                }
            }
            options.inSampleSize = simpleSize;
            bitmap = BitmapFactory.decodeFile(imageUri.getPath(), options);
        }
        return bitmap;
    }


    /**
     * 擷取指定檔案的大小
     * @param file
     * @return
     * @throws Exception
     */
    public static long getFileSize(File file) throws Exception{
        if(file == null) {
            return 0;
        }
        long size = 0;
        if(file.exists()) {
            FileInputStream mInputStream = new FileInputStream(file);
            size = mInputStream.available();
        }
        return size;
    }


    /**
     * 擷取手機運作記憶體
     * @param context
     * @return
     */
    public static long getTotalMemorySize(Context context){
        long size = 0;
        ActivityManager activityManager = (ActivityManager) context.getSystemService(context.ACTIVITY_SERVICE);
        ActivityManager.MemoryInfo outInfo = new ActivityManager.MemoryInfo();//outInfo對象裡面包含了記憶體相關的資訊
        activityManager.getMemoryInfo(outInfo);//把記憶體相關的資訊傳遞到outInfo裡面C++思想
        //size = outInfo.totalMem;  //總記憶體
        size = outInfo.availMem;    //剩餘記憶體
        return (size/1024/1024);
    }

}
           

四、調用

使用方法和通常的控件差不多,隻是多了一個設定圖檔的方法。

1.在布局檔案中添加布局。

<com.uni.myapplication.ZoomImageView
        android:id="@+id/zoom_image_view"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
           

2.在代碼中調用

zoomImageView = (ZoomImageView) findViewById(R.id.zoom_image_view);
zoomImageView.setImagePathBitmap(MainActivity.this, imagePath, 1.0f);
zoomImageView.setResourceBitmap(MainActivity.this, R.mipmap.ic_launcher);
           
其中setImagePathBitmap()是從系統中讀取圖檔加載的方法,setResourceBitmap()是從資源檔案中讀取圖檔的方法。
           

當然,從系統讀取圖檔需要添加讀寫權限,這個不能忘了。而且6.0以上的系統需要動态擷取權限。動态擷取權限的方法這裡就不介紹了,網上有很詳細的說明。

五、最終效果

Android控制圖檔在螢幕内縮放和移動一、需求二、自定義ZoomImageView三、ImageLoadUtils圖檔加載類四、調用五、最終效果