Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> 自定義ImageView實現局部截圖功能

自定義ImageView實現局部截圖功能

編輯:關於Android編程

1、前言

最近在做一個能夠自選區域進行局部截圖的功能,接下來,會給大家講解,整個截圖的實現過程。筆者這邊實現的自選區域的形狀是矩形,讀者如果有需要,可以根據我給大家講解的思路,修改成適合自己的截圖工具。先來看看效果圖

2、效果圖

\

這裡的圖片是來自筆者對webView的截圖產生的,讀者可以根據自己的需要,替換上面的圖片。

通過拖拽四條邊框,可以實現屏幕的局部截圖:

\

拖拽之後,只有需要截圖的部分才會高亮顯示,其余部分用遮罩掩蓋。筆者實現的拖拽四條邊都可以任意拖拽,並不一定要正方形或者長方形。也可以如下:

\

3、截圖的基本知識

實現截圖功能之前,我們需要了解View為我們提供的截圖方法的使用。筆者會盡量詳細講述各個方法,讓讀者可以比較明白的去自由結合各截圖方法的使用。


3.1、截圖的基本知識點

在對View進行截圖的時候,系統需要將當前View的內容緩存下來,因此,在截圖之前,我們需要判斷當前View是否允許視圖緩存。

public boolean isDrawingCacheEnabled()

如果當前View允許進行視圖緩存,會返回true否則返回false。如果返回false,我們就需要設置當前的視圖,讓它允許視圖緩存。
 public void setDrawingCacheEnabled(boolean enabled)
通過設置true來設置當前View,讓它可以進行視圖緩存。當設置為true,那麼接下來調用getDrawingCache或者buildDrawingCache方法,就會給當前視圖的內容生成一個bitmap圖像。

給當前視圖強制生成緩存

public void buildDrawingCache(boolean autoScale)

此方法在視圖不可緩存的時候,會強制生成一個視圖緩存,autoScale表示是否自適應縮放緩存視圖,如果為false的生成的視圖大小和View的大小是一致的,true的話,則會根據內容生成自適應大小的圖片。但是如果在調用此方法的之前,你沒有調用setDrawingEnabled(true)的話,在對生成的視圖使用完畢之後,應該對強制生成的視圖緩存進行一個清理。清理的方法如下:
public void destroyDrawingCache()

 

此方法會將緩存的視圖內容進行清理,從而對bitmap的資源進行釋放回收利用。

獲取緩存圖像的方法:

public Bitmap getDrawingCache(boolean autoScale)
此方法會將View的視圖緩存生成一個bitmap對象並返回,這樣我們就可以對view的內容進行截圖了。autoscale參數和前面講述的功能一樣。

綜合上述知識,使用方法如下:

 if(!webView.isDrawingCacheEnabled())
             webView.setDrawingCacheEnabled(true);
        webView.buildDrawingCache();
        Bitmap cropBitmap = webView.getDrawingCache(true);


 


4、自定義View的知識點要求

由於我們將會對View的視圖內容進行部分截圖,所以需要我們自定義一個能夠監聽用戶動作從而產生對應響應的View。關於自定義View的基礎知識,讀者如果不明白的,可以看筆者的這篇文章自定義View的基本思路,如果讀者有這些基礎知識,那麼可以接著往下看。

先給一個下面講解會遇到的局部變量和常量的含義說明,這樣會比較容易理解下面的操作邏輯:

//移動過程的xy,imageView的canvas的寬高,點擊事件的寬高,源圖片的寬高。
    private float moveX,moveY,startWidth,startHeight,downX,downY,bitmapWidth,bitmapHeight;
    private Context context;
    //當前手機的像素密度
    private float density;
    //是否可以開始拖拽選擇框
    private boolean isStart=true;
    //分別表示上線,下線,左線,右線
    private Line upLine,downLine,leftLine,rightLine;
    //是否是上線移動,是否是垂直移動,是否是左線移動,是否是水平移動
    //垂直移動分為上線移動和下線移動,垂直移動同理
    private boolean isMovingUpLine=false,isMovingVertical=false,isMovingLeftLine=false,isMovingHorizontal=false;
   //用來給點擊點的坐標預留一些空間,便於判斷移動時間的判斷
    private float padding;
    private Paint paint = new Paint();

 

