您的位置:首页 > 移动开发 > Android开发

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

2016-03-22 08:22 615 查看
项目地址: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上查看代码

本控件因为工作项目需要编写,功能比较独立、所以分享出来,希望能抛砖引玉,大牛们要是看到在结构上有不合理的地方,多指点:)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  android 图片