項(xiàng)目需求討論:截圖—涂鴉—分享

大家好,又到了新的一期項(xiàng)目需求分析。臺(tái)下的觀(guān)眾舉起手,讓我看到你們。

同時(shí)我已經(jīng)上傳該項(xiàng)目:截屏及仿支付寶涂鴉功能

歡迎各位點(diǎn)個(gè)star哦。(⊙o⊙)


image

開(kāi)始秋名山飄移之路

這個(gè)也是具體項(xiàng)目中遇到的項(xiàng)目需求:需要在一個(gè)特定的界面中(都是圖表和各種數(shù)據(jù),可能需求分享給別人,告訴別人這個(gè)數(shù)據(jù)怎么怎么,這個(gè)圖表怎么怎么)
所以給我們開(kāi)發(fā)的需求就是:

  1. 對(duì)該界面進(jìn)行截屏
  2. 截屏后跳到涂鴉界面,可能某個(gè)數(shù)據(jù)圈出來(lái),讓別人一看就知道。(涂鴉,橡皮擦,撤銷(xiāo),清空操作)
  3. 分享給QQ,微信等等其他第三方軟件。

功能上來(lái)說(shuō),其實(shí)沒(méi)啥難度。各種第三方的,別人寫(xiě)好的涂鴉封裝包也有很多。但是要么是不能隨意定制化,要么就是寫(xiě)的也不是特別好,而且我更加提倡的是技術(shù)本身不是特別好的各位同僚們,還是寧可下載下來(lái)觀(guān)看,然后自己試著去寫(xiě)一下,寫(xiě)一個(gè)自己的工具??赡軐?xiě)的沒(méi)別人好,可能有點(diǎn)重復(fù)造輪子的意思,但是對(duì)于自己的提升還是很大的。當(dāng)然大家可以提出意見(jiàn)。我寫(xiě)的也不見(jiàn)得很好。。(大牛請(qǐng)忽略這段話(huà)。哈哈)

具體步驟

1.界面截屏功能

因?yàn)楫?dāng)前我做的是截屏后把獲得的Bitmap保存為本地圖片,然后跳轉(zhuǎn)到涂鴉界面,然后打開(kāi)這個(gè)本地圖片進(jìn)行涂鴉處理。其他人可以根據(jù)自己的需求對(duì)這個(gè)截屏獲取的Bitmap進(jìn)行處理。

/**
 * 截屏并將圖片保存到相應(yīng)路徑下
 * @param activity 當(dāng)前需要截屏的activity
 * @param path 圖片保存路徑
 */
public static void SaveScreenShot(Activity activity,String path) {
    View view = activity.getWindow().getDecorView();
    view.setDrawingCacheEnabled(true);
    view.buildDrawingCache();
    Bitmap bitmap = view.getDrawingCache();

    FileOutputStream outputStream = null;
    try {
        outputStream = new FileOutputStream(path);
        bitmap.compress(Bitmap.CompressFormat.PNG,100,outputStream);
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    }finally {
        if(outputStream != null){
            try {
                outputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        view.setDrawingCacheEnabled(false);
    }
}

核心的代碼就是:

View view = activity.getWindow().getDecorView();
view.setDrawingCacheEnabled(true);
view.buildDrawingCache();
Bitmap bitmap = view.getDrawingCache();
view.setDrawingCacheEnabled(false);

2.涂鴉功能

核心的概念就是:自定義View(畫(huà)板),然后在上面先畫(huà)上我們截屏的圖片,然后再在上面畫(huà)上一層圖片,我們的涂鴉其實(shí)也就是在上面一層圖片畫(huà)來(lái)畫(huà)去。一臉懵逼?? 一定要逼我換個(gè)通俗易懂的???

比如畫(huà)板是一個(gè)桌子,我們先在上面放了一個(gè)海報(bào),然后我們?cè)倌靡粋€(gè)透明的圖片(這里比如我們拿的是一塊透明玻璃)蓋在海報(bào)上面,然后我們拿個(gè)粉筆在玻璃上面畫(huà)來(lái)畫(huà)去,這時(shí)候來(lái)我們看來(lái),是不是就等于海報(bào)被涂了各種顏色。比如我們要清空涂鴉。是不是只要把玻璃上面的涂鴉給擦光就行了。然后下面的海報(bào)就又變成了原來(lái)的樣子。

所以我們這個(gè)涂鴉也要一步步來(lái)處理,因?yàn)樵趯?shí)際開(kāi)發(fā)中會(huì)遇到各種問(wèn)題,所以要一步步來(lái)。


