随着互联网的发展,信息展示的元素越来越丰富,无论是PC端,还是移动端,图文混排已经成为一种通用的信息展示方式,但在各个平台却都没有提供这种原生的控件。为了更方便地在开发中展示丰富的文本信息,便自定义了这个图文混排控件。
普通的图文混排,无非就两种元素:文字和图片。根据这种特点,我们可以自定义协议,如图片的的网址以\img_url\来引用。根据这种协议就可以将整个文章进行分段,然后动态地创建控件(TextView和ImageView)加载数据即可。
文本不需要协议规定,图片网址用\img_url\的形式引用(协议自己定义)。
PS:对于文章中文字部分的或要做特殊处理。
根据上面的思想,将很容易实现原生的图片混排控件,我们只需要自定义一个LinearLayout, 在其中动态加载布局即可。
没错,使用正则表达式根据上面的协议便可对文章进行分段:
private final String imageRegex = "(.*?)";
/**
* 设置图文混排控件要显示的内容
* @param content 要显示的内容
*/
public void setContent(String content) {
// 格式化字符串(替换特殊符号)
String text = null;
// 设置子View水平居中
setGravity(Gravity.CENTER_HORIZONTAL);
Pattern pattern = Pattern.compile(imageRegex);
Matcher matcher = pattern.matcher(content);
while (matcher.find()) {
// 加载文字
text = content.substring(startPos, matcher.start());
if (!TextUtils.isEmpty(text)) {
appendTextView(clearNewlineChar(text));
}
// 加载图片
appendImageView(content.substring(matcher.start() + 5, matcher.end() - 6));
startPos = matcher.end();
}
// 加载最后一个图片后面的文字
text = content.substring(startPos);
if (!TextUtils.isEmpty(text)) {
appendTextView(clearNewlineChar(text));
}
}
/**
* 动态添加文本内容
* @param content
*/
private void appendTextView(String content) {
if (!TextUtils.isEmpty(content)) {
TextView textView = new TextView(context);
textView.setTextIsSelectable(true);
textView.setText(content);
textView.setGravity(Gravity.LEFT);
textView.getPaint().setTextSize(42);
textView.setLineSpacing(0, 1.4f);
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
params.bottomMargin = dpToPx(12);
params.leftMargin = dpToPx(10);
params.rightMargin = dpToPx(10);
textView.setLayoutParams(params);
addView(textView);
}
}
/**
* 动态添加图片
* @param imageUrl
*/
private void appendImageView(String imageUrl) {
ImageView imageView = new ImageView(context);
final int screenWidth = getDeviceScreenWidth();
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(screenWidth, (int) (screenWidth * 2.0 / 3));
params.bottomMargin = dpToPx(12);
imageView.setLayoutParams(params);
ImageLoader.getInstance().displayImage(imageUrl, imageView);
addView(imageView);
}
public class MixedTextImageLayout extends LinearLayout {
private int startPos = 0;
private Context context;
private final String articleRegex = "(((.*?))|(\\{poi\\}(.*?)\\{/poi\\}))";
private final String imageRegex = "(.*?)";
public MixedTextImageLayout(Context context) {
super(context);
this.context = context;
setOrientation(VERTICAL);
}
public MixedTextImageLayout(Context context, AttributeSet attrs) {
super(context, attrs);
this.context = context;
setOrientation(VERTICAL);
}
public MixedTextImageLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
this.context = context;
setOrientation(VERTICAL);
}
/**
* 设置图文混排控件要显示的内容
* @param content 要显示的内容
*/
public void setContent(String content) {
// 格式化字符串(替换特殊符号)
String text = null;
setGravity(Gravity.CENTER_HORIZONTAL);
Pattern pattern = Pattern.compile(imageRegex);
Matcher matcher = pattern.matcher(clearNeedlessChars(content));
while (matcher.find()) {
text = content.substring(startPos, matcher.start());
if (!TextUtils.isEmpty(text)) {
appendTextView(clearNewlineChar(text));
}
appendImageView(content.substring(matcher.start() + 5, matcher.end() - 6));
startPos = matcher.end();
}
text = content.substring(startPos);
if (!TextUtils.isEmpty(text)) {
appendTextView(clearNewlineChar(text));
}
}
/**
* 动态添加文本内容
* @param content
*/
private void appendTextView(String content) {
if (!TextUtils.isEmpty(content)) {
TextView textView = new TextView(context);
textView.setTextIsSelectable(true);
textView.setText(content);
textView.setGravity(Gravity.LEFT);
textView.getPaint().setTextSize(42);
textView.setLineSpacing(0, 1.4f);
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
params.bottomMargin = dpToPx(12);
params.leftMargin = dpToPx(10);
params.rightMargin = dpToPx(10);
textView.setLayoutParams(params);
addView(textView);
}
}
/**
* 动态添加图片
* @param imageUrl
*/
private void appendImageView(String imageUrl) {
ImageView imageView = new ImageView(context);
final int screenWidth = getDeviceScreenWidth();
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(screenWidth, (int) (screenWidth * 2.0 / 3));
params.bottomMargin = dpToPx(12);
imageView.setLayoutParams(params);
ImageLoader.getInstance().displayImage(imageUrl, imageView);
addView(imageView);
}
/**
* 清除多余的字符
* @param str
* @return
*/
private String clearNeedlessChars(String str) {
str = str.replaceAll("&","&");
str = str.replaceAll(""","\""); //"
str = str.replaceAll(" ","\t");// 替换跳格
str = str.replaceAll(" "," ");// 替换空格
str = str.replaceAll("<","<");
str = str.replaceAll(">",">");
str = str.replaceAll("\r","");
str = str.replaceAll("\n","");
str = str.replaceAll("\t","");
return str;
}
/**
* 清除多余的尾部换行符 注意:replaceFirst不会替换字符串本身的内容
* @param content
* @return
*/
private String clearNewlineChar(String content) {
int startPos = 0;
int endPos = content.length() - 1;
// 清除文字首部多余的换行符
while (startPos <= endPos) {
if (content.charAt(startPos) == '\n' || content.charAt(startPos) == '\r') {
startPos++;
// 当所有内容都是换行符的情况
if (startPos > endPos) {
content = "";
endPos -= startPos;
break;
}
} else {
// 获取清除后的字符串,并重新设置尾部位置
content = content.substring(startPos);
endPos -= startPos;
break;
}
}
// 清除文字尾部多余的换行符
while (endPos > 0) {
if (content.charAt(endPos) == '\n' || content.charAt(endPos) == '\r') {
endPos--;
} else {
content = content.substring(0, endPos+1);
break;
}
}
return content;
}
/**
* 获取屏幕宽度
* @return
*/
public int getDeviceScreenWidth() {
DisplayMetrics dm = getResources().getDisplayMetrics();
int w = dm.widthPixels;
int h = dm.heightPixels;
return w > h ? h : w;
}
/**
* dp转px
* @param dp
* @return
*/
public int dpToPx(int dp) {
return (int) (getResources().getDisplayMetrics().density * ((float) dp)+0.5);
}
}
在布局文件中定义之后,代码中直接调用setContent()方法设置内容即可。
mixedLayout = (MixedTextImageLayout) findViewById(R.id.mixed_layout);
mixedLayout.setContent(content);
其实图文混排有多种方式可以实现,最常用的可能就是直接用WebView加载了,简单易用且样式丰富,但是对移动端而言,交互相对较难。所以具体使用哪种方式,还是要根据需求进行取舍。当然,这种方式除了加载文字和图片之外,也可以加载其他布局(需要对代码进行扩展)。