android自定義View之頭像裁剪控件

近來做項目,發現自己項目裏並沒有對用戶上傳頭像做裁剪處理,目前需求也比較少,於是就有了這個裁剪控件的誕生。網友們寫了很多類似的控件,但是用着別人的總是感覺不舒服,考慮到簡單實用的原則就自己實現了下。

自定義View沒有gif效果圖的博客都是扯蛋(個人覺得沒有看下去的必要),所以先看實現效果再說:

首先思路:控件繼承自ImageView

1.繪製周邊透明陰影

2.上下左右拉伸移動,四個角的拉伸移動

3.計算邊距和裁剪大小,重新繪製

4.使用的話只需要調用clip()方法即可,返回選擇區域裁剪後的位圖bitmap對象

代碼思路都很簡單,因爲是前景透明度陰影,所以我直接在 onDrawForeground(Canvas canvas)方法中繪製,另外默認底色背景是在onDraw(Canvas canvas)方法super.onDrawForeground(canvas);語句調用前繪製。如下代碼示:

package com.example.myapplication.coustom;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Region;
import android.support.v7.widget.AppCompatImageView;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import com.example.myapplication.R;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;

/**
 * email:[email protected]
 * Created by gold on 2019/11/14
 * Describe:
 **/
public class PickImageView extends AppCompatImageView {
    public PickImageView(Context context) {
        this(context, null);
    }