如何把截圖的圖片,按照我們自定義的View(畫(huà)板)的大小,等比例放進(jìn)去。

可能有人要問(wèn)了。為啥要等比例放進(jìn)去,我們就直接把圖片塞進(jìn)去我們的畫(huà)板不就OK了嗎。
因?yàn)槲覀冞@次是全屏截圖!??!而我們的畫(huà)板肯定是比全屏的截圖面積要小,因?yàn)橄旅嬗挟?huà)筆、橡皮擦等其他功能菜單區(qū)域。
看效果(我故意把我的涂鴉畫(huà)板的高度設(shè)置小很多):

所要截圖的界面
截圖不做調(diào)整直接放入
截圖做了調(diào)整放入

[圖片上傳失敗...(image-c6aeef-1531720247591)]
OK,我們接下去就知道了。如果我們要在自定義的View(畫(huà)板)上先放上去截圖的圖片,就要先把圖片按比例處理好再放入。所以也就是說(shuō)這個(gè)圖片需要根據(jù)我們的畫(huà)板的面積來(lái)做相應(yīng)調(diào)整。

也有可能有人要問(wèn),如果我的需求不是截屏涂鴉,是打開(kāi)內(nèi)置的某個(gè)圖片,然后畫(huà)板反而比我的圖片大怎么辦
。沒(méi)關(guān)系。這里教你們一招。不用在意圖片跟畫(huà)板哪個(gè)大哪個(gè)小。

1.獲取圖片的寬和高:bitmapWidth,bitmapHeight
2.獲取畫(huà)板的寬和高:boardWidth,boardHeight
3.如果bitmapWidth > boardWidth或者 bitmapHeight > boardHeight,則把圖片縮小比例,來(lái)適應(yīng)畫(huà)板
4.否則就設(shè)置畫(huà)板控件的寬和高等于圖片的寬和高。既讓boardWidth = bitmapWidth,boardHeight = bitmapHeight。我們反過(guò)來(lái)讓畫(huà)板來(lái)適應(yīng)圖片即可。

/**
 *
 * 圖片過(guò)大則根據(jù)容器把原始圖片改變大小。從而適應(yīng)容器。
 * 否則改變畫(huà)板大小適應(yīng)圖片
 *
 * @param bitmap
 * @param boardView
 * @return
 */
public static Bitmap resizeBitmap(Bitmap bitmap, View boardView) {

    int bitmapHeight = bitmap.getHeight();
    int bitmapWidth = bitmap.getWidth();
    int boardHeight = boardView.getHeight();
    int boardWidth = boardView.getWidth();
        
    float scale = 1f;
    if(bitmapHeight > boardHeight || bitmapWidth > boardWidth){
        scale = bitmapHeight > bitmapWidth
                ? boardHeight / (bitmapHeight * 1f)
                : boardWidth / (bitmapWidth * 1f);
    }

    Matrix matrix = new Matrix();
    matrix.postScale(scale, scale);
    Bitmap resizeBitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmapWidth, bitmapHeight, matrix, false);

    ViewGroup.LayoutParams params = boardView.getLayoutParams();
    params.height = resizeBitmap.getHeight();
    params.width = resizeBitmap.getWidth();
    boardView .setLayoutParams(params);

    return resizeBitmap;
}

添加第二層Canvas。用于涂鴉