筆者將通過實現一個自定義ImageView,來實現一個可拖拽矩形邊框,對選中內容高亮顯示,對不選的內容進行遮罩掩飾。由於需要監聽用戶的觸摸行為,我們需要實現OnTouchListener接口,如下:

public class ClipImageView extends ImageView implements View.OnTouchListener

當ClipImageView第一次展示的時候,我們需要在它的四周畫上矩形邊框,如圖:

\

可以看到,在圖片和四周的邊框之間是有一定的留白的,便於分辨邊框和圖片的部分。所以我們需要重寫OnDraw方法:

 @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if(isStart&&moveY==0) {
            //初次布局,繪畫四周的邊線
            initCanvas(canvas);

        }
        else
        {
            //重畫邊線和遮罩
            reDraw(canvas);
        }
    }

我們關注iniCanvas方法即可,當前imageView如果是剛剛顯示出來並且沒有用戶觸摸動作的產生,就繪制四周的邊框。為什麼需要判斷是否有用戶觸摸動作呢,因為用戶產生觸摸動作的時候,View一定是處於可見生命周期,此時就不是第一次繪制視圖了,而是需要響應用戶觸摸行為來繪制視圖了。

 /**
     * 初始化布局
     * @param canvas
     */
    private void initCanvas(Canvas canvas) {
        //獲取畫布的寬高,用於初始化邊線的寬高
        startWidth = canvas.getWidth();
        startHeight = canvas.getHeight();
        upLine=new Line(0,0,startWidth,0);
        downLine=new Line(0, startHeight, startWidth, startHeight);
        leftLine=new Line(0,0,0,startHeight);
        rightLine=new Line(startWidth,0,startWidth,startHeight);
        Log.i("上線坐標",upLine.getLeft()+" "+upLine.getTop()+" "+upLine.getRight()+" "+upLine.getBottom() );
        Log.i("下線坐標",downLine.getLeft()+" "+downLine.getTop()+" "+downLine.getRight()+" "+downLine.getBottom() );
        Log.i("左線坐標",leftLine.getLeft()+" "+leftLine.getTop()+" "+leftLine.getRight()+" "+leftLine.getBottom());
        Log.i("右線坐標",rightLine.getLeft()+" "+rightLine.getTop()+" "+rightLine.getRight()+" "+rightLine.getBottom());
        //畫出上下左右四條線
        paint.setStrokeWidth(4);
        paint.setColor(Color.WHITE);
        //0-255,越小越透明
        paint.setAlpha(255);
        //畫邊線
        canvas.drawLine(0, 0, startWidth, 0, paint);//上
        canvas.drawLine(0, startHeight, startWidth, startHeight, paint);//下
        canvas.drawLine(0,0,0,startHeight,paint);//左
        canvas.drawLine(startWidth,0,startWidth,startHeight,paint);//右
    }
上述使用了paint和canvas進行繪制邊框,不懂得可以看我上面提到的博客。這裡面的Line是一個筆者自定義的bean對象,只是為了方便對用戶觸摸動作進行判斷產生的。如下:
package cn.com.chinaweal.share;

/**
 * Created by Myy on 2016/8/27.
 */
public class Line {

    private float left;//startX
    private float top;//startY
    private float right;//stopX
    private float bottom;//stopY


    public Line(float left, float top, float right, float bottom) {
        this.left = left;
        this.top = top;
        this.right = right;
        this.bottom = bottom;
    }

    public float getLeft() {
        return left;
    }

    public void setLeft(float left) {
        this.left = left;
    }

    public float getTop() {
        return top;
    }

    public void setTop(float top) {
        this.top = top;
    }

    public float getRight() {
        return right;
    }

    public void setRight(float right) {
        this.right = right;
    }

    public float getBottom() {
        return bottom;
    }

    public void setBottom(float bottom) {
        this.bottom = bottom;
    }
}

 


上面的解釋其實解釋為起始點和終止點更合適,因為它主要是用來記錄一條線的起點和重點的坐標的。

