Android图片海报制作-自定义文字排版控件组件

项目地址:https://github.com/coolstar1204/MakePoster

今天主要讲一下项目主要控件,文字排版控件组,实现类似QQ音乐歌词海报效果。
文字带装饰线效果

控件主要功能点

  • 可设置背景图片
  • 可设置标题文字,并支持标题文字自动居中、超长自动…
  • 可设置图片颜色效果,实现黑白、旧照片、变暗、变亮等效果(有些效果还不太理想)
  • 可增加多行自定义文字、支持文字设置阴影、颜色、大小、居中居右居左等对齐通用设置,支持文字特效
    排版(横向、竖向、不同行等宽不同字体大小效果等)

控件设计结构

控件层级控件类组

核心类

IPoster:字义了排版功能,直接子类有DiffSizePoster、HorizontalPoster、VerticalPoster三个类。
IBmpDrawer:定义了背景图绘制功能,实现类目前只有BmpDrawer,支持ColorMatrix设置
TextDrawer:控件基类,继承于View控件,内部管理上二个接口实现类、增加了标题栏和Logo的绘制功能、导出海报图片功能。

TextDrawer

核心函数有:

 @Override
    protected void onDraw(Canvas canvas) {
        onDrawBackBmp(canvas);
        //画文字,文字位置使用scroll变量偏移,达到拖动效果
        canvas.save();
        canvas.translate(mScrollX,mScrollY);
        if(poster!=null){
            poster.onPostDraw(canvas);
        }
        canvas.restore();
        //画标题文字与酷我logo
        drawLogoAndTitle(canvas);  //画标题和logo
    }
 private float drawTitle(float logoTop, Canvas canvas) {
        if(!TextUtils.isEmpty(kuwoMusicInfo)){
            float titleTargetWidth = bmpDrawer.getBmpScaleRect().width()-mTitleMarginValue-mTitleMarginValue; //有时有缩放显示的情况,文字也要同步变窄,默认是和控件一样宽
            float txtHeight = titlePaint.getFontMetrics().bottom-titlePaint.getFontMetrics().top;

            float txtTop = logoTop-txtHeight-V_SAPCE*2; //文字高度是在图标上方,文字高度上下各留20间距的区域中,居中显示
            RectF txtRect = new RectF(mTitleMarginValue,txtTop,getWidth()-mTitleMarginValue,logoTop);
            Paint.FontMetricsInt fontMetrics = titlePaint.getFontMetricsInt();
            //判断字符串长度是不是超长,超长转为...
            String drawTxt = kuwoMusicInfo;
            float txtWidth = titlePaint.measureText(kuwoMusicInfo);
            if(txtWidth>titleTargetWidth){
                float tailWidth = titlePaint.measureText("...");
                float[] wordWidths = new float[kuwoMusicInfo.length()];
                titlePaint.getTextWidths(kuwoMusicInfo,wordWidths);
                float tmpWidth = tailWidth;
                int wordIdx = kuwoMusicInfo.length()-1;  //默认是显示全部字符
                for(int i=0;i<wordWidths.length;i++){
                    if((tmpWidth+wordWidths[i])>titleTargetWidth){
                        wordIdx = i;
                        break;
                    }else{
                        tmpWidth += wordWidths[i];
                    }
                }
                drawTxt = kuwoMusicInfo.substring(0,wordIdx)+"...";  //截取一部分标题内容
            }
            float baseline = (txtRect.bottom + txtRect.top - fontMetrics.bottom - fontMetrics.top) / 2;
            // 下面这行是实现水平居中,drawText对应改为传入targetRect.centerX()
            titlePaint.setTextAlign(Paint.Align.CENTER);
            canvas.drawText(drawTxt, txtRect.centerX(), baseline, titlePaint);
//            canvas.drawRect(txtRect,titlePaint);
            return getHeight() - txtRect.top; //高度减去文字的上边坐标,得到文字与logo的总高度
        }
        return 0f;
    }
 private Bitmap innerBuildPost(String filePath, int outWidth, int outHeight, boolean saveFile){
        if(saveFile&& FileUtils.isExist(filePath)){
            if(false == FileUtils.deleteFile(filePath)){
                return null;
            }
        }
        try {
            Bitmap outBmp = Bitmap.createBitmap(outWidth,outHeight, Bitmap.Config.ARGB_8888);
            LogMgr.d("PostBmp","width:"+outWidth+",Height:"+outWidth);
            Canvas canvas = new Canvas(outBmp);
            ImageView.ScaleType oldScaleType =  bmpDrawer.getBmpScaleType();
            bmpDrawer.outputDraw(canvas,outWidth,outHeight);
            float scaleX = outWidth*1.0f/getWidth();  //获取屏幕与真实图片的比例
            float scaleY = outHeight*1.0f/getHeight();  //获取屏幕与真实图片的比例
            LogMgr.d("PostBmp","scaleX:"+scaleX+",scaleY:"+scaleY);
            Matrix matrix = canvas.getMatrix();
            canvas.save();
            if(Math.abs(scaleX-1.0f)>0.01|| Math.abs(scaleY-1.0f)>0.01){
                matrix.setScale(scaleX,scaleY,0,0); //缩放处理显示与原图的位置关系
                canvas.setMatrix(matrix);
            }
            if(poster!=null){
                canvas.save();
                canvas.translate(mScrollX,mScrollY);
                poster.onPostDraw(canvas);
                canvas.restore();
            }
            drawLogoAndTitle(canvas);
            canvas.restore();
            if(saveFile){  //如果设置要保存文件,则保存到本地sd卡中,如果不要,则直接返回bitmap对象
                File bmpFile = new File(filePath);
                FileOutputStream fout = new FileOutputStream(bmpFile);
                outBmp.compress(Bitmap.CompressFormat.JPEG,100,fout);
                fout.flush();
                fout.close();
            }
            return outBmp;
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (Throwable e){
            System.gc();
            e.printStackTrace();
        }
        return null;
    }

HorizontalPoster

本类实现文字的横向排版、运行多行的三种对齐方式,同时其子类实现了跳动文字效果、下划线效果、带符号线条装饰效果等。其核心函数是各类中的

 public void updateTextRect(int parentWidth, int parentHeight) {
        Log.d("Poster","------------updateTextRect-----------");
        if(textList!=null&&textList.length>0){
            if(drawTextList==null){
                drawTextList = new ArrayList<TxtRowInfo>(textList.length*2); //默认设置是歌词行数2倍,不能每个都换行吧!
            }
            drawTextList.clear();
            Paint.FontMetrics pfm = textPaint.getFontMetrics();
            txtHeight = pfm.descent-pfm.ascent;   //保存现有字号下单个字的高度
            maxWidth = 0;     //保存最大宽度
            float totalHeight=-pfm.top;    //保存总高度
            totalHeight+=margin_Top;  //加上上面的空隙
            float rawWidth;     //保存每一行的宽度
            for(int i=0;i<textList.length;i++){
                rawWidth = textPaint.measureText(textList[i]);
                if(rawWidth>(parentWidth-margin_Left-margin_Right)){
                    float[] chayWidths = new float[textList[i].length()];
                    textPaint.getTextWidths(textList[i],0,textList[i].length(),chayWidths);
                    float rowTmpWidth = 0f;
                    int startIdx = 0;
                    for(int j=0;j<chayWidths.length;j++){
                        if(rowTmpWidth+chayWidths[j]>(parentWidth-margin_Left-margin_Right)){ //如果加上当前的字符,超过父控件宽度了。则不加当前控件,换行
                            TxtRowInfo item = new TxtRowInfo();
                            item.rowText = textList[i].substring(startIdx,j);
                            item.rowWidth = rowTmpWidth;
                            item.startTop = totalHeight;
                            drawTextList.add(item);
                            maxWidth = Math.max(maxWidth,rowTmpWidth); //得到最大宽度

                            totalHeight+=(txtHeight+ROW_SPACE); //增加行高度
                            rowTmpWidth = chayWidths[j]; //重新计算新行宽度
                            startIdx = j;//保存换行的起始字符
                        }else{
                            rowTmpWidth+=chayWidths[j];
                        }
                    }
                    TxtRowInfo item = new TxtRowInfo();
                    item.rowText = textList[i].substring(startIdx);
                    item.rowWidth = rowTmpWidth;
                    item.startTop = totalHeight;
                    drawTextList.add(item);
                    maxWidth = Math.max(maxWidth,rowTmpWidth); //得到最大宽度
                    totalHeight+=(txtHeight+ROW_SPACE); //增加行高度
                }else{
                    TxtRowInfo info = new TxtRowInfo();
                    info.rowText = textList[i];
                    info.rowWidth = rawWidth;
                    info.startTop = totalHeight;
                    drawTextList.add(info);
                    totalHeight +=(txtHeight+ROW_SPACE);
                    maxWidth = Math.max(maxWidth,rawWidth); //得到最大宽度
                }

            }
            totalHeight = totalHeight+pfm.ascent-ROW_SPACE; //此处要减去开始直接设置的文字top值与ascent的差值,保持上面文字边缘空白相同
            totalHeight += margin_Bottom;  //加上下面要保留的空隙
            updateDrawTextLeft(maxWidth);
            textRect.set(0,0,margin_Left+maxWidth+margin_Right,totalHeight);  //保存文字显示区域
        }else{
            if(drawTextList!=null){
                drawTextList.clear();
                drawTextList = null;
            }
        }
    }

VerticalPoster

本类实现了文字的竖向排版,其子类也是实现了装饰线效果、跳动文字效果等。核心函数

 public void updateTextRect(int parentWidth, int parentHeight) {
        Log.d("Poster","------------updateTextRect-----------");
        if(textList!=null&&textList.length>0){
            if(drawTextList==null){
                drawTextList = new ArrayList<TxtRowInfo>(128); //默认设置是歌词行数2倍,不能每个都换行吧!
            }
            drawTextList.clear();
            Paint.FontMetrics pfm = textPaint.getFontMetrics();
            txtHeight = pfm.bottom-pfm.top;   //保存现有字号下单个字的高度
            maxHeight = 0;     //保存最大高度
            float maxRowWidth = textPaint.measureText("国"); //默认最宽的字符就是中文,数字与字母都比中文窄
            float startLeft = 0;
            float startTop =ROW_SPACE+(-pfm.top);
            int colCount = 1;  //默认肯定有第一列
            float totalWidth=0;    //保存总高度
            for(int i=0;i<textList.length;i++){
                float[] rawWidths = new float[textList[i].length()];     //保存每一行的每个字符的宽度数组
                textPaint.getTextWidths(textList[i],rawWidths);
                int rawNo = 0;  //记录换行的个数
                for(int j=0;j<textList[i].length();j++){
                    startLeft = parentWidth - colCount*(maxRowWidth+COL_SPACE);
                    startTop  = ROW_SPACE+(-pfm.top) + (j-rawNo)*txtHeight;//因为top为负数,则要先取负再加上
                    if((startTop+txtHeight)>parentHeight){ //如果测试发现下一个字符超过边界,则换行
                        rawNo = j;  //保存换行的这个字符index
                        updateColHeight(colCount,(startTop+pfm.bottom+ROW_SPACE));
                        colCount++;
                        startTop = ROW_SPACE+(-pfm.top);
                        startLeft = parentWidth - colCount*(maxRowWidth+COL_SPACE); //换列要重新计算一下
                    }
                    maxHeight = Math.max(maxHeight,(startTop+pfm.bottom+ROW_SPACE)); //保存最高的列,用于更新字符Rect

                    TxtRowInfo item = new TxtRowInfo();
                    item.rowText = String.valueOf(textList[i].charAt(j));
                    item.startLeft = startLeft+(maxRowWidth-rawWidths[j])/2; //把窄的字符要居中,所以这里要处理起点
                    item.startTop = startTop;
                    item.colIndex = colCount;   //保存此字所在列位置,用于列对齐时更新起点做条件
                    item.rowWidth = rawWidths[j];
                    drawTextList.add(item);
                }
                updateColHeight(colCount,(startTop+pfm.bottom+ROW_SPACE));
                colCount++;
            }
            totalWidth = (colCount-1)*(maxRowWidth+COL_SPACE)+COL_SPACE; //此处要加上最左一行左边的空白区域
            updateDrawTextTop(maxHeight);
            updateVerTextLeft(parentWidth,totalWidth);
            textRect.set(0,0,totalWidth,maxHeight);  //保存文字显示区域
        }else{
            if(drawTextList!=null){
                drawTextList.clear();
                drawTextList = null;
            }
        }
    }

DiffSizePoster

本类实现多行文字时,各行等宽、字体大小不同的混合排版效果。
文字大小不一效果
核心函数:

 public void updateTextRect(int parentWidth, int parentHeight) {
        if(textList!=null&&textList.length>0){
            if(drawTextList==null){
                drawTextList = new ArrayList<DiffRowInfo>();
            }
            drawTextList.clear();
            float totalHeight = 0;
            for(int i=0;i<textList.length;i++){
                float curTextSize = textPaint.getTextSize();
                float rowTotalWidth = textPaint.measureText(textList[i]);
                if(rowTotalWidth>parentWidth){
                    //缩小字号,直到小于父控件
                    while (rowTotalWidth>parentWidth){
                        textPaint.setTextSize(--curTextSize);
                        rowTotalWidth = textPaint.measureText(textList[i]);
                    }

                }else{
                    //扩大字号,直到大于父控件,然后获取前一字号值
                    while (rowTotalWidth<parentWidth){
                        textPaint.setTextSize(++curTextSize);
                        rowTotalWidth = textPaint.measureText(textList[i]);
                    }
                    curTextSize--; //大于时才循环停止,所以这里要再减去最后大于的字号值,还原到小于宽度范围内
                    textPaint.setTextSize(curTextSize);
                    rowTotalWidth = textPaint.measureText(textList[i]);
                }
//                float maxRowWidth = textPaint.measureText("国"); //保存每个汉字标准的宽度
                float maxRowHeight = textPaint.getFontMetrics().bottom - textPaint.getFontMetrics().top;
                float totoalWidth = 0;
                if(totalHeight<0.1f){
                    totalHeight += (maxRowHeight-(textPaint.getFontMetrics().bottom)); //保存上面所有行高,用于本行的top定位,因为文字的baseline在文字中下部,所以这里现减去bottom值,把y定位到baseline上
                }else{
                    totalHeight += (maxRowHeight-(textPaint.getFontMetrics().bottom)+HEIGHT_SPACE); //保存上面所有行高,用于本行的top定位,因为文字的baseline在文字中下部,所以这里现减去bottom值,把y定位到baseline上
                }
                if(totalHeight>parentHeight){
                    break;
                }
                float[] rowWidths = new float[textList[i].length()]; //保存每个字符的宽度,
                textPaint.getTextWidths(textList[i],rowWidths);
                float leftOffet = (parentWidth-rowTotalWidth)/2; //保存宽度差的一半,做为行首偏移量,实现居中效果
                for(int j=0;j<textList[i].length();j++){
                    DiffRowInfo item = new DiffRowInfo();
                    item.rowText = ""+textList[i].charAt(j);
                    item.startTop =totalHeight; //
                    item.startLeft = leftOffet + totoalWidth;
                    item.fontSize = curTextSize;
                    drawTextList.add(item);

                    totoalWidth += rowWidths[j];
                }
            }
            totalHeight = Math.min(totalHeight+textPaint.getFontMetrics().bottom+HEIGHT_SPACE,parentHeight);  //最高不能高过父控件高度
            textRect.set(0,0,parentWidth,totalHeight);
        }else{
            if(drawTextList!=null){
                drawTextList.clear();
                drawTextList = null;
            }
        }
    }

BmpDrawer

本类主要是用于绘制背景时,可设置ColorMatrix,达到改变图像颜色效果
核心代码是:

bgPaint.setColorFilter(new ColorMatrixColorFilter(bgColorMatrix));

同时模仿系统ImageView,支持了几种ScaleType的绘制

更多细节请去github上查看代码

本控件因为工作项目需要编写,功能比较独立、所以分享出来,希望能抛砖引玉,大牛们要是看到在结构上有不合理的地方,多指点:)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值