我們上面拿到了截圖的Bitmap,我們畫(huà)上去后,還要畫(huà)一層用于涂鴉的Bitmap,也就是我前面舉例的那塊透明玻璃。
也很簡(jiǎn)單。我們只要再新建一個(gè)Bitmap,大小等同于畫(huà)板的大小,然后用Canvas包裹,讓這個(gè)Bitmap能夠具有畫(huà)畫(huà)的功能,
然后再把這個(gè)新的Bitmap讓我們的畫(huà)板畫(huà)出來(lái)就可以了。
我們?cè)谧远xView 中寫(xiě)入一個(gè)方法:

/**
  * 設(shè)置背景圖片及監(jiān)理新的用來(lái)涂鴉的Bitmap
  * 
  * @param bitmap 傳入的截圖界面圖片
  */
public void setBackgroud(Bitmap bitmap) {
    this.backgroudBitmap = bitmap;
    this.bitmap = Bitmap.createBitmap(backgroudBitmap.getWidth(), backgroudBitmap.getHeight(), Bitmap.Config.ARGB_8888);
    paintCanvas = new Canvas(this.bitmap);
}

這樣我們把第一步處理好的截圖圖片傳進(jìn)來(lái)的時(shí)候。同時(shí)建一個(gè)和這個(gè)截圖圖片一樣大小的圖片,并且用Canvas包裹。這樣等會(huì)我們就可以用這個(gè)paintCanvas在新的圖片上面進(jìn)行畫(huà)畫(huà)了。

我們?cè)趏nDraw方法中也只要畫(huà)這二個(gè)Bitmap就可以了。

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

    if (backgroudBitmap != null && !backgroudBitmap.isRecycled()) {
        canvas.drawBitmap(backgroudBitmap, 0, 0, null);
    }

    if (bitmap != null && !bitmap.isRecycled()) {
        canvas.drawBitmap(bitmap, 0, 0, null);
    }
}


真正的涂鴉實(shí)現(xiàn)

真正的涂鴉功能,其實(shí)只是對(duì)我們的后面新建的Bitmap進(jìn)行各種操作,也就是用上面的paintCanvas來(lái)進(jìn)行畫(huà)線(xiàn)等操作。

@Override
public boolean onTouchEvent(MotionEvent event) {
    switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            startX = event.getX();
            startY = event.getY();

            path = new Path();
            path.moveTo(startX, startY);

            break;

        case MotionEvent.ACTION_MOVE:
            endX = event.getX();
            endY = event.getY();
            path.quadTo(startX, startY, endX, endY);
            paintCanvas.drawPath(path, isEraser ? eraserPaint : paint);
            startX = endX;
            startY = endY;
            postInvalidate();
            break;

        case MotionEvent.ACTION_UP:
            drawPathList.add(new DrawPathEntry(path, isEraser ? eraserPaint.getColor() : paint.getColor(), isEraser));
            break;

        default:
            break;
    }
    return true;
}

切換涂鴉顏色功能

其實(shí)用不同顏色畫(huà)筆來(lái)畫(huà),就是單純的切換Paint的顏色值即可。

/**
 * 設(shè)置畫(huà)筆顏色及橡皮擦
 *
 * @param type
 */
public void setPaintType(int type) {

    isEraser = false;
    switch (type) {
        case AnnotationConfig.PaintType.Paint_Red:
            paint.setColor(ContextCompat.getColor(context, R.color.red_radio));
            break;
        case AnnotationConfig.PaintType.Paint_Orange:
            paint.setColor(ContextCompat.getColor(context, R.color.orange_radio));
            break;
        case AnnotationConfig.PaintType.Paint_Yellow:
            paint.setColor(ContextCompat.getColor(context, R.color.yellow_radio));
            break;
        case AnnotationConfig.PaintType.Paint_Green:
            paint.setColor(ContextCompat.getColor(context, R.color.green_radio));
            break;
        case AnnotationConfig.PaintType.Paint_Blue:
            paint.setColor(ContextCompat.getColor(context, R.color.blue_radio));
            break;
        case AnnotationConfig.PaintType.Paint_Purple:
            paint.setColor(ContextCompat.getColor(context, R.color.purple_radio));
            break;
        case AnnotationConfig.PaintType.Paint_Eraser:
            isEraser = true;
            break;
        default:
            break;
    }
}

