Android自定义View 实现图片编辑功能(包括普通涂鸦、画圆、画矩形、画箭头、写字)_zdj_Develop的博客-程序员宅基地

技术标签: Android开发总结  自定义View  Android图片编辑  

以下为自定义View,继承自ImageView,可实现对图片进行编辑,包括普通涂鸦、画圆、画矩形、画箭头以及写字。

并且,可以对线条大小、线条颜色、字体大小、字体颜色进行设置。另外,实现了文字自动换行、移动文字位置、撤销等功能。

主要用到了Canvas、Paint、TextPaint、StaticLayout等Android中的图形图像方面和自定义View的技术。

下面直接上代码:

package au.com.joincc.secusoft.view;

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.RectF;
import android.os.Environment;
import android.os.Handler;
import android.os.Message;
import android.text.DynamicLayout;
import android.text.Layout;
import android.text.StaticLayout;
import android.text.TextPaint;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.MotionEvent;
import android.widget.ImageView;

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

import au.com.joincc.secusoft.R;

/**
 * Created by Administrator on 2017/9/4.
 * 自定义View.使用Canvas、Paint等来实现图片编辑功能(包括普通涂鸦、画圆、画矩形、画箭头、写字)
 */

public class GraffitiView extends ImageView {

    private Paint paint;  //画笔
    private Bitmap originalBitmap;  //源图
    private Bitmap newBitmap;
    private Bitmap finalBitmap;  //最终保存时的图片
    private Bitmap finalOvalBitmap;
    private Bitmap finalRectBitmap;
    private Bitmap finalArrowBitmap;
    private Bitmap finalTextBitmap;

    private float clickX = 0;  //触摸时的x坐标
    private float clickY = 0;  //触摸时的y坐标
    private float startX = 0;  //每次绘制起点的x坐标
    private float startY = 0;  //每次绘制起点的y坐标

    private boolean isEdit = false;  //是否允许对图片进行编辑
    private boolean isMove = true;  //是否进行绘制
    private boolean isClear = false;  //是否进行清空
    private int drawType = 0;  //绘制类型:0---线条;1---圆;2---矩形;3---箭头;4---文字
    private String text;

    private int color = Color.RED;  //画笔颜色
    private float strokeWidth = 4.0f;  //涂鸦线条的宽度
    private int textSize = 18;  //字体大小
    private int textColor = getResources().getColor(R.color.red);  //文字颜色

    private int mScreenWidth;
    private int mScreenHeight;

    private String filePath;

    public GraffitiView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public void setFilePath(String filePath) {
        this.filePath = filePath;
        //根据原位图的大小创建一个新位图,得到的位图是可变的
        DisplayMetrics dm = getResources().getDisplayMetrics();
        mScreenWidth = dm.widthPixels;
        mScreenHeight = dm.heightPixels;
        originalBitmap = BitmapFactory.decodeFile(filePath).copy(Bitmap.Config.ARGB_8888, true);
        //        new1Bitmap = Bitmap.createBitmap(originalBitmap);  //得到一个不可变位图
        finalBitmap = Bitmap.createScaledBitmap(originalBitmap, mScreenWidth, mScreenHeight, true);
        invalidate();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        switch (drawType) {
            case 0:
                canvas.drawBitmap(handWriting(), 0, 0, null);
                break;
            case 1:
                canvas.drawBitmap(drawOval(), 0, 0, null);
                break;
            case 2:
                canvas.drawBitmap(drawRect(), 0, 0, null);
                break;
            case 3:
                canvas.drawBitmap(drawArrow(), 0, 0, null);
                break;
            case 4:
                canvas.drawBitmap(writeText(), 0, 0, null);
                break;
        }
    }

