自定义的TextView,使部分内容拥有点击事件,并在点击内容上方出现对应的词义等信息

直接看效果图:

上面图中是一个TextView,注册部分内容(KeyWord)是其拥有点击事件,并通过计算KeyWord的坐标,显示KeyWord的想要的一些信息

/**
 * 一个TextView中包含一个可以点击的KeyWord(关键词),并通过点击关键词,在对应关键词位置正上方展示关键词对应的解释等逻辑
 * @author DuGuang
 *
 */
public class MainActivity extends Activity {

	private KeyWordTextView mTvKeyWord;
	private ShowHideView mShowHideView;
	private DotView mDotView;
	private int mStatusBarHeight; // 手机状态栏高度
	private int mTitleBarHeight;	//手机标题栏的高度

	private RelativeLayout mLlMain;
	private String mHide = "爱";
	private String mStartStr;	//关键词前面的字段
//	private String mStartStr = null;
	private String mKeyWord = "love"; //关键词
	private String mEndStr ;	//关键词后面的字段
//	private String mEndStr = null;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		initView();
		initData();
	}

	/**
	 * 初始化View
	 */
	private void initView() {
		mTvKeyWord = (KeyWordTextView) findViewById(R.id.mTvKeyWord);
		mLlMain = (RelativeLayout) findViewById(R.id.mLlMain);

		mShowHideView = new ShowHideView(this);
		mDotView = new DotView(this);

		RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(
				RelativeLayout.LayoutParams.WRAP_CONTENT,
				RelativeLayout.LayoutParams.WRAP_CONTENT);
		mShowHideView.setLayoutParams(params);
		mDotView.setLayoutParams(params);
		mShowHideView.setVisibility(View.INVISIBLE);

		mLlMain.addView(mShowHideView);
		mLlMain.addView(mDotView);
	}

	/**
	 * 填充数据
	 */
	private void initData() {
		mStartStr = "Whatever is worth doing is worth haha zhen de ke yi me, hao xi fan! Whatever is worth doing is worth haha zhen de ke yi me, hao xi fan! Whatever is worth doing is worth haha zhen de ke yi me, hao xi fan! Whatever is worth doing is worth haha zhen de ke yi me, hao xi fan! Whatever is worth doing is worth haha zhen de ke yi me, hao xi fan! Whatever is worth doing is worth haha zhen de ke yi me, hao xi fan! ";
		mEndStr = " Whatever is worth doing is worth doing well.";

		//这里开启子线程是为获取状态栏和标题栏的高度
		mTvKeyWord.post(new Runnable() {

			@Override
			public void run() {
				getBarHeight();
				mTvKeyWord.setAllString(mStartStr, mKeyWord, mEndStr, mHide,
						mStatusBarHeight, mTitleBarHeight, mShowHideView,  mDotView);
			}
		});

	}

	/**
	 * 获取手机状态栏和标题栏的高度
	 */
	private void getBarHeight() {
		Rect frame = new Rect();
		getWindow().getDecorView().getWindowVisibleDisplayFrame(frame);
		Window window = getWindow();
		// 状态栏的高度
		mStatusBarHeight = frame.top;
		// 标题栏跟状态栏的总体高度
		int contentViewTop = window.findViewById(Window.ID_ANDROID_CONTENT).getTop();
		// 标题栏的高度:用上面的值减去状态栏的高度及为标题栏高度
		mTitleBarHeight = contentViewTop - mStatusBarHeight;
		Log.i("dg", mStatusBarHeight + "..." + contentViewTop + "..."
				+ mTitleBarHeight);

	}

}
/**
 * 一个TextView中包含一个可以点击的KeyWord(关键词),并通过点击关键词,在对应关键词位置正上方展示关键词对应的解释等逻辑
 *
 * @author DuGuang
 * @date 2015.1.11
 *
 */
public class KeyWordTextView extends TextView {

	protected static final String TAG = KeyWordTextView.class.getSimpleName();
	private Context mContext;
	private String mHide; // 关键词的提示内容
	private ShowHideView mShowHideView;	//show关键词解释
	private DotView mDotView;	//画一个圆点,效验mShowHideView展示的位置是否正确

	private String mStartStr, mKeyWord, mEndStr;

	int mYStartTop;// 关键词第一个字符顶部y坐标
	int mYStartBottom;// 关键词第一个字符底部y坐标
	float mXStartLeft;// 关键词第一个字符左边x坐标
	float mXStartRight;// 关键词第一个字符右边x坐标