橡皮擦功能

其實(shí)橡皮擦功能,你可以理解就是在畫(huà)一個(gè)透明的線(xiàn),這個(gè)線(xiàn)蓋在了你的其他的畫(huà)好的線(xiàn)的上面。我們的目標(biāo)就是他們二個(gè)交集的地方,讓原本的線(xiàn)消失。
下表就是二個(gè)線(xiàn)交集的時(shí)候,不同Mode下呈現(xiàn)的情況。我們這里就可以用Clear模式。交集的地方,讓底下的顏色消失就可以。

[圖片上傳失敗...(image-43c445-1531720247591)]
設(shè)置橡皮擦的Paint的Xfermode模式為Mode.Clear。

Xfermode xfermode = new PorterDuffXfermode(PorterDuff.Mode.CLEAR);
eraserPaint.setXfermode(xfermode);

然后讓這個(gè)earserPaint按照上面涂鴉的寫(xiě)法,讓它畫(huà)線(xiàn)就可以。


撤銷(xiāo)功能

撤銷(xiāo)其實(shí)也很簡(jiǎn)單,就是我們?cè)诋?huà)的時(shí)候,把每次畫(huà)的Path和所畫(huà)這個(gè)Path的畫(huà)筆顏色保存下來(lái),放在一個(gè)List集合里面,然后每次點(diǎn)撤銷(xiāo),就把List集合里面最后一個(gè)的Path給去掉,然后把二層Bitmap(也就是那個(gè)透明玻璃)清空,再把List里面的所有的Path按照其對(duì)應(yīng)的畫(huà)筆顏色畫(huà)出來(lái)就可以了。

/**
 * 撤銷(xiāo)操作
 */
public void cancelPath() {
    if (drawPathList != null && drawPathList.size() <= 0) {
        return;
    }
    drawPathList.remove(drawPathList.size() - 1);
    paintCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
    for (DrawPathEntry entry : drawPathList) {
        paint.setColor(entry.getPaintColor());
        paintCanvas.drawPath(entry.getPath(), entry.isEraser() ? eraserPaint : paint);
    }
    postInvalidate();
}

清空涂鴉

清空涂鴉就比上面撤銷(xiāo)功能更簡(jiǎn)單了。把二層Bitmap(也就是那個(gè)透明玻璃)清空,然后把我們的上面存Path的List給清空,這樣撤銷(xiāo)也就不會(huì)出現(xiàn)原來(lái)的涂鴉。

/**
 * 清空涂鴉
 */
public void clearScrawlBoard() {
    paintCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
    drawPathList.clear();
    postInvalidate();
}

返回最終的涂鴉好的圖片

新建一個(gè)Canvas。把最終的截圖圖片的bitmap和最終已經(jīng)涂鴉好的二層Bitmap(透明玻璃)給畫(huà)上去,然后獲取最終的合成的Bitmap即可。

/**
 * @return 返回最終的涂鴉好的圖片
 */
public Bitmap getSrawBoardBitmap() {
    Bitmap resultBitmap = Bitmap.createBitmap(backgroudBitmap.getWidth(), backgroudBitmap.getHeight(), Bitmap.Config.ARGB_8888);
    Canvas canvas = new Canvas(resultBitmap);
    canvas.drawBitmap(backgroudBitmap, 0, 0, null);
    canvas.drawBitmap(bitmap, 0, 0, null);
    canvas.save();
    return resultBitmap;
}

3.圖片分享功能

我們通過(guò)上面的getSrawBoardBitmap()方法獲得了最后的合成的圖片。然后分享到微信,QQ等第三方軟件。我這邊用的是友盟分享。

友盟分享Android SDK

它只是幫你做了個(gè)封裝,而各個(gè)第三方的分享都要去其相應(yīng)的平臺(tái)去申請(qǐng)Key。
比如你要分享到微信和QQ,就要分別取申請(qǐng)微信和QQ的key。然后再集成到友盟分享中去,才能使用。

