本文主要讲在Textview中获取指定文字的位置,最后,附带一点文字宽高的测量。
下面,我会给出全部源码。自己建个demo,复制进去就能跑起来。
先强调一下,不建议在ListView中使用。最好是在一个单独展示Textview的界面中使用。
先上效果图:
需求及说明:给出一段文字,里面可能包含链接或表情。同时,也有要匹配的关键字,最后,在关键字处展示一个图片,用于强调这个关键字。对应关键字,也可能有多种情况,如:@人名、股票代码等。这里ABC和000001代替。最后,我要在第一个出现的000001位置展示图片。
在源码开始前,先看看项目结构:
说明:
CHEN是工具类
DataBean是listView中,list里面要放的数据的对象
KeyBean是关键字对象
Mainactivity_1是单纯展示一个Textview展示效果图中的内容
Mainactivity_2是把1中的功能放到ListView中(有问题,到现在为止,我没解决,下面会详细说)
Mainactivity_3是文字宽高的测量
MyTextView是可以匹配链接和表情的自定义Textview
PicImageView是自定义的ImageView,是用于自动展示帧动画的ImageView
ViewHolder是ListView中用的
代码:
先上工具相关的准备东西:
CHEN
package com.chen.demo;
import android.content.Context;
public class CHEN {
/**
* 网址要被替换成的文字
*/
public static String REPLACEMENT_STRING = "*点击链接";
// /**
// * 匹配网址的正则表达式。以http://为例
// */
// public static String urlRegex = "(http://[a-zA-Z0-9\\.\\-]+\\.([a-zA-Z]{2,4})(:\\d+)?(/[a-zA-Z0-9\\.\\-~!@#$%^&*+?:_/=<>,]*)?)|(www.[a-zA-Z0-9\\.\\-]+\\.([a-zA-Z]{2,4})(:\\d+)?(/[a-zA-Z0-9\\.\\-~!@#$%^&*+?:_/=<>,]*)?)|([a-zA-Z0-9\\.\\-]+\\.([a-zA-Z]{2,4})(:\\d+)?(/[a-zA-Z0-9\\.\\-~!@#$%^&*+?:_/=<>,]*)?)";
/**
* 匹配网址的正则表达式。有http://、https://、ftp://这3中开头的
*/
public static String urlRegex = "((http[s]{0,1}|ftp)://[a-zA-Z0-9\\.\\-]+\\.([a-zA-Z]{2,4})(:\\d+)?(/[a-zA-Z0-9\\.\\-~!@#$%^&*+?:_/=<>,]*)?)|(www.[a-zA-Z0-9\\.\\-]+\\.([a-zA-Z]{2,4})(:\\d+)?(/[a-zA-Z0-9\\.\\-~!@#$%^&*+?:_/=<>,]*)?)|([a-zA-Z0-9\\.\\-]+\\.([a-zA-Z]{2,4})(:\\d+)?(/[a-zA-Z0-9\\.\\-~!@#$%^&*+?:_/=<>,]*)?)";
public static int sp2px(float spValue, Context context) {
final float fontScale = context.getResources().getDisplayMetrics().scaledDensity;
return (int) (spValue * fontScale + 0.5f);
}
}
DataBean
package com.chen.demo;
public class DataBean {
//关键字的内容
private String content;
//类型,用于区分是否需要在关键字位置显示图片
private String type;
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
}
KeyBean
package com.chen.demo;
public class KeyBean {
//关键字的内容
private String content;
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
}
ViewHolder
package com.chen.demo;
import android.util.SparseArray;
import android.view.View;
public class ViewHolder {
private ViewHolder(){
}
public static <T extends View> T get(View convertView, int id){
SparseArray<View> viewHolder=(SparseArray<View>) convertView.getTag();
if(viewHolder==null){
viewHolder=new SparseArray<View>();
convertView.setTag(viewHolder);
}
View childView=viewHolder.get(id);
if(childView==null){
childView=convertView.findViewById(id);
viewHolder.put(id, childView);
}
return (T)childView;
}
}
自定义View
MyTextView
这里面定义了2种方法,一种是直接展示内容,关于关键字的位置,需要通过另外的方法获取;一种是展示完后直接返回关键字的位置
package com.chen.demo;
import android.content.Context;
import android.content.Intent;
import android.graphics.drawable.Drawable;
import android.support.annotation.Nullable;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.Spanned;
import android.text.TextPaint;
import android.text.TextUtils;
import android.text.method.LinkMovementMethod;
import android.text.style.ClickableSpan;
import android.text.style.ImageSpan;
import android.util.AttributeSet;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;
import java.util.ArrayList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class MyTextView extends TextView {
private Context context = null;
private int firstKeyWordPosition = -1;
public MyTextView(Context context) {
super(context);
this.context = context;
}
public MyTextView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
this.context = context;
}
public MyTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
this.context = context;
}
public void showContent_1(String str, ArrayList<KeyBean> keyBeanList) {
String content = str;
//处理匹配的url
Pattern p = Pattern.compile(CHEN.urlRegex);
Matcher m = p.matcher(content);
ArrayList<String> urlList = new ArrayList<String>();
while (m.find()) {
String urlStr = m.group();
if (urlStr.contains("http://") || urlStr.contains("ftp://")) {
//如果末尾有英文逗号或者中文逗号等,就去掉
while (urlStr.endsWith(",") || urlStr.endsWith(",") || urlStr.endsWith(".") || urlStr.endsWith("。") || urlStr.endsWith(";") || urlStr.endsWith(";") || urlStr.endsWith("!") || urlStr.endsWith("!") || urlStr.endsWith("?") || urlStr.endsWith("?")) {
urlStr = urlStr.substring(0, urlStr.length() - 1);
}
urlList.add(urlStr);
content = content.replace(urlStr, CHEN.REPLACEMENT_STRING);
}
}
SpannableString spannableString = new SpannableString(content);
//处理表情相关
String emoji_string = "\\[(.+?)\\]";
Pattern emoji_patten = Pattern.compile(emoji_string);
Matcher matcher = emoji_patten.matcher(content);
while (matcher.find()) {
Drawable drawable = context.getResources().getDrawable(R.mipmap.emoji_weixiao);
drawable.setBounds(0, 0, CHEN.sp2px(25, context), CHEN.sp2px(25, context));
ImageSpan imgSpan = new ImageSpan(drawable);
spannableString.setSpan(imgSpan, matcher.start(),
matcher.end(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
//表情相关处理结束
content = spannableString.toString();
//处理链接
if (urlList.size() > 0) {
int urlStartNew = 0;
int urlStartOld = 0;
String urlTemp = content;
for (int i = 0; i < urlList.size(); i++) {
final String regexUrl = urlList.get(i);
spannableString.setSpan(new ClickableSpan() {
@Override
public void updateDrawState(TextPaint ds) {
// TODO Auto-generated method stub
super.updateDrawState(ds);
ds.setColor(0xff2097D9);
ds.setUnderlineText(false);
}
@Override
public void onClick(View widget) {
Toast.makeText(context, regexUrl, Toast.LENGTH_SHORT).show();
}
}, urlStartOld + urlTemp.indexOf(CHEN.REPLACEMENT_STRING), urlStartOld + urlTemp.indexOf(CHEN.REPLACEMENT_STRING) + CHEN.REPLACEMENT_STRING.length(),
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
try {
//“点击链接”前面的回形针图片。大小可自己调整
Drawable drawable = context.getResources().getDrawable(R.mipmap.web_link);
drawable.setBounds(0, 0, CHEN.sp2px(25, context), CHEN.sp2px(25, context));
// ImageSpan imgSpan = new ImageSpan(drawable);
spannableString.setSpan(new ImageSpan(drawable), urlStartOld + urlTemp.indexOf(CHEN.REPLACEMENT_STRING), urlStartOld + urlTemp.indexOf(CHEN.REPLACEMENT_STRING) + 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
} catch (Exception e) {
//异常以后,就不加小图片了
}
setMovementMethod(LinkMovementMethod.getInstance());
urlStartNew = urlTemp.indexOf(CHEN.REPLACEMENT_STRING) + CHEN.REPLACEMENT_STRING.length();
urlStartOld += urlStartNew;
urlTemp = urlTemp.substring(urlStartNew);
}
}
//处理关键字
if (keyBeanList != null) {
for (int i = 0; i < keyBeanList.size(); i++) {
final String data = keyBeanList.get(i).getContent();
String temp = content;
int startNew = 0;
int startOld = 0;
if (temp.contains(data)) {
while (temp.contains(data)) {
spannableString.setSpan(new ClickableSpan() {
@Override
public void updateDrawState(TextPaint ds) {
super.updateDrawState(ds);
ds.setColor(0xff2097D9);
ds.setUnderlineText(false);
}
@Override
public void onClick(View widget) {
Toast.makeText(context, data, Toast.LENGTH_SHORT).show();
}
}, startOld + temp.indexOf(data), startOld + temp.indexOf(data) + data.length(),
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
setMovementMethod(LinkMovementMethod.getInstance());
startNew = temp.indexOf(data) + data.length();
startOld += startNew;
temp = temp.substring(startNew);
}
}
}
}
setText(spannableString);
if (keyBeanList != null) {
try {
firstKeyWordPosition = content.indexOf(keyBeanList.get(1).getContent());
} catch (Exception e) {
firstKeyWordPosition = -1;
}
}
}
public int getFirstKeyWordPosition() {
return firstKeyWordPosition;
}
public int showContent_2(String str, ArrayList<KeyBean> keyBeanList) {
String content = str;
//处理匹配的url
Pattern p = Pattern.compile(CHEN.urlRegex);
Matcher m = p.matcher(content);
ArrayList<String> urlList = new ArrayList<String>();
while (m.find()) {
String urlStr = m.group();
if (urlStr.contains("http://") || urlStr.contains("ftp://")) {
//如果末尾有英文逗号或者中文逗号等,就去掉
while (urlStr.endsWith(",") || urlStr.endsWith(",") || urlStr.endsWith(".") || urlStr.endsWith("。") || urlStr.endsWith(";") || urlStr.endsWith(";") || urlStr.endsWith("!") || urlStr.endsWith("!") || urlStr.endsWith("?") || urlStr.endsWith("?")) {
urlStr = urlStr.substring(0, urlStr.length() - 1);
}
urlList.add(urlStr);
content = content.replace(urlStr, CHEN.REPLACEMENT_STRING);
}
}
SpannableString spannableString = new SpannableString(content);
//处理表情相关
String emoji_string = "\\[(.+?)\\]";
Pattern emoji_patten = Pattern.compile(emoji_string);
Matcher matcher = emoji_patten.matcher(content);
while (matcher.find()) {
Drawable drawable = context.getResources().getDrawable(R.mipmap.emoji_weixiao);
drawable.setBounds(0, 0, CHEN.sp2px(25, context), CHEN.sp2px(25, context));
ImageSpan imgSpan = new ImageSpan(drawable);
spannableString.setSpan(imgSpan, matcher.start(),
matcher.end(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
//表情相关处理结束
content = spannableString.toString();
//处理链接
if (urlList.size() > 0) {
int urlStartNew = 0;
int urlStartOld = 0;
String urlTemp = content;
for (int i = 0; i < urlList.size(); i++) {
final String regexUrl = urlList.get(i);
spannableString.setSpan(new ClickableSpan() {
@Override
public void updateDrawState(TextPaint ds) {
// TODO Auto-generated method stub
super.updateDrawState(ds);
ds.setColor(0xff2097D9);
ds.setUnderlineText(false);
}
@Override
public void onClick(View widget) {
Toast.makeText(context, regexUrl, Toast.LENGTH_SHORT).show();
}
}, urlStartOld + urlTemp.indexOf(CHEN.REPLACEMENT_STRING), urlStartOld + urlTemp.indexOf(CHEN.REPLACEMENT_STRING) + CHEN.REPLACEMENT_STRING.length(),
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
try {
//“点击链接”前面的回形针图片。大小可自己调整
Drawable drawable = context.getResources().getDrawable(R.mipmap.web_link);
drawable.setBounds(0, 0, CHEN.sp2px(25, context), CHEN.sp2px(25, context));
// ImageSpan imgSpan = new ImageSpan(drawable);
spannableString.setSpan(new ImageSpan(drawable), urlStartOld + urlTemp.indexOf(CHEN.REPLACEMENT_STRING), urlStartOld + urlTemp.indexOf(CHEN.REPLACEMENT_STRING) + 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
} catch (Exception e) {
//异常以后,就不加小图片了
}
setMovementMethod(LinkMovementMethod.getInstance());
urlStartNew = urlTemp.indexOf(CHEN.REPLACEMENT_STRING) + CHEN.REPLACEMENT_STRING.length();
urlStartOld += urlStartNew;
urlTemp = urlTemp.substring(urlStartNew);
}
}
//处理关键字
if (keyBeanList != null) {
for (int i = 0; i < keyBeanList.size(); i++) {
final String data = keyBeanList.get(i).getContent();
String temp = content;
int startNew = 0;
int startOld = 0;
if (temp.contains(data)) {
while (temp.contains(data)) {
spannableString.setSpan(new ClickableSpan() {
@Override
public void updateDrawState(TextPaint ds) {
super.updateDrawState(ds);
ds.setColor(0xff2097D9);
ds.setUnderlineText(false);
}
@Override
public void onClick(View widget) {
Toast.makeText(context, data, Toast.LENGTH_SHORT).show();
}
}, startOld + temp.indexOf(data), startOld + temp.indexOf(data) + data.length(),
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
setMovementMethod(LinkMovementMethod.getInstance());
startNew = temp.indexOf(data) + data.length();
startOld += startNew;
temp = temp.substring(startNew);
}
}
}
}
setText(spannableString);
if (keyBeanList != null) {
try {
firstKeyWordPosition = content.indexOf(keyBeanList.get(1).getContent());
} catch (Exception e) {
firstKeyWordPosition = -1;
}
}
return firstKeyWordPosition;
}
}
PicImageView
package com.chen.demo;
import android.content.Context;
import android.graphics.drawable.Animatable;
import android.util.AttributeSet;
import android.widget.ImageView;
public class PicImageView extends ImageView {
public PicImageView(Context context) {
this(context, null);
}
public PicImageView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public PicImageView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
/**
* 初始化设置
*/
private void init() {
//不可以使用这个
//setBackgroundResource(R.drawable.refresh_loading);
//需要使用这个
setImageResource(R.drawable.pic_anim);
((Animatable) getDrawable()).start();
}
}
帧动画:pic_anim
随便找3个图,每个图展示1秒。这个随意
<?xml version="1.0" encoding="utf-8"?>
<animation-list
xmlns:android="http://schemas.android.com/apk/res/android"
android:oneshot="true">
<!--设置循环播放-->
<!--android:oneshot="false"-->
<!--true 表示单次播放-->
<item
android:drawable="@mipmap/ch_1"
android:duration="1000"/>
<item
android:drawable="@mipmap/ch_2"
android:duration="1000"/>
<item
android:drawable="@mipmap/ch_3"
android:duration="1000"/>
</animation-list>
至此,通过上面的代码,准备工作基本做完了。下面,开始功能实现。
activity_main_1
有人会对辅助线的295px有疑问,下面会有说明
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<!--辅助线-->
<View
android:id="@+id/auxiliary_line"
android:layout_width="1dp"
android:layout_height="match_parent"
android:layout_marginStart="295px"
android:background="#ff0000"
/>
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true">
<com.chen.demo.MyTextView
android:id="@+id/my_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:background="#55ff0000"
android:padding="5dp"
android:textSize="25sp"
/>
<com.chen.demo.PicImageView
android:id="@+id/pic_img"
android:layout_width="20dp"
android:layout_height="20dp"
android:visibility="gone"/>
</RelativeLayout>
</RelativeLayout>
MainActivity_1
package com.chen.demo;
import android.app.Activity;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.text.Layout;
import android.util.Log;
import android.view.View;
import android.widget.RelativeLayout;
import java.util.ArrayList;
public class MainActivity_1 extends Activity {
private MyTextView my_tv;
private PicImageView pic_img;
private ArrayList<KeyBean> keyBeanList;
//第二种方法需要,记录关键字位置
int position = -1;
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case 100:
//第一种方法,通过textview的方法拿到需要关键字的位置
//int position = my_tv.getFirstKeyWordPosition();
Log.e("position", position + "");
Layout layout = my_tv.getLayout();
if (position != -1 && layout != null) {
int line = layout.getLineForOffset(position);
Log.e("所在行数", line + "");
Rect bound = new Rect();
//拿到关键字所在行的矩形区域
layout.getLineBounds(line, bound);
int left = bound.left;
int top = bound.top;
int right = bound.right;
int bottom = bound.bottom;
int width = bound.width();
int height = bound.height();
Log.e("left", left + "");
Log.e("top", top + "");
Log.e("right", right + "");
Log.e("bottom", bottom + "");
Log.e("width", width + "");
Log.e("height", height + "");
float primaryHorizontal = layout.getPrimaryHorizontal(position);//字符左边x坐标
float secondaryHorizontal = layout.getSecondaryHorizontal(position);
Log.e("primaryHorizontal", primaryHorizontal + "");
Log.e("secondaryHorizontal", secondaryHorizontal + "");
RelativeLayout.LayoutParams pic_img_lp = (RelativeLayout.LayoutParams) pic_img.getLayoutParams();
pic_img_lp.setMargins((int) primaryHorizontal, top, 0, 0);
pic_img.setLayoutParams(pic_img_lp);
pic_img.setVisibility(View.VISIBLE);
mHandler.sendEmptyMessageDelayed(200, 3000);
}
break;
case 200:
pic_img.setVisibility(View.GONE);
break;
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main_1);
my_tv = findViewById(R.id.my_tv);
pic_img = findViewById(R.id.pic_img);
String s = "哈http://www.baidu.com哈123哈哈[\\微笑]哈哈ABC哈哈000001http://www.baidu.com哈[\\微笑]哈哈000001哈哈哈000001";
keyBeanList = new ArrayList<>();
KeyBean keyBean_1 = new KeyBean();
keyBean_1.setContent("ABC");
keyBeanList.add(keyBean_1);
KeyBean keyBean_2 = new KeyBean();
keyBean_2.setContent("000001");
keyBeanList.add(keyBean_2);
//第一种方法,展示内容
//my_tv.showContent_1(s, keyBeanList);
//第二种方法,展示内容后直接拿到需要关键字的位置的返回
position = my_tv.showContent_2(s, keyBeanList);
mHandler.sendEmptyMessageDelayed(100, 3000);
}
@Override
protected void onDestroy() {
if (mHandler != null && mHandler.hasMessages(100)) {
mHandler.removeMessages(100);
}
super.onDestroy();
}
}
说明:
1、在这种单独展示Textview,然后在指定关键字位置展示图片的情况,用MyTextView中的第一种或第二种方法,都没问题,可以任选一种
2、关于handler的使用。如果直接上来就getLayout(),因为视图还没加载完(系统还没准备好),会导致getLayout()得到空。也可以用视图树监听,加载完了再获取,或者用handler延迟获取,这个3秒是我随便写的,不一定是3秒。
3、之所以3秒后再发一个handler,是因为每张图片展示1秒,3张就是3秒
4、日志
在Activity中,我放了2个关键字:ABC和000001,同时,在MyTextView中,我取的是
if (keyBeanList != null) {
try {
firstKeyWordPosition = content.indexOf(keyBeanList.get(1).getContent());
} catch (Exception e) {
firstKeyWordPosition = -1;
}
}
即:第一个000001出现的位置。在Activity中,打印出了日志:
com.chen.demo E/position: 24
com.chen.demo E/所在行数: 1
com.chen.demo E/left: 0
com.chen.demo E/top: 98
com.chen.demo E/right: 1050
com.chen.demo E/bottom: 191
com.chen.demo E/width: 1050
com.chen.demo E/height: 93
com.chen.demo E/primaryHorizontal: 295.0
com.chen.demo E/secondaryHorizontal: 295.0
我为了看295对应的位置,才定义了辅助线
有细心的朋友应该发现了,从0开始计数,如果按字数数一遍,回形针图片长度算1,第一个微笑算1,会数出来,000001出现的位置是20,不是24,这是因为,你把表情算成了1,但是系统数的时候,微笑是[/微笑],这个长度是5,你少数了4,。20+4=24
附 getPrimaryHorizontal和getSecondaryHorizontal说明:
/**
* Get the primary(主要的;初级的;基本的) horizontal水平 position for the specified特定的 text offset偏移、位移.
* This is the location位置 where a new character特性 would be inserted插入、附着 in
* the paragraph's(paragraph:段落) primary direction方向.
*/
public float getPrimaryHorizontal(int offset) {
return getPrimaryHorizontal(offset, false /* not clamped */);
}
/**
* Get the secondary中等的、次要的 horizontal position for the specified text offset.
* This is the location where a new character would be inserted in
* the direction other than the paragraph's primary direction.
*/
public float getSecondaryHorizontal(int offset) {
return getSecondaryHorizontal(offset, false /* not clamped */);
}
以上,就是单独展示的情况,位置不准的话,可以在setMargins中自己调整。
考虑到有人会有疑问,在ListView中可以这样用吗?下面,我说一下ListView中的使用情况
(不建议在ListView中使用,如果不用,下面的内容忽略)
提前说明:
1、在ListView中使用的话,建议用MyTextView中的第二种方法,即:加载完内容后直接返回关键字位置。如果是加载完内容,再通过其他方法获取,容易出问题,有些手机甚至会出现“停止运行”的问题
2、会出现复用问题。即:有的不显示,有的显示过了,但是还会显示。
3、源码如下,有兴趣的朋友可以自己看看,我这里就不多说了
效果图:
说明:
每次加载10条。点击重新加载,会清空之前的所有数据,重新生成10条展示;点击加载更过,会生成10条新的,加再现有数据后面。
activity_main_2
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:id="@+id/reLoad_tv"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="#5500ff00"
android:gravity="center"
android:padding="5dp"
android:text="重新加载数据"
android:textSize="20sp"/>
<TextView
android:id="@+id/load_more_tv"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="#550000ff"
android:gravity="center"
android:padding="5dp"
android:text="加载更多数据"
android:textSize="20sp"/>
</LinearLayout>
<ListView
android:id="@+id/listview"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
MainActivity_2
package com.chen.demo;
import android.app.Activity;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.text.Layout;
import android.text.TextUtils;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ListView;
import android.widget.RelativeLayout;
import android.widget.TextView;
import java.util.ArrayList;
public class MainActivity_2 extends Activity implements View.OnClickListener {
private ListView listview;
private ArrayList<DataBean> dataList;
private ArrayList<KeyBean> keyBeanList;
private MyAdapter myAdapter;
//重新加载数据
private TextView reLoad_tv;
//加载更多数据
private TextView load_more_tv;
private int baseCount = 0;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main_2);
reLoad_tv = findViewById(R.id.reLoad_tv);
load_more_tv = findViewById(R.id.load_more_tv);
reLoad_tv.setOnClickListener(this);
load_more_tv.setOnClickListener(this);
listview = findViewById(R.id.listview);
myAdapter = new MyAdapter();
dataList = new ArrayList<>();
keyBeanList = new ArrayList<>();
KeyBean keyBean_1 = new KeyBean();
keyBean_1.setContent("ABC");
keyBeanList.add(keyBean_1);
KeyBean keyBean_2 = new KeyBean();
keyBean_2.setContent("000001");
keyBeanList.add(keyBean_2);
listview.setAdapter(myAdapter);
baseCount = 0;
setShowData();
}
private void setShowData() {
if (baseCount == 0) {
dataList.clear();
}
String baseStr = "哈http://www.baidu.com哈123哈哈[\\微笑]哈哈ABC哈哈000001http://www.baidu.com哈[\\微笑]哈哈000001哈哈哈000001";
DataBean dataBean;
for (int i = 10 * baseCount; i < 10 * (baseCount + 1); i++) {
dataBean = new DataBean();
dataBean.setContent("=" + i + "=" + baseStr);
dataBean.setType("0");
dataList.add(dataBean);
}
myAdapter.notifyDataSetChanged();
}
@Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.reLoad_tv:
baseCount = 0;
setShowData();
break;
case R.id.load_more_tv:
baseCount++;
setShowData();
break;
}
}
private class MyAdapter extends BaseAdapter {
private View auxiliary_line;
private MyTextView my_tv;
private PicImageView pic_img;
private DataBean bean;
int keyposition=-1;
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case 100:
if (TextUtils.equals("0", bean.getType())) {
//没有展示过的,才展示
// int keyposition = my_tv.getFirstKeyWordPosition();
Layout layout = my_tv.getLayout();
if (keyposition != -1 && layout != null) {
int line = layout.getLineForOffset(keyposition);
Log.e("所在行数", line + "");
Rect bound = new Rect();
layout.getLineBounds(line, bound);
int left = bound.left;
int top = bound.top;
int right = bound.right;
int bottom = bound.bottom;
int width = bound.width();
int height = bound.height();
float primaryHorizontal = layout.getPrimaryHorizontal(keyposition);
float secondaryHorizontal = layout.getSecondaryHorizontal(keyposition);
RelativeLayout.LayoutParams pic_img_lp = (RelativeLayout.LayoutParams) pic_img.getLayoutParams();
pic_img_lp.setMargins((int) primaryHorizontal, top, 0, 0);
pic_img.setLayoutParams(pic_img_lp);
pic_img.setVisibility(View.VISIBLE);
}
mHandler.sendEmptyMessageDelayed(200, 3000);
}
break;
case 200:
if (pic_img != null) {
pic_img.setVisibility(View.GONE);
}
bean.setType("1");
break;
}
}
};
@Override
public int getCount() {
return dataList.size();
}
@Override
public Object getItem(int i) {
return null;
}
@Override
public long getItemId(int i) {
return 0;
}
@Override
public View getView(int i, View view, ViewGroup viewGroup) {
bean = dataList.get(i);
if (view == null) {
view = LayoutInflater.from(MainActivity_2.this).inflate(R.layout.activity_main_1, null);
}
auxiliary_line = ViewHolder.get(view, R.id.auxiliary_line);
my_tv = ViewHolder.get(view, R.id.my_tv);
pic_img = ViewHolder.get(view, R.id.pic_img);
//这里就不用辅助线了
auxiliary_line.setVisibility(View.GONE);
pic_img.setVisibility(View.GONE);
// my_tv.showContent_1(bean.getContent(), keyBeanList);
keyposition= my_tv.showContent_2(bean.getContent(), keyBeanList);
if (TextUtils.equals("0", bean.getType())) {
mHandler.sendEmptyMessageDelayed(100, 3000);
}
return view;
}
}
}
最后,说一下文字的测量:
源码及日志如下,我不多做解释了,想深入的朋友,请看一位大神的博客:
http://blog.csdn.net/aigestudio/article/details/41447349
activity_main_3
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<TextView
android:id="@+id/textview"
android:layout_width="300px"
android:layout_height="200px"
android:layout_centerInParent="true"
android:background="#55ff0000"
android:textSize="50px"
/>
</RelativeLayout>
MainActivity_3
package com.chen.demo;
import android.app.Activity;
import android.graphics.Paint;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.text.TextPaint;
import android.util.Log;
import android.widget.TextView;
public class MainActivity_3 extends Activity {
private TextView textview;
String s = "";
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case 100:
TextPaint textPaint = textview.getPaint();
Rect mRect = new Rect();
textPaint.getTextBounds(s, 0, s.length(), mRect);
int text_rect_left = mRect.left;
int text_rect_top = mRect.top;
int text_rect_right = mRect.right;
int text_rect_bottom = mRect.bottom;
int text_rect_width = mRect.width();
int text_rect_height = mRect.height();
float textWidth = textPaint.measureText(s);
Log.e("text_rect_left", text_rect_left + "");
Log.e("text_rect_top", text_rect_top + "");
Log.e("text_rect_right", text_rect_right + "");
Log.e("text_rect_bottom", text_rect_bottom + "");
Log.e("text_rect_width", text_rect_width + "");
Log.e("text_rect_height", text_rect_height + "");
Log.e("textWidth", textWidth + "");
Log.e("right-left", text_rect_right - text_rect_left + "");
Log.e("bottom-top", text_rect_bottom - text_rect_top + "");
Paint.FontMetrics fontMetrics = textPaint.getFontMetrics();
Log.e("ascent", fontMetrics.ascent + "");
Log.e("top", fontMetrics.top + "");
Log.e("leading", fontMetrics.leading + "");
Log.e("descent", fontMetrics.descent + "");
Log.e("bottom", fontMetrics.bottom + "");
Log.e("bottom-top", fontMetrics.bottom-fontMetrics.top + "");
Log.e("descent-ascent", fontMetrics.descent-fontMetrics.ascent + "");
break;
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main_3);
textview = findViewById(R.id.textview);
s = "123";
textview.setText(s);
mHandler.sendEmptyMessageDelayed(100, 3000);
}
}
日志:
com.chen.demo E/text_rect_left: 4
com.chen.demo E/text_rect_top: -37
com.chen.demo E/text_rect_right: 81
com.chen.demo E/text_rect_bottom: 1
com.chen.demo E/text_rect_width: 77
com.chen.demo E/text_rect_height: 38
com.chen.demo E/textWidth: 84.0
com.chen.demo E/right-left: 77
com.chen.demo E/bottom-top: 38
com.chen.demo E/ascent: -46.38672
com.chen.demo E/top: -52.807617
com.chen.demo E/leading: 0.0
com.chen.demo E/descent: 12.207031
com.chen.demo E/bottom: 13.549805
com.chen.demo E/bottom-top: 66.35742
com.chen.demo E/descent-ascent: 58.59375