    public PickImageView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public PickImageView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }


    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int sizeW = MeasureSpec.getSize(widthMeasureSpec);
        int sizeH = MeasureSpec.getSize(heightMeasureSpec);
        Log.e("+++++sizeW=", sizeW + "===" + sizeH);
        setMeasuredDimension(sizeW, sizeH);
    }

    private int w, h;//控件寬高
    private boolean isFirst = true;//第一次獲取寬高

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        if (isFirst) {
            isFirst = false;
            w = r - l;
            h = b - t;
            movePadding = w / 8;
            region.set(0, 0, w, h);
            clipW = clipH = w / 2;
            paddingLeft = paddingRight = w / 4;
            paddingTop = paddingBottom = (h - clipH) / 2;
        }
    }

    private Paint paint;//畫筆
    private Region region;//整個範圍
    private Region regionL;//左邊界區域
    private Region regionR;//右邊界區域
    private Region regionT;//上邊界區域
    private Region regionB;//下邊界區域
    private Region regionInner;//裁剪內部邊界區域
    //下面是對應的Path對象
    private Path pathLeft;
    private Path pathRight;
    private Path pathTop;
    private Path pathBottom;
    private Path pathInner;

    private void init() {
        paint = new Paint();
        paint.setColor(Color.parseColor("#991F1F1F"));
        paint.setStrokeWidth(1);
        paint.setAntiAlias(true);
        paint.setStyle(Paint.Style.FILL);

        region = new Region();
        regionL = new Region();
        regionR = new Region();
        regionT = new Region();
        regionB = new Region();
        regionInner = new Region();

        pathLeft = new Path();
        pathRight = new Path();
        pathTop = new Path();
        pathBottom = new Path();
        pathInner = new Path();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        canvas.drawColor(0xff000000);
        super.onDraw(canvas);

    }

    private int paddingLeft;
    private int paddingRight;
    private int paddingTop;
    private int paddingBottom;
    private int clipW, clipH;//裁剪圖片的寬高

    @Override
    public void onDrawForeground(Canvas canvas) {
        super.onDrawForeground(canvas);
        //繪製邊界帶透明度陰影部分
        canvas.drawRect(0, 0, paddingLeft, h, paint);//左邊
        canvas.drawRect(paddingLeft, 0, w - paddingRight, paddingTop, paint);//上
        canvas.drawRect(w - paddingRight, 0, w, h, paint);//右
        canvas.drawRect(paddingLeft, h - paddingBottom, w - paddingRight, h, paint);//下

        //重置區域,拿到有效區域的path
        pathLeft.reset();
        pathRight.reset();
        pathTop.reset();
        pathBottom.reset();
        pathInner.reset();
        pathLeft.addRect(paddingLeft - movePadding, paddingTop - movePadding,
                paddingLeft + movePadding, h - paddingBottom + movePadding, Path.Direction.CW);
        pathRight.addRect(w - paddingRight - movePadding, paddingTop - movePadding,
                w - paddingRight + movePadding, h - paddingBottom + movePadding, Path.Direction.CW);
        pathTop.addRect(paddingLeft - movePadding, paddingTop - movePadding,
                w - paddingRight + movePadding, paddingTop + movePadding, Path.Direction.CW);
        pathBottom.addRect(paddingLeft - movePadding, h - paddingBottom - movePadding,
                w - paddingRight + movePadding, h - paddingBottom + movePadding, Path.Direction.CW);
        pathInner.addRect(paddingLeft + movePadding, paddingTop + movePadding,
                w - paddingRight - movePadding, h - paddingBottom - movePadding, Path.Direction.CW);
        regionL.setPath(pathLeft, region);
        regionR.setPath(pathRight, region);
        regionT.setPath(pathTop, region);
        regionB.setPath(pathBottom, region);
        regionInner.setPath(pathInner, region);

        //獲取屏幕中裁剪圖片的寬高
        clipW = w - paddingLeft - paddingRight;
        clipH = h - paddingTop - paddingBottom;

        Log.e("********", clipW + "====" + clipH);
    }


    private int movePadding = 50;//移動的左右觸摸有效區域
    private int downX, downY;//手指按下的位置
    private int flag = -1;//移動位置確認上、下、左、右、左上、左下、右上、右下、內部,詳細見checkLocation方法內備註

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        int action = event.getAction();
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                downX = (int) event.getX();
                downY = (int) event.getY();
                checkLocation(downX, downY);//確定觸摸位置
                break;
            case MotionEvent.ACTION_MOVE:
                if (flag != -1) {
                    int moveX = (int) event.getX();
                    int moveY = (int) event.getY();
                    int dx = moveX - downX;
                    int dy = moveY - downY;
                    downX = moveX;
                    downY = moveY;
                    doMoveEvent(dx, dy);//具體移動操作
                }
                break;
            case MotionEvent.ACTION_UP://重置
                flag = -1;
                break;
        }
        return true;
    }

    /**
     * 計算有效移動後的參數用於刷新頁面
     *
     * @param dx x方向增量
     * @param dy y方向增量
     */
    private void doMoveEvent(int dx, int dy) {
        Log.e("+++++MMMM", dx + "----" + dy);
        switch (flag) {
            case 0://中間
                if (paddingLeft + dx <= 0 && paddingTop + dy <= 0) {//左上位置限制
                    Log.e("+++++MMM1M", dx + "----" + dy);
                    break;
                }
                if (paddingLeft + dx <= 0 && paddingBottom - dy <= 0) {//左下位置限制
                    Log.e("+++++MMM2M", dx + "----" + dy);
                    break;
                }

                if (paddingRight - dx <= 0 && paddingTop + dy <= 0) {//右上位置限制
                    Log.e("+++++MMM3M", dx + "----" + dy);
                    break;
                }
                if (paddingRight - dx <= 0 && paddingBottom - dy <= 0) {//右下位置限制
                    Log.e("+++++MMM4M", dx + "----" + dy);
                    break;
                }


                if (paddingLeft + dx <= 0 || paddingRight - dx <= 0) {//左右邊界時繼續往左右移動只能上下
                    Log.e("+++++MMM6M", dx + "----" + dy);
                    paddingTop += dy;
                    paddingBottom -= dy;
                    break;
                }

                if (paddingTop + dy <= 0 || paddingBottom - dy <= 0) {//上下邊界時繼續往左右移動只能左右
                    Log.e("+++++MMM7M", dx + "----" + dy);
                    paddingLeft += dx;
                    paddingRight -= dx;
                    break;
                }

                if (paddingLeft + dx > 0 && paddingRight - dx > 0 && paddingTop + dy > 0 && paddingBottom - dy > 0) {//範圍內移動
                    Log.e("+++++MMM5M", dx + "----" + dy);
                    paddingLeft += dx;
                    paddingRight -= dx;
                    paddingTop += dy;
                    paddingBottom -= dy;
                    break;
                }

                break;

            case 1://左上
                paddingLeft += dx;
                paddingTop += dy;
                if (w / 2 > w - paddingLeft - paddingRight) {
                    paddingLeft = w / 2 - paddingRight;
                }
                if (w / 2 > h - paddingTop - paddingBottom) {
                    paddingTop = h - w / 2 - paddingBottom;
                }
                break;
            case 2://左下
                paddingLeft += dx;
                paddingBottom -= dy;
                if (w / 2 > w - paddingLeft - paddingRight) {
                    paddingLeft = w / 2 - paddingRight;
                }
                if (w / 2 > h - paddingTop - paddingBottom) {
                    paddingBottom = h - w / 2 - paddingTop;
                }
                break;
            case 3://右上
                paddingRight -= dx;
                paddingTop += dy;
                if (w / 2 > w - paddingLeft - paddingRight) {
                    paddingRight = w / 2 - paddingLeft;
                }
                if (w / 2 > h - paddingTop - paddingBottom) {
                    paddingTop = h - w / 2 - paddingBottom;
                }
                break;
            case 4://右下
                paddingRight -= dx;
                paddingBottom -= dy;
                if (w / 2 > w - paddingLeft - paddingRight) {
                    paddingRight = w / 2 - paddingLeft;
                }
                if (w / 2 > h - paddingTop - paddingBottom) {
                    paddingBottom = h - w / 2 - paddingTop;
                }
                break;
            case 5://左
                paddingLeft += dx;
                if (w / 2 > w - paddingLeft - paddingRight) {
                    paddingLeft = w / 2 - paddingRight;
                }
                break;
            case 6://右
                paddingRight -= dx;
                if (w / 2 > w - paddingLeft - paddingRight) {
                    paddingRight = w / 2 - paddingLeft;
                }
                break;
            case 7://上
                paddingTop += dy;
                if (w / 2 > h - paddingTop - paddingBottom) {
                    paddingTop = h - w / 2 - paddingBottom;
                }
                break;
            case 8://下
                paddingBottom -= dy;
                if (w / 2 > h - paddingTop - paddingBottom) {
                    paddingBottom = h - w / 2 - paddingTop;
                }
                break;
        }
        checkBounds();
        invalidate();
    }

    /**
     * 統一做邊界越界限制
     */
    private void checkBounds() {
        if (paddingLeft < 0) {
            paddingLeft = 0;
        }
        if (paddingRight < 0) {
            paddingRight = 0;
        }
        if (paddingTop < 0) {
            paddingTop = 0;
        }
        if (paddingBottom < 0) {
            paddingBottom = 0;
        }
    }

    /**
     * 確定點擊位置
     *
     * @param x 按下的x座標
     * @param y 按下的y座標
     */
    private void checkLocation(int x, int y) {
        if (regionInner.contains(x, y)) {//中間
            flag = 0;
            Log.e("+++++MM", "==========" + 0);
            return;
        }
        if (regionL.contains(x, y) && regionT.contains(x, y)) {//左上
            flag = 1;
            Log.e("+++++MM", "==========" + 1);
            return;
        }
        if (regionL.contains(x, y) && regionB.contains(x, y)) {//左下
            flag = 2;
            Log.e("+++++MM", "==========" + 2);
            return;
        }

        if (regionR.contains(x, y) && regionT.contains(x, y)) {//右上
            flag = 3;
            Log.e("+++++MM", "==========" + 3);
            return;
        }
        if (regionR.contains(x, y) && regionB.contains(x, y)) {//右下
            flag = 4;
            Log.e("+++++MM", "==========" + 4);
            return;
        }

        if (regionL.contains(x, y)) {//左
            flag = 5;
            Log.e("+++++MM", "==========" + 5);
            return;
        }
        if (regionR.contains(x, y)) {//右
            flag = 6;
            Log.e("+++++MM", "==========" + 6);
            return;
        }
        if (regionT.contains(x, y)) {//上
            flag = 7;
            Log.e("+++++MM", "==========" + 7);
            return;
        }
        if (regionB.contains(x, y)) {//下
            flag = 8;
            Log.e("+++++MM", "==========" + 8);
            return;
        }
    }