UMImage image = new UMImage(DrawBaseActivity.this, bit);
new ShareAction(DrawBaseActivity.this).withText("hello").withMedia(image)
    .setDisplayList(SHARE_MEDIA.QQ,SHARE_MEDIA.WEIXIN,SHARE_MEDIA.ALIPAY)
    .setCallback(new UMShareListener() {
        @Override
        public void onStart(SHARE_MEDIA share_media) {

        }

        @Override
        public void onResult(SHARE_MEDIA share_media) {
            Toast.makeText(DrawBaseActivity.this, share_media + " 分享成功啦", Toast.LENGTH_SHORT).show();
        }

        @Override
        public void onError(SHARE_MEDIA share_media, Throwable throwable) {
            Toast.makeText(DrawBaseActivity.this,share_media + " 分享失敗啦", Toast.LENGTH_SHORT).show();
            if(throwable!=null){
                Log.d("throw","throw:"+throwable.getMessage());
            }
        }

        @Override
        public void onCancel(SHARE_MEDIA share_media) {
            Toast.makeText(DrawBaseActivity.this,share_media + " 分享取消了", Toast.LENGTH_SHORT).show();
            }
    }).open();

當(dāng)然這種分享過(guò)去給別人,是會(huì)攜帶你的app相關(guān)信息,比如我分享一個(gè)圖片到微信,在微信聊天列表的圖片下面會(huì)有我們的app的名字信息。但是如果我不想一個(gè)個(gè)平臺(tái)去注冊(cè),就想單純的當(dāng)一個(gè)文本發(fā)送到其他app,我們只需要簡(jiǎn)單的寫(xiě)一下就可以:


Uri uri = Uri.fromFile(new File(path));
share("xxx截圖說(shuō)明", uri);


 /**
     * 圖片及文字分享
     *
     * @param content
     * @param uri
     */
    private void share(String content, Uri uri) {
        Intent shareIntent = new Intent(Intent.ACTION_SEND);
        if (uri != null) {
            shareIntent.putExtra(Intent.EXTRA_STREAM, uri);
            shareIntent.setType("image/*");
            //當(dāng)用戶(hù)選擇短信時(shí)使用sms_body取得文字
            shareIntent.putExtra("sms_body", content);
        } else {
            shareIntent.setType("text/plain");
        }
        shareIntent.putExtra(Intent.EXTRA_TEXT, content);
        //自定義選擇框的標(biāo)題
        startActivity(Intent.createChooser(shareIntent, "邀請(qǐng)好友"));
        //系統(tǒng)默認(rèn)標(biāo)題

    }

最終

最后的最后,貼上相應(yīng)的相關(guān)自定義View和涂鴉的所屬的Activity的代碼。大家也可以去我上面貼的Github地址中取下載demo。更方便查看:截屏及仿支付寶涂鴉功能

ScrawlActivity.java
package com.example.scrawldemo;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.os.Environment;
import android.support.annotation.IdRes;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.RadioGroup;
import android.widget.Toast;

import com.example.scrawldemo.config.AnnotationConfig;
import com.example.scrawldemo.util.BitmapUtil;
import com.example.scrawldemo.widget.ScrawlBoardView;

import java.io.FileNotFoundException;
import java.io.FileOutputStream;

import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;

public class ScrawlActivity extends AppCompatActivity {

    @BindView(R.id.color_group)
    RadioGroup colorGroup;
    @BindView(R.id.board_view)
    ScrawlBoardView boardView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ButterKnife.bind(this);

        String path = getIntent().getStringExtra("path");
        final Bitmap bitmap = BitmapFactory.decodeFile(path);