	int mYEndTop;// 关键词最后一个字符顶部y坐标
	int mYEndBottom;// 关键词最后一个字符底部y坐标
	float mXEndLeft;// 关键词最后一个字符左边x坐标
	float mXEndRight;// 关键词最后一个字符右边x坐标

	private Layout mLayout;
	private int mStartPosition; // 关键词起始的位置
	private int mEndPosition; // 关键词结束的位置

	private int mStatusBarHeight; //手机状态栏高度
	private int mTitleBarHeight;	//手机标题栏的高度
	private SpannableString mSpStr;	//用于给KeyWord单独加颜色和下划线的类

	@SuppressLint("NewApi")
	public KeyWordTextView(Context context, AttributeSet attrs, int defStyleAttr) {
		super(context, attrs, defStyleAttr);
		this.mContext = context;
	}

	public KeyWordTextView(Context context, AttributeSet attrs) {
		super(context, attrs);
		this.mContext = context;
	}

	public KeyWordTextView(Context context) {
		super(context);
		this.mContext = context;
	}

	/**
	 * 填充段落内容
	 *
	 * @param startStr
	 *
	 * @param keyWord
	 *
	 * @param endStr
	 *
	 */
	/**
	 * @param startStr
	 * @param keyWord
	 * @param endStr
	 * @param hide
	 * @param mDotView
	 * @param mTitleBarHeight
	 * @param mShowHideView
	 */
	/**
	 * @param startStr	关键词之前显示的内容,如果没有则传null
	 * @param keyWord	关键词,可以点击的词
	 * @param endStr	关键词之后显示的内容,如果没有则传null
	 * @param hide		展示关键词的解释字段
	 * @param statusBarHeight	状态栏的高度
	 * @param titleBarHeight	标题栏的高度
	 * @param showHideView	展示关键词的View
	 * @param dotView	画一个圆点,效验mShowHideView展示的位置是否正确
	 */
	public void setAllString(String startStr, String keyWord, String endStr, String hide, int statusBarHeight, int titleBarHeight,
			ShowHideView showHideView, DotView dotView) {
		this.mStartStr = startStr;
		this.mKeyWord = keyWord;
		this.mEndStr = endStr;

		this.mHide = hide;
		this.mStatusBarHeight = statusBarHeight;
		this.mShowHideView = showHideView;
		this.mTitleBarHeight = titleBarHeight;
		this.mDotView = dotView;

		setKeyWordClick();
		initData();
		getTextLayout();
	}

	/**
	 * 填充相关数据
	 */
	private void initData() {
		// 计算关键词的起始位置
		mStartPosition = StringUtil.isNotEmpty(mStartStr) ? mStartStr.length() + 1 : 1;

		//计算关键词结束所在位置
		mEndPosition = StringUtil.isNotEmpty(mEndStr) ?mStartPosition + mKeyWord.length()+1 : mStartPosition + mKeyWord.length() -1 ;

		setText(mStartStr);
		append(mSpStr);
		append(StringUtil.isNotEmpty(mEndStr) ? mEndStr : "");
		setMovementMethod(LinkMovementMethod.getInstance());// 相当于注册关键词的点击事件(开始响应点击事件)
	}