當我們繪制好邊框之後,就需要監聽用戶的觸摸行為了,這也是實現可拖拽ImageView最大的難點。裡面涉及的邏輯思維,是筆者通過調試一步一步得出來的,讀者如果不懂沒關系,可以自己按照自己的行為一步一步調試。代碼如下:

 /**
     * 監聽觸摸事件,這裡的邏輯是最復雜的
     * @param v
     * @param event
     * @return
     */
    @Override
    public boolean onTouch(View v, MotionEvent event) {
        if(event.getAction()==MotionEvent.ACTION_DOWN)
        {
            //記錄當前點擊的坐標,用於判斷用戶當前移動的是那條線以及用於產生移動距離
            downX=event.getX();
            downY=event.getY();
            //說明當前點擊的是上線,波動距離為上線上下20個像素點的范圍
            if((downY<=upLine.getTop()+padding)&&(downY>=upLine.getTop()-padding))
            {
                isMovingUpLine=true;//表示當前是上線被用戶拖拽
                isMovingVertical=true;//表示是垂直方向的移動
                isMovingHorizontal=false;//表示不是水平方向的移動
            }
            else if((downY<=downLine.getTop()+padding)&&(downY>=downLine.getTop()-padding))//下線
            {
                isMovingUpLine=false;
                isMovingVertical=true;
                isMovingHorizontal=false;
            }
            else if((downX<=leftLine.getLeft()+padding)&&(downX>=leftLine.getLeft()-padding))//左線
            {
                isMovingLeftLine=true;
                isMovingHorizontal=true;
                isMovingVertical=false;
            }
            else if((downX<=rightLine.getLeft()+padding)&&(downX>=rightLine.getLeft()-padding))//右線
            {
                isMovingLeftLine=false;
                isMovingHorizontal=true;
                isMovingVertical=false;
            }
            else
            {
                //如果不在任何一條邊線的拖動范圍,對用戶此次觸摸事件忽略
                isMovingHorizontal=false;
                isMovingVertical=false;
            }
            return  true;
        }
        if(event.getAction()==MotionEvent.ACTION_MOVE)
        {
            //產生移動距離,用於重新繪制邊線和遮罩
            moveX=event.getX()-downX;
            moveY=event.getY()-downY;
            //必須保存這次的移動坐標,以便下一次移動的時候可以判斷從上一次的移動坐標產生新的移動的距離
            downX=event.getX();
            downY=event.getY();
            if(isMovingUpLine&&isMovingVertical)
            {
                float detY=upLine.getTop()+moveY;
                //當移動的距離超過下線20個像素范圍或到達頂端的時候,需要進行特殊的處理。
                //否則就使用移動的距離。
                //上線必須處於下線上邊的20個像素點以上,避免交叉
                if (detY>=downLine.getTop()-padding)
                    detY=downLine.getTop()-padding;
                //上線不可超過頂端
                if(detY<=0)
                    detY=0;
                upLine.setTop(detY);
                upLine.setBottom(detY);
            }
            if(!isMovingUpLine&&isMovingVertical)
            {
                //下線的處理
                float detY=downLine.getTop()+moveY;
                if(detY<=upLine.getTop()+padding)//和上線保持20的像素點
                    detY=upLine.getTop()+padding;
                if(detY>=startHeight)//不可超過邊線
                    detY=startHeight;
                downLine.setTop(detY);
                downLine.setBottom(detY);
            }
            if(isMovingLeftLine&&isMovingHorizontal)
            {
                //左線的處理
                float detX=leftLine.getLeft()+moveX;
                if (detX>=rightLine.getLeft()-padding)
                    detX=rightLine.getLeft()-padding;
                if(detX<=0)
                    detX=0;
                leftLine.setLeft(detX);
                leftLine.setRight(detX);
            }
            if(!isMovingLeftLine&&isMovingHorizontal)
            {
                //右線的處理
                float detX=rightLine.getLeft()+moveX;
                if(detX<=leftLine.getLeft()+padding)//和上線保持20的像素點
                    detX=leftLine.getLeft()+padding;
                if(detX>=startWidth)//不可超過邊線
                    detX=startWidth;
                rightLine.setLeft(detX);
                rightLine.setRight(detX);
            }
            if(isMovingVertical||isMovingHorizontal)
               invalidate();
            Log.i("moveY",moveY+"");
            Log.i("isMovingVertical",isMovingVertical+"");
            Log.i("isMovingHorizontal",isMovingHorizontal+"");
            Log.i("上線",isMovingUpLine+"");
            Log.i("moveX",moveX+"");
            Log.i("左線",isMovingLeftLine+"");
            return true;
        }

        if(event.getAction()==MotionEvent.ACTION_UP)
        {

            return true;
        }
        return false;
    }