        boardView.post(new Runnable() {
            @Override
            public void run() {
                final Bitmap resizeBitmap = BitmapUtil.resizeBitmap(bitmap,boardView);
                //final Bitmap resizeBitmap = bitmap;
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        boardView.setBackgroud(resizeBitmap);
                    }
                });
            }
        });

        colorGroup.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(RadioGroup group, @IdRes int checkedId) {
                switch (checkedId) {
                    case R.id.red_radio:
                        boardView.setPaintType(AnnotationConfig.PaintType.Paint_Red);
                        break;
                    case R.id.orange_radio:
                        boardView.setPaintType(AnnotationConfig.PaintType.Paint_Orange);
                        break;
                    case R.id.yellow_radio:
                        boardView.setPaintType(AnnotationConfig.PaintType.Paint_Yellow);
                        break;
                    case R.id.green_radio:
                        boardView.setPaintType(AnnotationConfig.PaintType.Paint_Green);
                        break;
                    case R.id.blue_radio:
                        boardView.setPaintType(AnnotationConfig.PaintType.Paint_Blue);
                        break;
                    case R.id.purple_radio:
                        boardView.setPaintType(AnnotationConfig.PaintType.Paint_Purple);
                        break;
                    case R.id.eraser_radio:
                        boardView.setPaintType(AnnotationConfig.PaintType.Paint_Eraser);
                        break;
                    default:
                        break;
                }
            }
        });
    }

    @OnClick({R.id.cancel, R.id.rubbish,R.id.finish_btn,R.id.send_btn})
    public void onViewClicked(View view) {
        switch (view.getId()) {
            case R.id.cancel:
                boardView.cancelPath();
                break;
            case R.id.rubbish:
                boardView.clearScrawlBoard();
                break;
            case R.id.finish_btn:
                finish();
                break;
            case R.id.send_btn:
                Bitmap bitmap = boardView.getSrawBoardBitmap();

                //該處的bitmap是涂鴉好的圖片。
                //該例子中是把涂鴉好的圖片保存到本地
                try {
                    FileOutputStream outputStream = new FileOutputStream(ScrawlActivity.this.getExternalFilesDir(Environment.DIRECTORY_PICTURES)+"/ChintScreenShot.png");
                    bitmap.compress(Bitmap.CompressFormat.PNG,100,outputStream);
                    Toast.makeText(this, "保存到本地成功", Toast.LENGTH_SHORT).show();
                } catch (FileNotFoundException e) {
                    e.printStackTrace();
                }

                //我分享用的是友盟分享。也就是把上面獲取到的bitmap傳入到相應(yīng)的友盟分享中的方法即可
                /*
                UMImage image = new UMImage(ScrawlActivity.this, bitmap);
                new ShareAction(DrawBaseActivity.this).withText("hello").withMedia(image)
                        .setDisplayList(SHARE_MEDIA.QQ,SHARE_MEDIA.WEIXIN,SHARE_MEDIA.ALIPAY)
                        .setCallback(new UMShareListener() {
                            @Override
                            public void onStart(SHARE_MEDIA share_media) {

                            }

                            @Override
                            public void onResult(SHARE_MEDIA share_media) {
                                Toast.makeText(DrawBaseActivity.this, share_media + " 分享成功啦", Toast.LENGTH_SHORT).show();
                            }

                            @Override
                            public void onError(SHARE_MEDIA share_media, Throwable throwable) {
                                Toast.makeText(DrawBaseActivity.this,share_media + " 分享失敗啦", Toast.LENGTH_SHORT).show();
                                if(throwable!=null){
                                    Log.d("throw","throw:"+throwable.getMessage());
                                }
                            }

                            @Override
                            public void onCancel(SHARE_MEDIA share_media) {
                                Toast.makeText(DrawBaseActivity.this,share_media + " 分享取消了", Toast.LENGTH_SHORT).show();
                            }
                        }).open();
                  */



                break;

            default:
                break;
        }
    }
}

ScrawlBoardView.java
package com.example.scrawldemo.widget;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Xfermode;
import android.support.annotation.Nullable;
import android.support.v4.content.ContextCompat;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;

import com.example.scrawldemo.R;
import com.example.scrawldemo.bean.DrawPathEntry;
import com.example.scrawldemo.config.AnnotationConfig;

import java.util.ArrayList;
import java.util.List;

/**
 * Project:AndroidDemo
 * Author:dyping
 * Date:2017/4/11 10:36
 */

public class ScrawlBoardView extends View {

    Canvas paintCanvas;
    Paint paint, eraserPaint;
    Bitmap bitmap;
    Bitmap backgroudBitmap;