    /**
     * 画线条
     * @return
     */
    public Bitmap handWriting() {
        if (isClear) {
            isClear = false;
            finalBitmap = newBitmap;
            return finalBitmap;
        } else {
            Bitmap bitmap = Bitmap.createScaledBitmap(finalBitmap, mScreenWidth, mScreenHeight, true);
            Canvas canvas = new Canvas(bitmap);
            paint = new Paint();
            paint.setStyle(Paint.Style.STROKE);
            paint.setAntiAlias(true);
            paint.setColor(color);
            paint.setStrokeWidth(strokeWidth);
            if (isMove) {  //当移动时绘制
                canvas.drawLine(startX, startY, clickX, clickY, paint);  //在画布上画线(通过起点、终点和画笔)
            }
            //起点需要不断变化,否则画出来的不是线而是面,因为起点一直不变
            startX = clickX;
            startY = clickY;
            finalBitmap = bitmap;
            return finalBitmap;
        }
    }

    /**
     * 画椭圆(包括圆,圆是一种特殊的椭圆)
     * @return
     */
    public Bitmap drawOval() {
        if (isClear) {
            isClear = false;
            finalBitmap = newBitmap;
            return finalBitmap;
        } else {
            Bitmap bitmap = Bitmap.createScaledBitmap(finalBitmap, mScreenWidth, mScreenHeight, true);
            Canvas canvas = new Canvas(bitmap);
            paint = new Paint();
            paint.setStyle(Paint.Style.STROKE);
            paint.setAntiAlias(true);
            paint.setColor(color);
            paint.setStrokeWidth(strokeWidth);
            if (isMove) {
                canvas.drawOval(new RectF(startX, startY, clickX, clickY), paint);
            }
            finalOvalBitmap = bitmap;
            return finalOvalBitmap;
        }
    }

    /**
     * 画矩形
     * @return
     */
    public Bitmap drawRect() {
        if (isClear) {
            isClear = false;
            finalBitmap = newBitmap;
            return finalBitmap;
        } else {
            Bitmap bitmap = Bitmap.createScaledBitmap(finalBitmap, mScreenWidth, mScreenHeight, true);
            Canvas canvas = new Canvas(bitmap);
            paint = new Paint();
            paint.setStyle(Paint.Style.STROKE);
            paint.setAntiAlias(true);
            paint.setColor(color);
            paint.setStrokeWidth(strokeWidth);
            if (isMove) {
                canvas.drawRect(startX, startY, clickX, clickY, paint);
            }
            finalRectBitmap = bitmap;
            return finalRectBitmap;
        }
    }

    /**
     * 画箭头
     */
    public Bitmap drawArrow() {
        if (isClear) {
            isClear = false;
            finalBitmap = newBitmap;
            return finalBitmap;
        } else {
            Bitmap bitmap = Bitmap.createScaledBitmap(finalBitmap, mScreenWidth, mScreenHeight, true);
            Canvas canvas = new Canvas(bitmap);
            paint = new Paint();
            paint.setStyle(Paint.Style.STROKE);
            paint.setAntiAlias(true);
            paint.setColor(color);
            paint.setStrokeWidth(strokeWidth);
            if (isMove) {
                drawArrow(startX, startY, clickX, clickY, canvas, paint);
            }
            finalArrowBitmap = bitmap;
            return finalArrowBitmap;
        }
    }

    public void drawArrow(float startX, float startY, float endX, float endY, Canvas canvas, Paint paint) {
        double H = 10;  //箭头高度
        double L = 6;  //底边的一半
        int x3 = 0;
        int y3 = 0;
        int x4 = 0;
        int y4 = 0;
        double awrad = Math.atan(L / H);  //箭头角度
        double arraow_len = Math.sqrt(L * L + H * H);  //箭头的长度
        double[] arrXY_1 = rotateVec(endX - startX, endY - startY, awrad, true, arraow_len);
        double[] arrXY_2 = rotateVec(endX - startX, endY - startY, -awrad, true, arraow_len);
        double x_3 = endX - arrXY_1[0];  //(x3,y3)是第一端点
        double y_3 = endY - arrXY_1[1];
        double x_4 = endX - arrXY_2[0];  //(x4,y4)是第二端点
        double y_4 = endY - arrXY_2[1];
        Double X3 = new Double(x_3);
        x3 = X3.intValue();
        Double Y3 = new Double(y_3);
        y3 = Y3.intValue();
        Double X4 = new Double(x_4);
        x4 = X4.intValue();
        Double Y4 = new Double(y_4);
        y4 = Y4.intValue();
        //画线
        canvas.drawLine(startX, startY, endX, endY, paint);
        Path triangle = new Path();
        triangle.moveTo(endX, endY);
        triangle.lineTo(x3, y3);
        triangle.lineTo(x4, y4);
        triangle.close();
        canvas.drawPath(triangle, paint);
    }