上述的觸摸邏輯比較復雜,筆者在注釋講了很清楚了,因為不涉及技術難點,所以就不展開講解,邏輯的疑惑還需要讀者自行去調試解惑。注意到,我們有在某個特點條件下調用了invalidate方法,這個方法是干嘛的呢?

此方法會導致當前View變得不可用,然後就會調用onDraw重新繪制View。此方法是運行在主線程中的,請注意,所以不要在onDraw裡面做耗時操作。其實不只是onDraw,任何View的方法都不應該進行耗時操作。android還提供了另外一個可以產生相同行為的方法,不同點是,它的執行實在非主線程中的,叫做postInvalidate。

ClipImageView的onDraw方法在前面提到過,現在要了解的是reDraw方法,此方法用於相應用戶觸摸行為,來重新繪制邊框的線和遮罩。如下:

\

方法如下:

  /**
     * 重新繪制矩形區域
     * @param canvas
     */
    private void reDraw(Canvas canvas) {
        isStart=false;
        paint.setStrokeWidth(4);
        //style有三種模式fill,stroke,fill_and_stroke三種。
        paint.setStyle(Paint.Style.FILL_AND_STROKE);
        paint.setColor(Color.WHITE);
        //畫出上線和下線左線右線
        //畫上線的時候,應該以左線作為x的起點的參考,以右線作為x的終點的參考。其余線都需要參考其他線,邏輯不列了
        canvas.drawLine(leftLine.getLeft(),upLine.getTop(),rightLine.getLeft(),upLine.getBottom(),paint);//上線
        canvas.drawLine(leftLine.getLeft(),downLine.getTop(),rightLine.getRight(),downLine.getBottom(),paint);//下線
        canvas.drawLine(leftLine.getLeft(),upLine.getTop(),leftLine.getRight(),downLine.getBottom(),paint);//左線
        canvas.drawLine(rightLine.getLeft(),upLine.getTop(),rightLine.getRight(),downLine.getBottom(),paint);//右線
        //畫出遮罩
        paint.setColor(Color.BLACK);
        //設置透明度,以至於下邊的內容不會完全被遮住導致不可見。
        paint.setAlpha(180);
        canvas.drawRect(0,0,upLine.getRight(),upLine.getBottom()-1,paint);//上邊遮罩
        canvas.drawRect(downLine.getLeft(),downLine.getTop()+1,startWidth,startHeight,paint);//下邊遮罩
        //畫左右遮罩需要照顧到上下遮罩,避免重新繪制已經存在遮罩的區域
        //3為的是消除遮罩之間的縫隙
        canvas.drawRect(0,upLine.getTop()+3,leftLine.getRight()-1,downLine.getTop()-3,paint);//左邊遮罩
        canvas.drawRect(rightLine.getLeft()+1,upLine.getTop()+3,startWidth,downLine.getTop()-3,paint);//右邊遮罩
        Log.i("上線坐標",upLine.getLeft()+" "+upLine.getTop()+" "+upLine.getRight()+" "+upLine.getBottom() );
        Log.i("下線坐標",downLine.getLeft()+" "+downLine.getTop()+" "+downLine.getRight()+" "+downLine.getBottom() );
        Log.i("左線坐標",leftLine.getLeft()+" "+leftLine.getTop()+" "+leftLine.getRight()+" "+leftLine.getBottom());
        Log.i("右線坐標",rightLine.getLeft()+" "+rightLine.getTop()+" "+rightLine.getRight()+" "+rightLine.getBottom());
    }


難點在於了解遮罩的繪畫邏輯和邊框的邏輯,其實在我們繪制上線的時候,x的起點坐標,必須和左線的x的坐標相同,這樣才能形成一個矩形區域。揮著遮罩的時候,我們把上半部分的遮罩全部參照上線為界限,下半部分參照下線為界限。而左邊部分,就以左線的X左邊到canvas的邊線為寬,高度則是以上線的y和下線的y為參考,這樣就不會導致四個角落被重復遮罩了。這樣說很晦澀難懂,讀者可以想象一下,如果不宜上線和下線為參考標准,而是以左線的xy會參考標准,是否會導致左上角和左下角的區域被遮罩繪制依次。而繪制上半部分的遮罩的時候,以上線的xy為標准,那麼就會繪制左上角有右上角的部分,這樣就導致左上角被繪制了兩次,從而整個畫面不協調。