    float startX, startY, endX, endY;

    Context context;

    boolean isEraser;
    List<DrawPathEntry> drawPathList = new ArrayList<>();
    Path path;

    public ScrawlBoardView(Context context) {
        super(context);
    }

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

        paint = new Paint();
        paint.setStyle(Paint.Style.STROKE);
        paint.setAntiAlias(true);
        paint.setDither(true);
        paint.setFilterBitmap(true);
        paint.setColor(ContextCompat.getColor(context, R.color.red_radio));
        paint.setStrokeWidth(10);

        eraserPaint = new Paint();
        eraserPaint.setStyle(Paint.Style.STROKE);
        eraserPaint.setStrokeWidth(20);
        eraserPaint.setColor(Color.TRANSPARENT);
        Xfermode xfermode = new PorterDuffXfermode(PorterDuff.Mode.CLEAR);
        eraserPaint.setXfermode(xfermode);

        this.context = context;
    }

    public ScrawlBoardView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

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

        if (backgroudBitmap != null && !backgroudBitmap.isRecycled()) {
            canvas.drawBitmap(backgroudBitmap, 0, 0, null);
        }

        if (bitmap != null && !bitmap.isRecycled()) {
            canvas.drawBitmap(bitmap, 0, 0, null);
        }
    }

    /**
     * 設(shè)置背景圖片及監(jiān)理新的用來(lái)涂鴉的Bitmap
     *
     * @param bitmap 傳入的截圖界面圖片
     */
    public void setBackgroud(Bitmap bitmap) {
        this.backgroudBitmap = bitmap;
        this.bitmap = Bitmap.createBitmap(backgroudBitmap.getWidth(), backgroudBitmap.getHeight(), Bitmap.Config.ARGB_8888);
        paintCanvas = new Canvas(this.bitmap);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                startX = event.getX();
                startY = event.getY();

                path = new Path();
                path.moveTo(startX, startY);

                break;

            case MotionEvent.ACTION_MOVE:
                endX = event.getX();
                endY = event.getY();
                path.quadTo(startX, startY, endX, endY);
                paintCanvas.drawPath(path, isEraser ? eraserPaint : paint);
                startX = endX;
                startY = endY;
                postInvalidate();
                break;

            case MotionEvent.ACTION_UP:
                drawPathList.add(new DrawPathEntry(path, isEraser ? eraserPaint.getColor() : paint.getColor(), isEraser));
                break;

            default:
                break;
        }
        return true;
    }

    /**
     * 設(shè)置畫(huà)筆顏色及橡皮擦
     *
     * @param type
     */
    public void setPaintType(int type) {

        isEraser = false;
        switch (type) {
            case AnnotationConfig.PaintType.Paint_Red:
                paint.setColor(ContextCompat.getColor(context, R.color.red_radio));
                break;
            case AnnotationConfig.PaintType.Paint_Orange:
                paint.setColor(ContextCompat.getColor(context, R.color.orange_radio));
                break;
            case AnnotationConfig.PaintType.Paint_Yellow:
                paint.setColor(ContextCompat.getColor(context, R.color.yellow_radio));
                break;
            case AnnotationConfig.PaintType.Paint_Green:
                paint.setColor(ContextCompat.getColor(context, R.color.green_radio));
                break;
            case AnnotationConfig.PaintType.Paint_Blue:
                paint.setColor(ContextCompat.getColor(context, R.color.blue_radio));
                break;
            case AnnotationConfig.PaintType.Paint_Purple:
                paint.setColor(ContextCompat.getColor(context, R.color.purple_radio));
                break;
            case AnnotationConfig.PaintType.Paint_Eraser:
                isEraser = true;
                break;
            default:
                break;
        }
    }

    /**
     * 撤銷(xiāo)操作
     */
    public void cancelPath() {
        if (drawPathList != null && drawPathList.size() <= 0) {
            return;
        }
        drawPathList.remove(drawPathList.size() - 1);
        paintCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
        for (DrawPathEntry entry : drawPathList) {
            paint.setColor(entry.getPaintColor());
            paintCanvas.drawPath(entry.getPath(), entry.isEraser() ? eraserPaint : paint);
        }
        postInvalidate();
    }


    /**
     * 清空涂鴉
     */
    public void clearScrawlBoard() {
        paintCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
        drawPathList.clear();
        postInvalidate();
    }

    /**
     * @return 返回最終的涂鴉好的圖片
     */
    public Bitmap getSrawBoardBitmap() {
        Bitmap resultBitmap = Bitmap.createBitmap(backgroudBitmap.getWidth(), backgroudBitmap.getHeight(), Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(resultBitmap);
        canvas.drawBitmap(backgroudBitmap, 0, 0, null);
        canvas.drawBitmap(bitmap, 0, 0, null);
        canvas.save();
        return resultBitmap;
    }

}

