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

来源:互联网 发布:山东税务软件下载 编辑:程序博客网 时间:2024/05/01 21:26

项目地址: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上查看代码

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

0 0