到了這裡,基本實現了一個可拖拽的自由選擇的矩形區域的ImageView。那麼接下來,我們就緒截取部分圖片從而產生用戶所期望的部分截圖了。


5、截取部分圖案

我們需要對Bitmap對象進行部分截取圖案,需要了解下面這個方法:

public static Bitmap createBitmap(Bitmap source, int x, int y, int width, int height) 

source表示圖片源,x,y表示截圖的起始坐標,width,height表示截取的寬高。

因此,如果我們需要對用戶選中的圖案進行截圖,就需要獲取起始坐標和寬高。但因為,我們自定義的imagView的顯示的圖片的大小和顯示的bitmap源圖片的大小並不一致,因此我們需要對它進行一個比率轉換,將用戶選中的區域轉換成源bitmap的真實區域。代碼如下:

 /**
     * 獲取截圖的范圍
     * @return
     */
    public Line getClipLine()
    {
        Bitmap bitmap=((ClipActivity)context).getBitmap();
        bitmapWidth=bitmap.getWidth();//獲取源圖片的寬高
        bitmapHeight=bitmap.getHeight();

        //計算當前顯示的圖片和原始圖片的縮放比例
        float xRatio=bitmapWidth/(startWidth-getPaddingRight()-getPaddingLeft());
        float yRatio=bitmapHeight/(startHeight-getPaddingTop()-getPaddingBottom());
        //判斷截圖的XY坐標,如果小於0,則認為截取的圖片XY是從0開始
        float clipX=(leftLine.getLeft()-getPaddingLeft())<=0?0:leftLine.getLeft()-getPaddingLeft();
        float clipY=(upLine.getTop()-getPaddingTop())<=0?0:upLine.getTop()-getPaddingTop();
        //獲得截取的圖片XY轉換成原始圖片的XY坐標
        float x=clipX*xRatio;
        float y=clipY*yRatio;
        //獲得截取圖片的寬高
        float clipWidth=(rightLine.getRight()-(startWidth-getPaddingRight())>=0?startWidth-getPaddingRight()-getPaddingLeft():rightLine.getRight())-clipX;
        float clipHeight=(downLine.getBottom()-(startHeight-getPaddingBottom())>=0?startHeight-getPaddingBottom()-getPaddingTop():downLine.getBottom())-clipY;
        //准換成原始圖片的寬高
        float width=clipWidth*xRatio;
        float height=clipHeight*yRatio;

        Line line=new Line(x,y,width,height);
        return line;
    }

上述比較重要的是,對於周邊padding部分的忽略,因為用於截取的圖片是在圖片區域內,並不包含外部縫隙。而我們為了方便描繪邊框,添加了外部縫隙,所以這些要忽略掉。

上述是整個ClipImageView的實現過程。接下來,整個截圖完整流程的核心代碼部分,讀者自行參考:

ClipActivity的資源文件:




    

 

ClipActivity:

package cn.com.chinaweal.share;


import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;

/**
 * 圖像截圖
 * Created by Myy on 2016/8/27.
 */
public class ClipActivity extends AppCompatActivity {

    private ClipImageView imageView;
    private Line line;
    private Bitmap bitmap;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.line_layout);
        imageView = (ClipImageView) findViewById(R.id.imageView);
        bitmap = BitmapFactory.decodeFile(Constants.path);
        imageView.setImageBitmap(bitmap);
    }

    public void sure(View view) {
        line = imageView.getClipLine();
        Log.i("結果", line.getLeft() + " " + line.getTop() + " " + line.getRight() + " " + line.getBottom());
        bitmap=bitmap.createBitmap(bitmap, (int)line.getLeft(),(int)line.getTop(),(int)line.getRight(),(int)line.getBottom());//截取圖片
        Constants.saveBitmap(bitmap);//保存截取圖片
        setResult(RESULT_OK);
        finish();
    }


    /**
     * 獲取圖片
     * @return
     */
    public Bitmap getBitmap()
    {
        return  bitmap;
    }

    public void cancel(View view)
    {
        finish();
    }


}

 