	/**
	 * 获取对应TextView的layout属性
	 */
	private void getTextLayout() {
		// view变化监听器ViewTreeObserver介绍
		ViewTreeObserver vto = getViewTreeObserver();
		vto.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
			@Override
			public void onGlobalLayout() {
				mLayout = getLayout();
				Rect bound = new Rect();
				int line = mLayout.getLineForOffset(mStartPosition);

				mLayout.getLineBounds(line, bound);

				mYStartTop = bound.top;
				mYStartBottom = bound.bottom;

				mXStartLeft = mLayout.getPrimaryHorizontal(mStartPosition);
				mXStartRight = mLayout.getSecondaryHorizontal(mStartPosition);

			}
		});
	}

	/**
	 * 设置关键词的点击事件
	 */
	private void setKeyWordClick() {
		mSpStr = new SpannableString(mKeyWord);
		mSpStr.setSpan(new ClickableSpan() {
			@Override
			public void updateDrawState(TextPaint ds) {
				super.updateDrawState(ds);
				 ds.setColor(Color.BLUE); //设置文字颜色
				ds.setUnderlineText(true); // 设置下划线
			}

			@Override
			public void onClick(View widget) {

				Log.d("", "onTextClick........被点击了");
				Log.i(TAG, "mYStartTop >>> " + mYStartTop);
				Log.i(TAG, "mYStartBottom >>> " + mYStartBottom);
				Log.i(TAG, "mXStartLeft >>> " + mXStartLeft);
				Log.i(TAG, "mXStartRight >>> " + mXStartRight);

				int[] location1 = new int[2];
//				getLocationInWindow(location1);
				getLocationOnScreen(location1);
				Log.i(TAG, "location1[0] >>> " + location1[0]);
				Log.i(TAG, "location1[1] >>> " + location1[1]);

				getKeyWordEndInfo();
//				RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
//				params.leftMargin = 100;
//				params.topMargin = 100;
//				mdot.setLayoutParams(params);

				RelativeLayout.LayoutParams params2 = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
				params2.leftMargin = (int)(mXStartLeft+(mXEndRight - mXStartLeft)/2)- mShowHideView.getWidth()/2;
				params2.topMargin = (int)location1[1] - mStatusBarHeight - mTitleBarHeight + mYStartTop - mShowHideView.getHeight();
				mShowHideView.setLayoutParams(params2);
				mShowHideView.setPosition(mHide);

				mDotView.setPosition((int)(mXStartLeft+(mXEndRight - mXStartLeft)/2), (int)location1[1] - mStatusBarHeight - mTitleBarHeight + mYStartTop);

			}
		}, 0, mKeyWord.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);

	}

	/**
	 * 获取关键词最后一个字符的X,Y的相关信息
	 */
	public void getKeyWordEndInfo(){
		mLayout = getLayout();
		Rect bound = new Rect();
		int line = mLayout.getLineForOffset(mEndPosition);

		mLayout.getLineBounds(line, bound);

		mYEndTop = bound.top;
		mYEndBottom = bound.bottom;

		mXEndLeft = mLayout.getPrimaryHorizontal(mEndPosition);
		mXEndRight = mLayout.getSecondaryHorizontal(mEndPosition);

		Log.i(TAG, "mYEndTop >>> " + mYEndTop);
		Log.i(TAG, "mYEndBottom >>> " + mYEndBottom);
		Log.i(TAG, "mXEndLeft >>> " + mXEndLeft);
		Log.i(TAG, "mXEndRight >>> " + mXEndRight);
	}

	@Override
	protected void onDraw(Canvas canvas) {
		super.onDraw(canvas);

	}

}
/**
 * 点击KeyWord后,显示KeyWord的相关信息的View
 * @author DuGuang
 *
 */
public class ShowHideView extends LinearLayout{

	private Context mContext;
	private TextView mTvShowHide;

	@SuppressLint("NewApi")
	public ShowHideView(Context context, AttributeSet attrs, int defStyleAttr) {
		super(context, attrs, defStyleAttr);
		this.mContext = context;
		initView();
	}

	public ShowHideView(Context context, AttributeSet attrs) {
		super(context, attrs);
		this.mContext = context;
		initView();
	}

	public ShowHideView(Context context) {
		super(context);
		this.mContext = context;
		initView();
	}

	public void setPosition(String hide){
		mTvShowHide.setText(hide);
		setVisibility(View.VISIBLE);
	}

	/**
	 * 填充自定义的XML布局
	 */
	public void initView() {
		View view = View.inflate(mContext, R.layout.item_popwin_start_hide,ShowHideView.this);
		mTvShowHide = (TextView) view.findViewById(R.id.tv_paly_hide);
	}

}
/**
 * 画一个点,用来效验ShowHideView显示的位置是否正确
 * @author DuGuang
 *
 */
public class DotView extends View{

	private static final String TAG = DotView.class.getSimpleName();
	private Context mContext;

	private float mX;
	private float mY;
	@SuppressLint("NewApi")
	public DotView(Context context, AttributeSet attrs, int defStyleAttr) {
		super(context, attrs, defStyleAttr);
		this.mContext = context;
	}

	public DotView(Context context, AttributeSet attrs) {
		super(context, attrs);
		this.mContext = context;
	}

	public DotView(Context context) {
		super(context);
		this.mContext = context;
	}

	public void setPosition(int x, int y){
		mX = x;
		mY = y;
		invalidate();
	}

	@Override
	protected void onDraw(Canvas canvas) {
		super.onDraw(canvas);

		Paint paint  = new Paint();
		paint.setStyle(Style.FILL);
		paint.setStrokeWidth(20);
		paint.setColor(mContext.getResources().getColor(R.color.blue));

		Log.i(TAG, "mX >>> "+mX);
		Log.i(TAG, "mY >>> "+mY);
		canvas.drawPoint(mX, mY, paint);
	}

}

Demo下载地址:

Github地址:

时间: 2024-12-12 11:40:04

自定义的TextView,使部分内容拥有点击事件,并在点击内容上方出现对应的词义等信息的相关文章

自定义图例点击事件