//======================重點:開發者只需要調用該方法即可===========================
    /**
     * @return Bitmap  返回裁剪後的位圖
     */
    public Bitmap clip() {
        Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.abcdde);
        int width = bitmap.getWidth();
        int height = bitmap.getHeight();
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        bitmap.compress(Bitmap.CompressFormat.PNG, 100, baos);
        byte[] aa = baos.toByteArray();
        InputStream inputStream = new ByteArrayInputStream(aa);
        Bitmap bm = null;
        //需要對寬高按比例轉化爲真實寬高
        bm = Bitmap.createBitmap(bitmap, (int) (paddingLeft * (width * 1f / w)), (int) (paddingTop * (height * 1f / h)), (int) (clipW * (width * 1f / w)), (int) (clipH * (height * 1f / h)));
        return bm;
    }
}

控件佈局使用也是簡單地ImageView一樣:

    <com.example.myapplication.coustom.PickImageView
        android:id="@+id/clipImage"
        android:layout_width="match_parent"
        android:layout_height="500dp"
        android:scaleType="centerCrop"
        android:src="@drawable/abcdde">
    </com.example.myapplication.coustom.PickImageView>

 

這裏沒有做裁剪後圖片的保存操作,自己需要自己加Ok。。。。。。。。。。。。。。。。。。。。

當然這就是一個簡單的實現,一般也可滿足正常的需求了。使用中有什麼意見===歡迎留言=====

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章