Constans:

package cn.com.chinaweal.share;

import android.graphics.Bitmap;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;

/**
 * Created by Myy on 2016/8/29.
 */
public class Constants {

    public static String path="/sdcard/ic_launcher.png";//文件路徑



    /**
     * 保存圖片
     * @param cropBitmap
     */
    public static void saveBitmap(Bitmap cropBitmap) {
        File file = new File(Constants.path);
        FileOutputStream os=null;
        if (file.exists())
            file.delete();
        try {
            file.createNewFile();
            os = new FileOutputStream(file);
            cropBitmap.compress(Bitmap.CompressFormat.PNG, 100, os);
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            try {
                os.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

}

 

clipImageView:

package cn.com.chinaweal.share;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.widget.ImageView;

/**
 * Created by Myy on 2016/8/27.
 */
public class ClipImageView extends ImageView implements View.OnTouchListener{

    //移動過程的xy,imageView的canvas的寬高,點擊事件的寬高,源圖片的寬高。
    private float moveX,moveY,startWidth,startHeight,downX,downY,bitmapWidth,bitmapHeight;
    private Context context;
    //當前手機的像素密度
    private float density;
    //是否可以開始拖拽選擇框
    private boolean isStart=true;
    //分別表示上線,下線,左線,右線
    private Line upLine,downLine,leftLine,rightLine;
    //是否是上線移動,是否是垂直移動,是否是左線移動,是否是水平移動
    //垂直移動分為上線移動和下線移動,垂直移動同理
    private boolean isMovingUpLine=false,isMovingVertical=false,isMovingLeftLine=false,isMovingHorizontal=false;
   //用來給點擊點的坐標預留一些空間,便於判斷移動時間的判斷
    private float padding;
    private Paint paint = new Paint();
    public ClipImageView(Context context) {
        super(context);
        init(context);
    }

    public ClipImageView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context);

    }

    private void init(Context context)
    {
        this.context=context;
        density=ScreenUtils.getDensity(context);
        //默認事件產生的波動距離是20個像素點
        padding=density*20;
        //監聽動作,產生移動事件
        this.setOnTouchListener(this);

    }
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if(isStart&&moveY==0) {
            //初次布局,繪畫四周的邊線
            initCanvas(canvas);

        }
        else
        {
            //重畫邊線和遮罩
            reDraw(canvas);
        }
    }

    /**
     * 重新繪制矩形區域
     * @param canvas
     */
    private void reDraw(Canvas canvas) {
        isStart=false;
        paint.setStrokeWidth(4);
        //style有三種模式fill,stroke,fill_and_stroke三種。
        paint.setStyle(Paint.Style.FILL_AND_STROKE);
        paint.setColor(Color.WHITE);
        //畫出上線和下線左線右線
        //畫上線的時候,應該以左線作為x的起點的參考,以右線作為x的終點的參考。其余線都需要參考其他線,邏輯不列了
        canvas.drawLine(leftLine.getLeft(),upLine.getTop(),rightLine.getLeft(),upLine.getBottom(),paint);//上線
        canvas.drawLine(leftLine.getLeft(),downLine.getTop(),rightLine.getRight(),downLine.getBottom(),paint);//下線
        canvas.drawLine(leftLine.getLeft(),upLine.getTop(),leftLine.getRight(),downLine.getBottom(),paint);//左線
        canvas.drawLine(rightLine.getLeft(),upLine.getTop(),rightLine.getRight(),downLine.getBottom(),paint);//右線
        //畫出遮罩
        paint.setColor(Color.BLACK);
        //設置透明度,以至於下邊的內容不會完全被遮住導致不可見。
        paint.setAlpha(180);
        canvas.drawRect(0,0,upLine.getRight(),upLine.getBottom()-1,paint);//上邊遮罩
        canvas.drawRect(downLine.getLeft(),downLine.getTop()+1,startWidth,startHeight,paint);//下邊遮罩
        //畫左右遮罩需要照顧到上下遮罩本
        //3為的是消除遮罩之間的縫隙
        canvas.drawRect(0,upLine.getTop()+3,leftLine.getRight()-1,downLine.getTop()-3,paint);//左邊遮罩
        canvas.drawRect(rightLine.getLeft()+1,upLine.getTop()+3,startWidth,downLine.getTop()-3,paint);//右邊遮罩
        Log.i("上線坐標",upLine.getLeft()+" "+upLine.getTop()+" "+upLine.getRight()+" "+upLine.getBottom() );
        Log.i("下線坐標",downLine.getLeft()+" "+downLine.getTop()+" "+downLine.getRight()+" "+downLine.getBottom() );
        Log.i("左線坐標",leftLine.getLeft()+" "+leftLine.getTop()+" "+leftLine.getRight()+" "+leftLine.getBottom());
        Log.i("右線坐標",rightLine.getLeft()+" "+rightLine.getTop()+" "+rightLine.getRight()+" "+rightLine.getBottom());
    }