BitmapUtil.java
package com.example.scrawldemo.util;

import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.Matrix;
import android.view.View;
import android.view.ViewGroup;

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

/**
 * Project:AndroidDemo
 * Author:dyping
 * Date:2017/4/11 10:40
 */

public class BitmapUtil {

    /**
     *
     * 圖片過(guò)大則根據(jù)容器把原始圖片改變大小。從而適應(yīng)容器。
     * 否則改變畫(huà)板大小適應(yīng)圖片
     *
     * @param bitmap
     * @param boardView
     * @return
     */
    public static Bitmap resizeBitmap(Bitmap bitmap, View boardView) {

        int bitmapHeight = bitmap.getHeight();
        int bitmapWidth = bitmap.getWidth();
        int boardHeight = boardView.getHeight();
        int boardWidth = boardView.getWidth();

        float scale = 1f;
        if(bitmapHeight > boardHeight || bitmapWidth > boardWidth){
            scale = bitmapHeight > bitmapWidth
                    ? boardHeight / (bitmapHeight * 1f)
                    : boardWidth / (bitmapWidth * 1f);
        }

        Matrix matrix = new Matrix();
        matrix.postScale(scale, scale);
        Bitmap resizeBitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmapWidth, bitmapHeight, matrix, false);

        ViewGroup.LayoutParams params = boardView.getLayoutParams();
        params.height = resizeBitmap.getHeight();
        params.width = resizeBitmap.getWidth();
        boardView .setLayoutParams(params);

        return resizeBitmap;
    }


    /**
     * 截屏并將圖片保存到相應(yīng)路徑下
     *
     * @param activity 當(dāng)前需要截屏的activity
     * @param path     圖片保存路徑
     */
    public static void SaveScreenShot(Activity activity, String path) {
        View view = activity.getWindow().getDecorView();
        view.setDrawingCacheEnabled(true);
        view.buildDrawingCache();
        Bitmap bitmap = view.getDrawingCache();

        FileOutputStream outputStream = null;
        try {
            outputStream = new FileOutputStream(path);
            bitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } finally {
            if (outputStream != null) {
                try {
                    outputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            view.setDrawingCacheEnabled(false);
        }
    }
}

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀(guān)點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 178,954評(píng)論 25 709
  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫(kù)、插件、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 15,199評(píng)論 4 61
  • 發(fā)布時(shí)間:2014年2月開(kāi)源的一個(gè)前端開(kāi)發(fā)庫(kù) 1 官網(wǎng)介紹 2 版本下載 http://v1-cn.vuejs.o...
    Jianshu9527閱讀 1,211評(píng)論 0 0
  • 跟大多數(shù)的畢業(yè)生一樣,當(dāng)自己要踏出校園的那一刻,覺(jué)得自己終于解放了。我們?cè)僖膊槐厣险n了,再也不用擔(dān)心老師會(huì)點(diǎn)名了,...
    愛(ài)上世界的張大路閱讀 777評(píng)論 24 13
  • 泡司令最近接到讀者來(lái)信: “和老公結(jié)婚兩年多,和公公婆婆住一起,公公婆婆都對(duì)我挺好,可是最近公公的行為讓我越來(lái)越困...
    泡司令閱讀 4,301評(píng)論 0 1

友情鏈接更多精彩內(nèi)容