https://bbs.hcharts.cn/article-109-1.html ***************************************** 图表自带的图例点击事件是: 点击某个显示/隐藏的图例,该图例对应的serie就隐藏/显示. 个人觉得不合理,正常来说,有多条折线(或其他类型的图表),点击某个图例是想只看该图例对应的数据: 于是修改了图例点击事件. 实现的效果是(以折线图为例): 1. 当某条折线隐藏时,点击该折线的图例 --> 该折线显示: 2. 当全部折线都显

自定义TextView使之具有跑马灯的效果

一.问题的引入 使用普通的textview跑马的效果,一开始没有焦点事件不会进行滚动,button有焦点事件,但是比较难看,因此需要自定一个TextView 一出生就有焦点 然后需要自定义一个textview FocusedTextView.java package com.xuliugen.mobilesafe.ui; import android.content.Context; import android.util.AttributeSet; import android.view.Vi

AndroidRichText 让Textview轻松的支持富文本(图像ImageSpan、点击效果等等类似QQ微信聊天)

代码地址:https://github.com/Luction/AndroidRichText AndroidRichText帮助实现像QQ,微信一样的,一个TextView里既有文字又有表情又有图片的效果,采用插件化的框架,代码简单,可拓展性强. 基础框架包只有四个java文件, RichTextWrapper :TextView的包裹类,实现支持富文本,通过new RichTextWrapper(TextView v)来构造. RTMovementMethod: 继承自Android原生的L

Android自定义适配器和ListView的点击事件相结合的使用

下边演示一个使用ListView和自定义适配器的案例,点击ListView中的条目会出现一个对话框,进行成绩的修改,修改之后会立即通知适配器进行数据的重新加载,如下: (1).用于显示listView的布局文件: <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" andro

Android 自定义View——自定义点击事件

每个人手机上都有通讯录,这是毫无疑问的,我们通讯录上有一个控件,在通讯录的最左边有一列从"#"到"Z"的字母,我们通过滑动或点击指定的字母来确定联系人的位置,进而找到联系人.我们这一节就通过开发这个控件,来学如何自定义控件的点击事件. 通讯录列表查找控件界面绘制 首先我们需要先将控件的基本布局绘制出来,这里我们不在做详细的解释,在<Android 自定义View--自定义View控件 >博客中,我们已经详细讲解了如何绘制自定义控件的布局.通讯录列表查找控

Android项目实战(十六):QQ空间实现(一)—— 展示说说中的评论内容并有相应点击事件

原文:Android项目实战(十六):QQ空间实现(一)-- 展示说说中的评论内容并有相应点击事件 大家都玩QQ空间客户端,对于每一个说说,我们都可以评论,那么,对于某一条评论: 白雪公主 回复 小矮人 : 你们好啊~ 我们来分析一下: 1.QQ空间允许我们 点击 回复人和被回复人的名字就可以进入对于用户的个人主页(即点击文字“白雪公主”/“小矮人”,就可以进入到这俩用户相应个人主页) 2.点击 回复的文字,就可以对回复人进行回复(即点击评论中回复的内容“你们好啊~”,便对弹出一个编辑框对回复人

ListView使用自定义适配器的情况下实现适配器的文本和图标控件点击事件执行Activity界面中的方法

ListView使用的是自定义适配器,列表项的布局文件中含有文本和图标,实现文本区域和图标区域的点击事件. 实现思路:在自定义适配器MyArrayAdapter 类型中自定义接口和接口方法,分别设置文本区域和图标区域的OnClickListener,然后在activity界面中MyArrayAdapter实例实现这个接口. 1.listitem布局文件 <?xml version="1.0" encoding="utf-8"?> <LinearLa

listView长按事件内修改ListView对象内容

============问题描述============ 我在ListView的Item长按事件内打开一个弹出窗口,窗口内有一个EditText对象,在这个编辑框内输入文本点确定后,希望可以直接修改掉ListView对象内某个TextView对象的内容:现在ListView长按事件内直接修改没有问题了,只是弹出一个窗口再修改不知道该如何实现: ============解决方案1============ 引用 楼主 hackerlyf 的回复: 我在ListView的Item长按事件内打开一个弹出

设置TextView下划线并响应点击事件(SpannableString)

写Demo程序的时候能表带自定义的数据结构对象吗? --低级程序猿 前情提要:网上介绍TextView+SpannableString的文章真心太长,真心看不懂. ====原文===== 下面是一个20行的完整Demo代码:基本原理是使用一个SpannableString并设置其ClickableSpan来响应点击事件. TextView useInfo = (TextView) findViewById(R.id.info); String url_0_text = "用户协议及隐私条款&qu