    /**
     * 初始化布局
     * @param canvas
     */
    private void initCanvas(Canvas canvas) {
        //獲取畫布的寬高,用於初始化邊線的距離
        startWidth = canvas.getWidth();
        startHeight = canvas.getHeight();
        upLine=new Line(0,0,startWidth,0);
        downLine=new Line(0, startHeight, startWidth, startHeight);
        leftLine=new Line(0,0,0,startHeight);
        rightLine=new Line(startWidth,0,startWidth,startHeight);
        Log.i("上線坐標",upLine.getLeft()+" "+upLine.getTop()+" "+upLine.getRight()+" "+upLine.getBottom() );
        Log.i("下線坐標",downLine.getLeft()+" "+downLine.getTop()+" "+downLine.getRight()+" "+downLine.getBottom() );
        Log.i("左線坐標",leftLine.getLeft()+" "+leftLine.getTop()+" "+leftLine.getRight()+" "+leftLine.getBottom());
        Log.i("右線坐標",rightLine.getLeft()+" "+rightLine.getTop()+" "+rightLine.getRight()+" "+rightLine.getBottom());
        //畫出上下左右兩條線
        paint.setStrokeWidth(4);
        paint.setColor(Color.WHITE);
        //0-255,越小越透明
        paint.setAlpha(255);
        //畫邊線
        canvas.drawLine(0, 0, startWidth, 0, paint);
        canvas.drawLine(0, startHeight, startWidth, startHeight, paint);
        canvas.drawLine(0,0,0,startHeight,paint);
        canvas.drawLine(startWidth,0,startWidth,startHeight,paint);
    }



