Android图片海报制作-自定义文字排版控件组件
2016-03-22 08:22
615 查看
项目地址:https://github.com/coolstar1204/MakePoster
今天主要讲一下项目主要控件,文字排版控件组,实现类似QQ音乐歌词海报效果。
可设置标题文字,并支持标题文字自动居中、超长自动…
可设置图片颜色效果,实现黑白、旧照片、变暗、变亮等效果(有些效果还不太理想)
可增加多行自定义文字、支持文字设置阴影、颜色、大小、居中居右居左等对齐通用设置,支持文字特效
排版(横向、竖向、不同行等宽不同字体大小效果等)
IBmpDrawer:定义了背景图绘制功能,实现类目前只有BmpDrawer,支持ColorMatrix设置
TextDrawer:控件基类,继承于View控件,内部管理上二个接口实现类、增加了标题栏和Logo的绘制功能、导出海报图片功能。
核心函数:
核心代码是:
同时模仿系统ImageView,支持了几种ScaleType的绘制
更多细节请去github上查看代码
本控件因为工作项目需要编写,功能比较独立、所以分享出来,希望能抛砖引玉,大牛们要是看到在结构上有不合理的地方,多指点:)
今天主要讲一下项目主要控件,文字排版控件组,实现类似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上查看代码
本控件因为工作项目需要编写,功能比较独立、所以分享出来,希望能抛砖引玉,大牛们要是看到在结构上有不合理的地方,多指点:)
相关文章推荐
- 使用C++实现JNI接口需要注意的事项
- Android IPC进程间通讯机制
- Android Manifest 用法
- [转载]Activity中ConfigChanges属性的用法
- Android之获取手机上的图片和视频缩略图thumbnails
- Android之使用Http协议实现文件上传功能
- Android学习笔记(二九):嵌入浏览器
- android string.xml文件中的整型和string型代替
- i-jetty环境搭配与编译
- android之定时器AlarmManager
- android wifi 无线调试
- Android Native 绘图方法
- Android java 与 javascript互访(相互调用)的方法例子
- android 代码实现控件之间的间距
- android FragmentPagerAdapter的“标准”配置
- Android"解决"onTouch和onClick的冲突问题
- android:installLocation简析
- android searchView的关闭事件
- SourceProvider.getJniDirectories