在Textview中获取指定文字位置(兼顾网址链接和emoji表情),并在其附近展示图片
2017-10-16 11:28
615 查看
本文主要讲在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
DataBean
KeyBean
ViewHolder
自定义View
MyTextView
这里面定义了2种方法,一种是直接展示内容,关于关键字的位置,需要通过另外的方法获取;一种是展示完后直接返回关键字的位置
PicImageView
帧动画:pic_anim
随便找3个图,每个图展示1秒。这个随意
至此,通过上面的代码,准备工作基本做完了。下面,开始功能实现。
activity_main_1
有人会对辅助线的295px有疑问,下面会有说明
MainActivity_1
说明:
1、在这种单独展示Textview,然后在指定关键字位置展示图片的情况,用MyTextView中的第一种或第二种方法,都没问题,可以任选一种
2、关于handler的使用。如果直接上来就getLayout(),因为视图还没加载完(系统还没准备好),会导致getLayout()得到空。也可以用视图树监听,加载完了再获取,或者用handler延迟获取,这个3秒是我随便写的,不一定是3秒。
3、之所以3秒后再发一个handler,是因为每张图片展示1秒,3张就是3秒
4、日志
在Activity中,我放了2个关键字:ABC和000001,同时,在MyTextView中,我取的是
即:第一个000001出现的位置。在Activity中,打印出了日志:
我为了看295对应的位置,才定义了辅助线
有细心的朋友应该发现了,从0开始计数,如果按字数数一遍,回形针图片长度算1,第一个微笑算1,会数出来,000001出现的位置是20,不是24,这是因为,你把表情算成了1,但是系统数的时候,微笑是[/微笑],这个长度是5,你少数了4,。20+4=24
附 getPrimaryHorizontal和getSecondaryHorizontal说明:
以上,就是单独展示的情况,位置不准的话,可以在setMargins中自己调整。
考虑到有人会有疑问,在ListView中可以这样用吗?下面,我说一下ListView中的使用情况
(不建议在ListView中使用,如果不用,下面的内容忽略)
提前说明:
1、在ListView中使用的话,建议用MyTextView中的第二种方法,即:加载完内容后直接返回关键字位置。如果是加载完内容,再通过其他方法获取,容易出问题,有些手机甚至会出现“停止运行”的问题
2、会出现复用问题。即:有的不显示,有的显示过了,但是还会显示。
3、源码如下,有兴趣的朋友可以自己看看,我这里就不多说了
效果图:
说明:
每次加载10条。点击重新加载,会清空之前的所有数据,重新生成10条展示;点击加载更过,会生成10条新的,加再现有数据后面。
activity_main_2
MainActivity_2
最后,说一下文字的测量:
源码及日志如下,我不多做解释了,想深入的朋友,请看一位大神的博客:
activity_main_3
MainActivity_3
日志:
下面,我会给出全部源码。自己建个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
相关文章推荐
- 一段文本内容中展示网址链接---TTTAttributedLabel链接点击和textView两种方式实现
- android TextView如何通过setCompoundDrawables调整文字和图片的位置(文字显示在图片内)?
- Android textview获取html文字 + 图片显示
- TextView设置指定位置处文字颜色的坑
- TextView实现图片和文字展示
- 图文混排中,在textView的指定光标下插入文字或图片
- 从指定网址下载文件 并读出其内容 && 从网上下载文件放在本地&&从网上获取图片设在imageView里
- android textview获取图片
- TextView加图片,真正可以调大小位置
- recyclerview网络获取图片展示瀑布流MVP
- 1.3 实现图片压缩、添加文字或图片水印、指定位置裁剪等功能的ImageUtility(内有效果图) (转)
- 利用CMHTMLView实现webview基础功能,同时获取点击图片链接的功能
- Android TextView 嵌套图片及其点击,TextView 部分文字点击,文字多颜色
- Android(java)学习笔记139:在TextView组件中利用Html插入文字或图片
- 关于带图片的TextView-代码更改图片位置注意事项
- 用9path图片做背景 button和textview的文字不显示
- iOS - 图文混排(支持图片文字混排,网址@#话题#以及自定义链接,关键字高亮等)
- Android 实现TextView中 文字链接的方式
- URL(获取指定网址里的图片)
- Android TextView(同时显示图片+文字)