    /**
     * 監聽觸摸事件,這裡的邏輯是最復雜的
     * @param v
     * @param event
     * @return
     */
    @Override
    public boolean onTouch(View v, MotionEvent event) {
        if(event.getAction()==MotionEvent.ACTION_DOWN)
        {
            //記錄當前點擊的坐標,用於判斷用戶當前移動的是那條線以及用於產生移動距離
            downX=event.getX();
            downY=event.getY();
            //說明當前點擊的是上線,波動距離為上線上下20個像素點的范圍
            if((downY<=upLine.getTop()+padding)&&(downY>=upLine.getTop()-padding))
            {
                isMovingUpLine=true;//表示當前是上線被用戶拖拽
                isMovingVertical=true;//表示是垂直方向的移動
                isMovingHorizontal=false;//表示不是水平方向的移動
            }
            else if((downY<=downLine.getTop()+padding)&&(downY>=downLine.getTop()-padding))
            {
                isMovingUpLine=false;
                isMovingVertical=true;
                isMovingHorizontal=false;
            }
            else if((downX<=leftLine.getLeft()+padding)&&(downX>=leftLine.getLeft()-padding))
            {
                isMovingLeftLine=true;
                isMovingHorizontal=true;
                isMovingVertical=false;
            }
            else if((downX<=rightLine.getLeft()+padding)&&(downX>=rightLine.getLeft()-padding))
            {
                isMovingLeftLine=false;
                isMovingHorizontal=true;
                isMovingVertical=false;
            }
            else
            {
                //如果不在任何一條邊線的拖動范圍,對用戶此次觸摸事件忽略
                isMovingHorizontal=false;
                isMovingVertical=false;
            }
            return  true;
        }
        if(event.getAction()==MotionEvent.ACTION_MOVE)
        {
            //產生移動距離,用於重新繪制邊線和遮罩
            moveX=event.getX()-downX;
            moveY=event.getY()-downY;
            //必須保存這次的移動坐標,以便下一次移動的時候可以判斷從上一次的移動坐標產生新的移動的距離
            downX=event.getX();
            downY=event.getY();
            if(isMovingUpLine&&isMovingVertical)
            {
                float detY=upLine.getTop()+moveY;
                //當移動的距離超過下線20個像素范圍或到達頂端的時候,需要進行特殊的處理。
                //否則就使用移動的距離。
                //上線必須處於下線上邊的20個像素點以上
                if (detY>=downLine.getTop()-padding)
                    detY=downLine.getTop()-padding;
                //上線不可超過頂端
                if(detY<=0)
                    detY=0;
                upLine.setTop(detY);
                upLine.setBottom(detY);
            }
            if(!isMovingUpLine&&isMovingVertical)
            {
                //下線的處理
                float detY=downLine.getTop()+moveY;
                if(detY<=upLine.getTop()+padding)//和上線保持20的像素點
                    detY=upLine.getTop()+padding;
                if(detY>=startHeight)//不可超過邊線
                    detY=startHeight;
                downLine.setTop(detY);
                downLine.setBottom(detY);
            }
            if(isMovingLeftLine&&isMovingHorizontal)
            {
                //左線的處理
                float detX=leftLine.getLeft()+moveX;
                if (detX>=rightLine.getLeft()-padding)
                    detX=rightLine.getLeft()-padding;
                if(detX<=0)
                    detX=0;
                leftLine.setLeft(detX);
                leftLine.setRight(detX);
            }
            if(!isMovingLeftLine&&isMovingHorizontal)
            {
                //右線的處理
                float detX=rightLine.getLeft()+moveX;
                if(detX<=leftLine.getLeft()+padding)//和上線保持20的像素點
                    detX=leftLine.getLeft()+padding;
                if(detX>=startWidth)//不可超過邊線
                    detX=startWidth;
                rightLine.setLeft(detX);
                rightLine.setRight(detX);
            }
            if(isMovingVertical||isMovingHorizontal)
               invalidate();
            Log.i("moveY",moveY+"");
            Log.i("isMovingVertical",isMovingVertical+"");
            Log.i("isMovingHorizontal",isMovingHorizontal+"");
            Log.i("上線",isMovingUpLine+"");
            Log.i("moveX",moveX+"");
            Log.i("左線",isMovingLeftLine+"");
            return true;
        }

        if(event.getAction()==MotionEvent.ACTION_UP)
        {

            return true;
        }
        return false;
    }

    /**
     * 獲取截圖的范圍
     * @return
     */
    public Line getClipLine()
    {
        Bitmap bitmap=((ClipActivity)context).getBitmap();
        bitmapWidth=bitmap.getWidth();
        bitmapHeight=bitmap.getHeight();

        //計算當前顯示的圖片和原始圖片的縮放比例
        float xRatio=bitmapWidth/(startWidth-getPaddingRight()-getPaddingLeft());
        float yRatio=bitmapHeight/(startHeight-getPaddingTop()-getPaddingBottom());
        //判斷截圖的XY坐標,如果小於0,則認為截取的圖片XY是從0開始
        float clipX=(leftLine.getLeft()-getPaddingLeft())<=0?0:leftLine.getLeft()-getPaddingLeft();
        float clipY=(upLine.getTop()-getPaddingTop())<=0?0:upLine.getTop()-getPaddingTop();
        //獲得截取的圖片XY轉換成原始圖片的XY坐標
        float x=clipX*xRatio;
        float y=clipY*yRatio;
        //獲得截取圖片的寬高
        float clipWidth=(rightLine.getRight()-(startWidth-getPaddingRight())>=0?startWidth-getPaddingRight()-getPaddingLeft():rightLine.getRight())-clipX;
        float clipHeight=(downLine.getBottom()-(startHeight-getPaddingBottom())>=0?startHeight-getPaddingBottom()-getPaddingTop():downLine.getBottom())-clipY;
        //准換成原始圖片的寬高
        float width=clipWidth*xRatio;
        float height=clipHeight*yRatio;

        Line line=new Line(x,y,width,height);
        return line;
    }
}

效果圖就不列出來了,這些源碼是筆者自己項目需要寫的demo,不方便全部展示,但是邏輯流程可以照搬。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved