android之仿豆瓣写日志
Posted 小钟视野
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了android之仿豆瓣写日志相关的知识,希望对你有一定的参考价值。
先来看看某帮的效果图:所说的也是类似的效果图
图1是正常编辑文本以及插入图片时的状态图,图2是长按拖动图片位置的状态
难点剖析
- 控件拖动:主要用gitHub上的开源控件DragListView 控件地址 这里不再做讲解
- RecycleView中光标是如何定位在指定的控件
- 如何解决部分机型识别不了键盘中的删除键、回车键
- 如何将图片插入相应的位置
- 如何实现并发上传图片
逻辑讲解
正常输入文本:
- 当按下回车键时,获取光标的位置;
(1)光标在文本头部,则在文本之上添加一个EditText
(2)光标在文本尾部,则在文本之后添加一个EditText
(3)光标在文本之间,则将文本分成两段,并将后一段的文本内容赋值给新建的EditText - 当按下删除键时,获取光标位置
(1)光标在文本头部,则判断是前一个preItem是存在;如果此preItem不是图片,则将删除光标所在的 item,并且将内容追加在preItem中;如果preItem是图片,则弹出是否要删除图片
(2)光标不再头部,则不做处理 - 当点击添加图片:
<1>、定位光标(已有焦点): 则选完图之后,将图片插入到光标所在位置,规则同回车键;图片和图片之间要添加一个EditText,如果最后的一个插入的位置下一个nextItem正好EditText,则不需要增加EditText
<2>、上一次已经定位光标并当前失去焦点(没有光标):则将定位到相应的位置,让图片插入到此item之后
<3>、未曾获取过焦点或光标,比如进入页面就选图,则将图片插入到第一个item之后 - 当点击输入表情:
<1>、未曾获取过光标:则将焦点默认选择在第一个item - 点击空白处要获取焦点,弹出键盘,总之要判断当前点击的是空白处还是EditText;
<1>RecyclerView中光标是如何定位在指定的控件
不管是ListView或RecyclerView都有复用机制,导致定位光标可能会出现错乱的现象,再加上RecyclerView的NotifyXXX方法很是丰富,不会刷新所有的item,导致holder一直持有的旧的position,如果如果通过position作为item的唯一标识,那么在插入文本时会找不到要定位的EditText;所以一定要给item一个唯一标识,不管是刷新还是不刷新次id必须是唯一的且能映射到item;
先看代码如下:
public class EditViewHolder extends DragItemAdapter.ViewHolder<TopicPublishModelNew.TypeContent>
PubTopicEditText etEditorTopic;
PublishTopicAdapter mItemAdapter;
//由于使用holderView缓存pos和view,当删除item时,不可见的view,不会执行onBindViewHolder,
//所以无法删除或换行(pos = 5 删除一项后,还是5,导致pos>数据源的size,会数组越界)
// 于是用getItemId(每个item都是唯一值)查找现在item的pos。。。。
// int itemPos = -1;//不可使用,不实时调用onBindViewHolder
public EditViewHolder(final View itemView, PublishTopicAdapter itemAdapter)
super(itemView, itemAdapter.getGrabHandleId(), itemAdapter.getDragOnLongPress());
this.mItemAdapter = itemAdapter;
etEditorTopic = (PubTopicEditText) itemView.findViewById(R.id.et_editor_topic);
setEditTextListener();
private void setEditTextListener()
etEditorTopic.setTextWatchListener(new PubTopicEditText.TextWatchListener()
@Override
public void clickEnterKey(View v, CharSequence splitEnterBefore, CharSequence splitEnterAfter)
int itemPos = mItemAdapter.getPositionForItemId(mItemId);
if (isLegalPosition(itemPos))
mItemAdapter.getItem(itemPos).info.content = splitEnterBefore;
etEditorTopic.setText(splitEnterBefore);
long uniqueId = mItemAdapter.getItemUniqueId();
mItemAdapter.getCursorItem().setEtFocusId(uniqueId);//让焦点换行
mItemAdapter.getCursorItem().setInsertImgParams(0, splitEnterAfter);//回车后,光标在首位
mItemAdapter.getCursorItem().setCursorEditeText(null);
mItemAdapter.getCursorItem().setCurCursorIndex(0);//换行后,光标应该显示的位置
mItemAdapter.addItem(itemPos + 1,
TopicPublishModelNew.TypeContent.createTextInstance(uniqueId, splitEnterAfter));
mItemAdapter.notifyItemRangeChanged(itemPos, 2);
mItemAdapter.setPressEnterKey(true);
etEditorTopic.clearFocus();//先清除焦点,否则会偶现获取两次焦点,直接定位到第3行的情况
final int scrollPos = itemPos + 1;
PublishTopicAdapter.ScrollPosListener scrollPosListener = mItemAdapter.getScrollPosListener();
if (null != scrollPosListener) //换行后,滚动到RecycleView到相应的位置
scrollPosListener.onScrollToPos(scrollPos);
//将string改成CharSequence之后,content和这个是一个对象,字符长度是一样的
@Override
public void clickDelKey(View v, CharSequence text, int delCount)
int itemPos = mItemAdapter.getPositionForItemId(mItemId);
if (isLegalPosition(itemPos))
TopicPublishModelNew.PublishContent item = mItemAdapter.getItem(itemPos).info;
mItemAdapter.delCharsCount(delCount);
item.content = text;
@Override
public void delEnterChar(View v, CharSequence text)
int itemPos = mItemAdapter.getPositionForItemId(mItemId);//
//首行不需要删除或者文本行数至少有一行
if (itemPos > 0 &&
itemPos < mItemAdapter.getItemCount() &&
TopicPublishModelNew.TypeContent.edtiTextCount > 1)
int prevItem = itemPos - 1;
TopicPublishModelNew.TypeContent mTypeItem = mItemAdapter.getItem(prevItem);
if (TopicPublishModelNew.TYPE_TEXT.equals(mTypeItem.type)) //删除文本
int size = mTypeItem.info.content.length();
CharSequence srcText = mItemAdapter.getItem(prevItem).info.content;
mItemAdapter.getItem(prevItem).info.content =
new SpannableStringBuilder(srcText).append(text);
mItemAdapter.getCursorItem().setCurCursorIndex(size);//删除换行后,光标应该显示的位置
mItemAdapter.getCursorItem().setEtFocusId(mTypeItem.position);
mItemAdapter.setPressDelEnterKey(true);
mItemAdapter.notifyItemChanged(prevItem);
mItemAdapter.removeItem(itemPos);
TopicPublishModelNew.TypeContent.edtiTextCount--;
else //删除图片
mItemAdapter.showDeleteImgDialog(prevItem);
@Override
public void onTextChanged(CharSequence text, int start, int before, int addCount)
//将string改成CharSequence之后,content和这个是一个对象,字符长度是一样的
int itemPos = mItemAdapter.getPositionForItemId(mItemId);//
if (isLegalPosition(itemPos))
TopicPublishModelNew.TypeContent item = mItemAdapter.getItem(itemPos);
TopicPublishModelNew.PublishContent info = item.info;
if (null != info)
if (text == info.content) //是同一个对象,不需要重新赋值
//添加字符的个数,也可能是删除负数,选中点击删除键就是负值
mItemAdapter.addCharsCount(addCount);
else if (null == info.content ||
!text.toString().equals(info.content.toString()))
//不是同一个对象,需要重新赋值,且要计算增加个数
//添加字符的个数,也可能是删除负数,选中点击删除键就是负值
mItemAdapter.addCharsCount(addCount);
//防止:列表来回滑动,会重新赋值不再是同一个对象,不需要统计字数
//将string改成CharSequence之后,content和这个是一个对象,字符长度是一样的,这个赋值可以不用
item.info.content = text;
);
etEditorTopic.setOnFocusChangeListener(new View.OnFocusChangeListener()
@Override
public void onFocusChange(View v, boolean hasFocus)
if (hasFocus) //某个et获取焦点
//正常手指触摸获取焦点
if (!mItemAdapter.isPressEnterKey() &&
!mItemAdapter.isPressDelEnterKey())
mItemAdapter.getCursorItem().setEtFocusId(mItemId);
mItemAdapter.getCursorItem()
.setInsertImgParams(etEditorTopic.getSelectionStart()
,etEditorTopic.getText().toString());
mItemAdapter.getCursorItem().setCursorEditeText(etEditorTopic);
mItemAdapter.setPressEnterKey(false);
mItemAdapter.setPressDelEnterKey(false);
else //上个et失去焦点:顺序,上个et失去焦点,当前et获取焦点
);
private boolean isLegalPosition(int itemPos)
return itemPos > -1 && itemPos < mItemAdapter.getItemCount();
@Override
public void updateView(TopicPublishModelNew.TypeContent model, final int position)
TopicPublishModelNew.PublishContent content = model.info;
if (null != content)
CharSequence text = content.content;
//内容且图片为空才显示提示语
if (0 == position &&
mItemAdapter.getContentCount() == 0 &&
0 == mItemAdapter.getSelectedImgCount())
etEditorTopic.setHint("请输入正文");
else
etEditorTopic.setHint("");
etEditorTopic.setText(text);
itemView.setTag(model);
etEditorTopic.post(new Runnable()
@Override
public void run()
setEtLines(etEditorTopic);
//这个etTopic获取焦点
if (position == mItemAdapter.getCursorItem().getEtFocudPostion())
etEditorTopic.requestFocus();
mItemAdapter.getCursorItem().setCursorEditeText(etEditorTopic);
int curCursorIndex = mItemAdapter.getCursorItem().getCurCursorIndex();
if (-1 != curCursorIndex && curCursorIndex <= etEditorTopic.length())
etEditorTopic.setSelection(curCursorIndex);
mItemAdapter.getCursorItem().setCurCursorIndex(-1);
else
etEditorTopic.setSelection(etEditorTopic.length());
);
//监听表情需要传入EditText
mItemAdapter.setEmojiEditText(etEditorTopic);
private void setEtLines(PubTopicEditText etEditorTopic)
if (mItemAdapter.isStartDraged()) //将其缩放,拖动的时候
if (etEditorTopic.getLineCount() > 3)
etEditorTopic.setMaxLines(3);
etEditorTopic.setPadding(20, 20, 20, 10);
etEditorTopic.setBackgroundResource(R.drawable.publish_topic_item_bg_shape);
else
etEditorTopic.setPadding(4, 0, 0, 0);
etEditorTopic.setMaxLines(Integer.MAX_VALUE);
etEditorTopic.setBackgroundResource(0);
从上可知,主要代码就是setEditTextListener()这个方法里边的监听器,而最主要的逻辑是etEditorTopic.setTextWatchListener(new PubTopicEditText.TextWatchListener() )
这个监听器的四个方法的实现;这个是是在他的父类里边实现的;这里我们先来说说简单的逻辑,之后再看看父类的主要实现;
先来看看当执行onBindViewHolder方法时,会调用updateView更新数据
public void updateView(TopicPublishModelNew.TypeContent model, final int position)
TopicPublishModelNew.PublishContent content = model.info;
if (null != content)
CharSequence text = content.content;
//内容且图片为空才显示提示语
if (0 == position && mItemAdapter.getContentCount() == 0 && 0 == mItemAdapter.getSelectedImgCount())
etEditorTopic.setHint("请输入正文");
else
etEditorTopic.setHint("");
etEditorTopic.setText(text);
itemView.setTag(model);
etEditorTopic.post(new Runnable()
@Override
public void run()
setEtLines(etEditorTopic);
//这个etTopic获取焦点
if (position == mItemAdapter.getCursorItem().getEtFocudPostion())
etEditorTopic.requestFocus();
mItemAdapter.getCursorItem().setCursorEditeText(etEditorTopic);
int curCursorIndex = mItemAdapter.getCursorItem().getCurCursorIndex();
if (-1 != curCursorIndex && curCursorIndex <= etEditorTopic.length())
etEditorTopic.setSelection(curCursorIndex);
mItemAdapter.getCursorItem().setCurCursorIndex(-1);
else
etEditorTopic.setSelection(etEditorTopic.length());
);
//监听表情需要传入EditText
mItemAdapter.setEmojiEditText(etEditorTopic);
这里主要是更新数据:当插入文本、输入内容、插入图片等等时,更新内容
mItemAdapter.getCursorItem()这个主要是管理当前光标相关的;比如光标所在的位置、光标和item绑定的唯一id、通过id映射到相应的item的position等;
为什么使用post?post里边执行的又是什么逻辑?
其实:
1. setEtLines(etEditorTopic);要获取当前的EditText的行号,以及长按状态下要有固定的可拖动的item高和背景,而高是通过行数控制的,拖动情况下最大行数是3
2. 获取当前要获得焦点的EditText,通过
mItemAdapter.getCursorItem().getEtFocudPostion()获取要聚焦的EditText的当前position
再来看看获取焦点的监听器
etEditorTopic.setOnFocusChangeListener(new View.OnFocusChangeListener()
@Override
public void onFocusChange(View v, boolean hasFocus)
if (hasFocus) //某个et获取焦点
//正常手指触摸获取焦点
if (!mItemAdapter.isPressEnterKey() &&
!mItemAdapter.isPressDelEnterKey())
mItemAdapter.getCursorItem().setEtFocusId(mItemId);
mItemAdapter.getCursorItem()
.setInsertImgParams(
etEditorTopic.getSelectionStart(),
etEditorTopic.getText().toString());
mItemAdapter.getCursorItem().setCursorEditeText(etEditorTopic);
mItemAdapter.setPressEnterKey(false);
mItemAdapter.setPressDelEnterKey(false);
else //上个et失去焦点:顺序,上个et失去焦点,当前et获取焦点
);
当获取或失去焦点时就会执行这个方法:而获取焦点有正常点击和updateView()中etEditorTopic.requestFocus();
此时要更新焦点所在的item的FoucsId也就是唯一id(mItemId);以及光标的位置和此Item的EditText实例,因为光标位置所在的item来回点击更换光标的位置不会实时更新,需要通过实例获取光标位置才是准确的;
再来看看那主要的四个方法:接口说明如下:
public interface TextWatchListener
/**
* 点击系统回车键
*
* @param v
* @param splitEnterAfter 按回车键后,字符分成两段,获取后一段字符串
* @param splitEnterBefore 按回车键后,字符分成两段,获取前一段字符串
*/
void clickEnterKey(View v, CharSequence splitEnterBefore, CharSequence splitEnterAfter);
/***
* 点击系统的删除键
* @param v EditText
* @param text 删除后剩余的字符
*@param delCount 删除字数
*/
void clickDelKey(View v, CharSequence text, int delCount);
/**
* 删除换行符 光标在头部然后点击删除键
*
* @param v
* @param text 删除换行后,剩余的字符串
*/
void delEnterChar(View v, CharSequence text);
/****
*
* @param s
* @param start
* @param before
* @param addCount 字符串增加个数
*/
void onTextChanged(CharSequence s, int start, int before, int addCount);
总结来说:四个方法一次只会回调一个
1. clickEnterKey():是指当点击系统回车键 时回调
2. clickDelKey(): 是指当点击系统的删除键时回调
3. delEnterChar():是指当删除换行符时回调:删除换行符也就是光标在头部然后点击删除键
4. onTextChanged():内容发生变化,当以上3个方法都不执行时就会回调这个方法
这四个方法一个个分析:
public void clickEnterKey(View v, CharSequence splitEnterBefore, CharSequence splitEnterAfter)
int itemPos = mItemAdapter.getPositionForItemId(mItemId);
if (isLegalPosition(itemPos))
mItemAdapter.getItem(itemPos).info.content = splitEnterBefore;
etEditorTopic.setText(splitEnterBefore);
long uniqueId = mItemAdapter.getItemUniqueId();
mItemAdapter.getCursorItem().setEtFocusId(uniqueId);//让焦点换行
mItemAdapter.getCursorItem().setInsertImgParams(0, splitEnterAfter);//回车后,光标在首位
mItemAdapter.getCursorItem().setCursorEditeText(null);
mItemAdapter.getCursorItem().setCurCursorIndex(0);//换行后,光标应该显示的位置
mItemAdapter.addItem(itemPos + 1, TopicPublishModelNew.TypeContent.createTextInstance(uniqueId, splitEnterAfter));
mItemAdapter.notifyItemRangeChanged(itemPos, 2);
mItemAdapter.setPressEnterKey(true);
etEditorTopic.clearFocus();//先清除焦点,否则会偶现获取两次焦点,直接定位到第3行的情况
final int scrollPos = itemPos + 1;
PublishTopicAdapter.ScrollPosListener scrollPosListener = mItemAdapter.getScrollPosListener();
if (null != scrollPosListener) //换行后,滚动到RecycleView到相应的位置
scrollPosListener.onScrollToPos(scrollPos);
当点击回车键后,就是换行,则创建一个新的EditText的控件,将splitEnterAfter(光标之后的文本内容)赋值,将splitEnterBefore赋值给原来的控件;将焦点放到新创建的EditText,所以要更新mItemAdapter.getCursorItem()的一系列信息,如:
long uniqueId = mItemAdapter.getItemUniqueId();//这就是标识item的唯一id
mItemAdapter.getCursorItem().setEtFocusId(uniqueId);//让焦点换行
然后notifyxxxx()执行updateView,此时就会根据mItemId(也就是uniqueId )获取焦点的position,直接
etEditorTopic.requestFocus();流程同之前讲述的。
然后定位RecycleView到相应位置,显示光标;
//将string改成CharSequence之后,content和这个是一个对象,字符长度是一样的
@Override
public void clickDelKey(View v, CharSequence text, int delCount)
int itemPos = mItemAdapter.getPositionForItemId(mItemId);
if (isLegalPosition(itemPos))
TopicPublishModelNew.PublishContent item = mItemAdapter.getItem(itemPos).info;
mItemAdapter.delCharsCount(delCount);
item.content = text;
当点击删除键之后:主要是用于统计删除的字符个数以及重新赋值给数据源;
getPositionForItemId此方法是根据唯一值id映射item的position
public void delEnterChar(View v, CharSequence text)
int itemPos = mItemAdapter.getPositionForItemId(mItemId);//
if (itemPos > 0 && itemPos < mItemAdapter.getItemCount() && TopicPublishModelNew.TypeContent.edtiTextCount > 1) //首行不需要删除或者文本行数至少有一行
int prevItem = itemPos - 1;
TopicPublishModelNew.TypeContent mTypeItem = mItemAdapter.getItem(prevItem);
if (TopicPublishModelNew.TYPE_TEXT.equals(mTypeItem.type)) //删除文本
int size = mTypeItem.info.content.length();
CharSequence srcText = mItemAdapter.getItem(prevItem).info.content;
mItemAdapter.getItem(prevItem).info.content =
new SpannableStringBuilder(srcText).append(text);
mItemAdapter.getCursorItem().setCurCursorIndex(size);//删除换行后,光标应该显示的位置
mItemAdapter.getCursorItem().setEtFocusId(mTypeItem.position);
mItemAdapter.setPressDelEnterKey(true);
mItemAdapter.notifyItemChanged(prevItem);
mItemAdapter.removeItem(itemPos);
TopicPublishModelNew.TypeContent.edtiTextCount--;
else //删除图片
mItemAdapter.showDeleteImgDialog(prevItem);
当点击删除回车符,即:光标在首位,又点击了删除键,则表示想删除此item,将内容和上一行合并;
拿到当前内容,判断preItem是否是图片,若是图片,则弹出是否删除图片;若不是图片,则将preItem和当前item内容合并,删除当前EditText,更新CursorItem,统计EditText个数;最后notifyXXX();更新获取焦点
public void onTextChanged(CharSequence text, int start, int before, int addCount) //将string改成CharSequence之后,content和这个是一个对象,字符长度是一样的
int itemPos = mItemAdapter.getPositionForItemId(mItemId);//
if (isLegalPosition(itemPos))
TopicPublishModelNew.TypeContent item = mItemAdapter.getItem(itemPos);
TopicPublishModelNew.PublishContent info = item.info;
if (null != info)
if (text == info.content) //是同一个对象,不需要重新赋值
//添加字符的个数,也可能是删除负数,选中点击删除键就是负值
mItemAdapter.addCharsCount(addCount);
else if (null == info.content ||
!text.toString().equals(info.content.toString()))
//不是同一个对象,需要重新赋值,且要计算增加个数
//添加字符的个数,也可能是删除负数,选中点击删除键就是负值
mItemAdapter.addCharsCount(addCount);
//防止:列表来回滑动,会重新赋值不再是同一个对象,不需要统计字数
//将string改成CharSequence之后,content和这个是一个对象,字符长度是一样的,这个赋值可以不用
item.info.content = text;
用于统计内容输入的字符数、数据源更新内容;
这里就分析完了这个方法!!!!
这里注意的是:
由于数据源用的不是String而是CharSequence原因是表情每次都要去解析,很耗性能,而且很多时候解析不出来;关于表情的问题,这里不做讲解 ,之后在讨论,或者有需要的留言讨论;
接下来看看父类是如何实现这四个方法的:立刻上代码
public class PubTopicEditText extends EmojiEditText
private static final String TAG = "===111 PubTopicEditText";
/***是否点击删除键*/
private boolean isClickDelKey = false;
/***是不是按了回车键*/
private boolean isClickEnterKey = false;
public PubTopicEditText(Context context)
this(context, null);
public PubTopicEditText(Context context, AttributeSet attrs)
this(context, attrs, 0);
public PubTopicEditText(Context context, AttributeSet attrs, int defStyleAttr)
super(context, attrs, defStyleAttr);
initListener();
/***是否有键盘兼容性问题:华为plk-tl01或原生机型监听不到回车键,只能在TextWatch做处理*/
private boolean hasKeyboardCompatibility = true;
private TextWatcher textWatcher = null;
private void initListener()
if (null == textWatcher)
Log.d(TAG, "==textWatcher===");
textWatcher = new TextWatcher()
private int beforeLength;
int selectionStart;
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after)
// Log.d(TAG,"beforeTextChanged s = " + s.toString() + " start = " + start + " count = " + count + " after = " + after);
selectionStart = getSelectionStart();
int selectionEnd = getSelectionEnd();
Log.d(TAG, "beforeTextChanged selectionStart = " + selectionStart + " selectionEnd= " + selectionEnd);
Log.d(TAG, s.length() + " beforeTextChanged s = " + s.toString());
beforeLength = s.length();
@Override
public void afterTextChanged(Editable s)
// Log.d(TAG, "afterTextChanged s = " + s.toString());
/**
* @param s 输入后的所有文本
* @param start 光标的位置
* @param before
* @param count 一次输入字数的个数
*/
@Override
public void onTextChanged(CharSequence s, int start, int before, int count)
if (null != textWatchListener)
if (hasKeyboardCompatibility) //华为有些机型或者原生机型,监听不到键盘的回车键
int addLength = s.length() - beforeLength;
if (addLength == 1 && s.length() >= selectionStart + addLength) //换行符算一个字符
CharSequence addStr = s.subSequence(selectionStart, selectionStart + addLength);
if ("\\n".equals(addStr.toString())) //换行符。。算一个字符
clickEnterKey(selectionStart);
return;
if (isClickDelKey) //删除键
isClickDelKey = false;
textWatchListener.clickDelKey(PubTopicEditText.this, s, beforeLength - s.length());
else if (!isClickEnterKey) //不是回车键 增加字符
textWatchListener.onTextChanged(s, start, before, s.length() - beforeLength);
isClickEnterKey = false;
;
this.addTextChangedListener(textWatcher);
this.setOnKeyListener(new View.OnKeyListener() //华为手机与原生手机等等监听不到键盘的回车键、删除键,只能通过\\n来判断兼容
@Override
public boolean onKey(View v, int keyCode, KeyEvent event)
hasKeyboardCompatibility = false;
if (event.getAction() == KeyEvent.ACTION_DOWN) //要加action_dow否则会执行两次
if (keyCode == KeyEvent.KEYCODE_DEL)
if (delKeyFlag == 2)
return false;
delKeyFlag = 1;
delEnterChar();
// return true;//返回true,则不会删除EditText的文本内容
else if (keyCode == KeyEvent.KEYCODE_ENTER) //点击换行符
clickEnterKey(getSelectionStart());
return true;//返回true,则换行符不会输入到EditText,也就不会换行
return false;
);
/***
* 点击回车键:换行,则新增一个控件
* @param selectionStart
*/
private void clickEnterKey(int selectionStart)
if (null != textWatchListener)
// Log.d(TAG,"clickEnterKey");
CharSequence text = getText();
CharSequence textBefore = "";
CharSequence textAfter = "";
if (!TextUtils.isEmpty(text))
if (-1 != selectionStart)
textBefore = text.subSequence(0, selectionStart);
textAfter = text.subSequence(selectionStart, text.length());
if (!TextUtils.isEmpty(textAfter) && textAfter.toString().startsWith("\\n"))
//要去掉换行符,,否则会多次循环执行onTextChange,导致android Cannot call this method while RecyclerView is computing a layout or scrolling
textAfter = textAfter.subSequence(1, textAfter.length());//过滤换行符
if (textAfter.toString().contains("\\n")) //一般不会包含,因为已经过滤掉了,这里防止有导致异常is computing a layout or scrolling
textAfter = textAfter.toString().replaceAll("\\n", "");//使用这个可能会导致表情解析不出来,所以这里只是作为防止异常产生
isClickEnterKey = true;
textWatchListener.clickEnterKey(PubTopicEditText.this, textBefore, textAfter);
/***光标在控件首位,再次点击,则认为是删除整行:即删除换行符,合并成一行*/
private void delEnterChar()
if (null != textWatchListener)
Log.d(TAG,"delEnterChar");
isClickDelKey = true;
if (isDelEnterLine()) //删除换行符
isClickDelKey = false;
textWatchListener.delEnterChar(PubTopicEditText.this, getText());
/***是否删除换行符:默认第一次新增时是true*/
private boolean isDelEnterLine()
return 0 == getSelectionStart();
/**
* setOnkeyListener监听不到删除键,EditableInputConnection用这玩意监听,
* 这个标志防止delEvent触发两次。
* 0:未初始化;1:使用onKey方法触发;2:使用onDelEvdent方法触发
*/
private int delKeyFlag;
/*****
* 为了兼容华为某些机型监听不到删除键
* @param outAttrs
* @return
*/
@Override
public InputConnection onCreateInputConnection(EditorInfo outAttrs)
super.onCreateInputConnection(outAttrs);
EditableInputConnection editableInputConnection = new EditableInputConnection(this);
outAttrs.initialSelStart = getSelectionStart();
outAttrs.initialSelEnd = getSelectionEnd();
outAttrs.initialCapsMode = editableInputConnection.getCursorCapsMode(getInputType());
editableInputConnection.setDelEventListener(new EditableInputConnection.OnDelEventListener()
@Override
public boolean onDelEvent() //华为有些机型监听不到删除键、兼容性处理
if (delKeyFlag == 1)
return false;
delKeyFlag = 2;
delEnterChar();
return false;
);
delKeyFlag = 0;
return editableInputConnection;
private TextWatchListener textWatchListener ;
public void setTextWatchListener(TextWatchListener textWatchListener)
this.textWatchListener = textWatchListener;
这里直接看监听器:一切逻辑处理都在监听器
先看看常规的监听键盘设置:
this.setOnKeyListener(new View.OnKeyListener() //华为手机与原生手机等等监听不到键盘的回车键、删除键,只能通过\\n来判断兼容
@Override
public boolean onKey(View v, int keyCode, KeyEvent event)
hasKeyboardCompatibility = false;
if (event.getAction() == KeyEvent.ACTION_DOWN) //要加action_dow否则会执行两次
if (keyCode == KeyEvent.KEYCODE_DEL)
if (delKeyFlag == 2)
return false;
delKeyFlag = 1;
delEnterChar();
// return true;//返回true,则不会删除EditText的文本内容
else if (keyCode == KeyEvent.KEYCODE_ENTER) //点击换行符
clickEnterKey(getSelectionStart());
return true;//返回true,则换行符不会输入到EditText,也就不会换行
return false;
);
这里就是平时常用的监听键盘的删除键、回车键等;
先不分析delKeyFlag;等会在看看它的作用
当我们按下删除键、回车键时会分别调用相应的方法delEnterChar();和clickEnterKey()
/***光标在控件首位,再次点击,则认为是删除整行:即删除换行符,合并成一行*/
private void delEnterChar()
if (null != textWatchListener)
Log.d(TAG,"delEnterChar");
isClickDelKey = true;
if (isDelEnterLine()) //删除换行符
isClickDelKey = false;
textWatchListener.delEnterChar(PubTopicEditText.this, getText());
这里会判断是否是删除的换行符也就是是不是合并preItem内容,删除当前EditText;
isClickDelKey :当执行合并内容之后会更新onTextChange,这个标志用于控制回调delEnterChar还是clickDelKey()
/***
* 点击回车键:换行,则新增一个控件
* @param selectionStart
*/
private void clickEnterKey(int selectionStart)
if (null != textWatchListener)
// Log.d(TAG,"clickEnterKey");
CharSequence text = getText();
CharSequence text以上是关于android之仿豆瓣写日志的主要内容,如果未能解决你的问题,请参考以下文章
Android控件GridView之仿支付宝钱包首页带有分割线的GridView九宫格的完美实现
Android控件GridView之仿支付宝钱包首页带有分割线的GridView九宫格的完美实现
Android自定义View实战之仿百度加载动画,一种优雅的Loading方式