    public double[] rotateVec(float px, float py, double ang, boolean isChLen, double newLen) {
        double mathstr[] = new double[2];
        //矢量旋转函数,参数含义分别是x分量、y分量、旋转角、是否改变长度、新长度
        double vx = px * Math.cos(ang) - py * Math.sin(ang);
        double vy = px * Math.sin(ang) + py * Math.cos(ang);
        if (isChLen) {
            double d = Math.sqrt(vx * vx + vy * vy);
            vx = vx / d * newLen;
            vy = vy / d * newLen;
            mathstr[0] = vx;
            mathstr[1] = vy;
        }
        return mathstr;
    }

    /**
     * 在图片上写字
     * @param text
     */
    public void writeTextToBitmap(String text) {
        drawType = 4;
        this.text = text;
        invalidate();
    }

    public Bitmap writeText() {
        if (isClear) {
            isClear = false;
            finalBitmap = newBitmap;
            finalTextBitmap = finalBitmap;
            return finalBitmap;
        } else {
            Bitmap bitmap = Bitmap.createScaledBitmap(finalBitmap, mScreenWidth, mScreenHeight, true);
            Canvas canvas = new Canvas(bitmap);
            TextPaint textPaint = new TextPaint();
            textPaint.setColor(textColor);
            textPaint.setTextSize(textSize * getResources().getDisplayMetrics().density);
            textPaint.setAntiAlias(true);
            StaticLayout staticLayout = new StaticLayout(text, textPaint, mScreenWidth * 3 / 4, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false);
            canvas.translate(clickX, clickY);
            staticLayout.draw(canvas);
            finalTextBitmap = bitmap;
            return finalTextBitmap;
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        clickX = event.getX();
        clickY = event.getY();
        if (event.getAction() == MotionEvent.ACTION_DOWN) {
            //当按下并不移动时,刷新并记下按下时的坐标点但不绘制
            startX = clickX;
            startY = clickY;
            isMove = false;
            invalidate();
            return true;
        } else if (event.getAction() == MotionEvent.ACTION_MOVE) {
            //当移动时,进行绘制,从按下的坐标点起
            if (isEdit) {
                isMove = true;
            }
            invalidate();
            return true;
        } else if (event.getAction() == MotionEvent.ACTION_UP) {
            isMove = false;
            switch (drawType) {
                case 1:
                    finalBitmap = finalOvalBitmap;
                    break;
                case 2:
                    finalBitmap = finalRectBitmap;
                    break;
                case 3:
                    finalBitmap = finalArrowBitmap;
                    break;
            }
            return true;
        }
        return super.onTouchEvent(event);
    }

    /**
     * 设置画的类型
     * @param drawType
     */
    public void setDrawType(int drawType) {
        if (this.drawType == 4) {
            finalBitmap = finalTextBitmap;
        }
        this.drawType = drawType;
    }

    /**
     * 设置是否允许对图片进行编辑
     * @param isEdit
     */
    public void setIdEdit(boolean isEdit) {
        this.isEdit = isEdit;
    }

    /**
     * 清空
     */
    public void clear() {
        isClear = true;
        newBitmap = Bitmap.createScaledBitmap(originalBitmap, mScreenWidth, mScreenHeight, true);
        invalidate();
    }

    /**
     * 设置画笔颜色
     * @param color
     */
    public void setColor(int color) {
        this.color = color;
    }

    /**
     * 设置画笔的宽度
     * @param strokeWidth
     */
    public void setStyle(float strokeWidth) {
        this.strokeWidth = strokeWidth;
    }

    /**
     * 设置字体大小
     * @param textSize
     */
    public void setTextSize(int textSize) {
        this.textSize = textSize;
    }

    /**
     * 设置字体颜色
     * @param textColor
     */
    public void setTextColor(int textColor) {
        this.textColor = textColor;
    }

    /**
     * 保存图片
     */
    public void saveBitmap(Handler handler) {
        if (drawType == 4) {
            finalBitmap = finalTextBitmap;
        }
        if (!filePath.equals(Environment.getExternalStorageDirectory().getPath() + "/JCamera/image.jpeg")) {
            int bitmapWidth = originalBitmap.getWidth();
            int bitmapHeight = originalBitmap.getHeight();
            finalBitmap = Bitmap.createScaledBitmap(finalBitmap, bitmapWidth, bitmapHeight, true);
        }
        FileOutputStream fos = null;
        try {
            fos = new FileOutputStream(filePath);
            finalBitmap.compress(Bitmap.CompressFormat.PNG, 90, fos);
            fos.flush();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (fos != null) {
                try {
                    fos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            handler.sendMessage(Message.obtain());
        }
    }
}

以上代码结构很清晰,注释写的也比较清楚。读者可以直接拿去使用。

关于一些基础知识,如果不明白,可以查看我的另一篇文章Canvas、Paint、TextPaint、StaticLayout,或者通过查看Google文档等方式自行了解。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/zdj_Develop/article/details/79517250

智能推荐

WAMP中的mysql设置密码(默认密码为空)及phpmyadmin的配置_weixin_30610755的博客-程序员宅基地

来自: http://wenku.baidu.com/link?url=J4K28e1kt-_ykJLsOtS1b5T6hKj5IzL5hXSKIiB133AvPCUXLlxGKScsBsxi0mA-mPaL3dNDMQZ-UW9aHqIG1KQLEKDZmUWO0XY9ozUtnd7为WAMP中的mysql设置密码密码WAMP安装好后,mysql密码是为空的,那么要如何修改呢?其实很...

mac的一些小技巧_weixin_34418883的博客-程序员宅基地

切换到超级管理员:sudo -s;让你很快的全屏之间进行切换!很方便!很实用!command+tab今天的感觉到公司的每一个人员,对于mac的系统的使用都是非常的熟悉的,我还什么都不会。我得更快的学习使用,熟悉各种基本的命令。技巧还是必须要掌握的。转载于:https://www.cnblogs.com/xiaohaillong/p/5802047.htm...

k-means算法_weixin_30622107的博客-程序员宅基地

supervised learning:训练数据集中样本的类别已知unsupervised learning:事先并不知道任何样本的类别聚类属于无监督学习,以往的回归、朴素贝叶斯、SVM等都是有类别标签y的,也就是说样例中已经给出了样例的分类。而聚类的样本中却没有给定y,只有特征x。聚类的目的是找到每个样本x潜在的类别y,并将同类别y的样本x放在一起。在聚类问题中,给我们的训练样本是,...

PTA 练习3-4 统计字符 (15分)_eclipse_ali的博客-程序员宅基地

本题要求编写程序,输入10个字符,统计其中英文字母、空格或回车、数字字符和其他字符的个数。输入格式:输入为10个字符。最后一个回车表示输入结束,不算在内。输出格式:在一行内按照letter = 英文字母个数, blank = 空格或回车个数, digit = 数字字符个数, other = 其他字符个数的格式输出。输入样例1:aZ &09 Az输出样例1:letter = 4, blank = 3, digit = 2, other = 1答案代码块#includ

基于jsp+mysql+Spring+mybatis的SSM教师师资管理系统_保安本安的博客-程序员宅基地

运行环境:最好是java jdk 1.8,我们在这个平台上运行的。其他版本理论上也可以。IDE环境:Eclipse,Myeclipse,IDEA都可以tomcat环境:Tomcat 7.x,8.x,9.x版本均可硬件环境:windows 7/8/10 2G内存以上(推荐4G,4G以上更好)主要功能说明:管理员角色包含以下功能:管理员角色登录,教师管理,教师授课管理,审批教师的项目开题,项目详情审批,项目总结审批,公告管理等功能。教师角色包含以下功能:教师角色登录,修改个人信息,修改密码,公告列表...

[转载]Windows Phone SDK 8.0新特性(翻译)_weixin_33881140的博客-程序员宅基地

随笔- 6 文章- 0 评论- 30 Windows Phone SDK 8.0新特性(翻译)原文地址:What's new in Windows Phone SDK 8.0欢迎使用Windows Phone SDK 8.0。Windows Phone 8为开发人员提供了一些新特性和升级特性。包括本地代码的游戏开发,手机版的Windows Runtime,以及新的内核。我们把这...

随便推点

Generator async promise 的区别_Sugar阿的博客-程序员宅基地

首先 Generator async promise 这三个api都是es6新增api可以理解为异步的解决方案,但是他们的适用场景不一样Generator执行 Generator 函数会返回一个遍历器对象,也就是说,Generator 函数除了状态机,还是一个遍历器对象生成函数。返回的遍历器对象,可以依次遍历 Generator 函数内部的每一个状态。Generator 函数定义...

分页导出,防止内存溢出_Nameless007-Victory的博客-程序员宅基地

分页导出,防止内存溢出1.定义模板方法类/** * 分页导出模板 * * T 为查询参数 需要有 PageNo 和 PageSize * R 为查询结果,导出的对象 */public abstract class PagingExportTemplate<T extends BasicQueryEntity,R> { private static final Logger log = LoggerFactory.getLogger(PagingExportTempl

教你如何使用Gateway搭建网关服务及实现动态路由_海·是倒过来的天的博客-程序员宅基地_gateway搭建

就是像图中原理一样,哈哈哈~~~~~~~~网关作为微服务中非常重要的一部分,是必须要掌握的;本文记录一下我是如何使用Gateway搭建网关服务及实现动态路由的,帮助大家学习如何快速搭建一个网关服务,了解路由相关配置,鉴权的流程及业务处理,有兴趣的一定看到最后,非常适合没接触过网关服务的同学当作入门教程。搭建服务框架 SpringBoot 2.1 <parent><groupId>org.springframework.boot</groupI...

android主窗口与子窗口,4.3.1主序、子序和窗口类型_侃叔的博客-程序员宅基地

看一下WindowState的构造函数:**WindowState.java::WindowState.WindowState()**```WindowState(WindowManagerService service, Sessions, IWindow c, WindowToken token,WindowState attachedWindow, int seq, WindowManage...

xor和gates的专杀脚本_weixin_30393907的博客-程序员宅基地

前段时间的一次样本,需要给出专杀,应急中遇到的是linux中比较常见的两个家族gates和xor。首先是xor的专杀脚本,xor样本查杀的时候需要注意的是样本的主进程和子进程相互保护(详见之前的xor ddos分析http://www.cnblogs.com/goabout2/p/4888651.html),想要杀掉的话,需要先通过kill –stop挂起主进程,再删除其他的文件,但...

Linux虚机挂载_change9513的博客-程序员宅基地_虚拟机linux挂载

第一步:列出所有磁盘 命令:ll /dev/disk/by-path如果无法确认数据盘设备名称,请使用df命令来确认系统盘的名称,从而排除挂错盘的情况。第二步:格式化硬盘 命令:fdisk /dev/sdb第三步:创建分区 命令:mkfs.ext4 /dev/sdb1根据不同的磁盘格式进行格式化(ext4格式或xfs格式要确定)第四步:挂载分区命令:mkdir /data命令:mount /dev/sdb1 /data第五步:将信息写入fstab,让系统开启自动挂载。命令:e

推荐文章

热门文章

相关标签