接着上一篇的评论点赞弹框之后,这次来说说微信的评论输入框,点击屏幕外部评论框和键盘消失,滑动列表时输入框和键盘也要消失,这里不是说一定要舔微信啥的,只是单纯从技术角度出发,分析原理和实现,解决我们自己的需求.
实现的效果图如下:(软键盘在评论框下面)
1.布局文件main.xml
2.MainActivity代码:
/**
* @作者: njb
* @时间: 2019/7/22 10:53
* @描述: 仿微信朋友圈文本显示全文与收起
*/
public class MainActivity extends AppCompatActivity implements CircleAdapter.MyClickListener, View.OnClickListener {
private RecyclerView recyclerView;
private CircleAdapter circleAdapter;
private String content = "茫茫的长白大山,浩瀚的原始森林,大山脚下,原始森林环抱中散落着几十户人家的" +
"一个小山村,茅草房,对面炕,烟筒立在屋后边。在村东头有一个独立的房子,那就是青年点," +
"窗前有一道小溪流过。学子在这里吃饭,由这里出发每天随社员去地里干活。干的活要么上山伐" +
"树,抬树,要么砍柳树毛子开荒种地。在山里,可听那吆呵声:“顺山倒了!”放树谨防回头棒!" +
"树上的枯枝打到别的树上再蹦回来,这回头棒打人最厉害。";
private List strings;
private LikePopupWindow likePopupWindow;
private int page = 1;
private EditText etComment;
private LinearLayout llComment;
private TextView tvSend;
private LinearLayout llScroll;
private int screenHeight;
private int editTextBodyHeight;
private int currentKeyboardH;
private int selectCommentItemOffset;
private int commentPosition;
protected final String TAG = this.getClass().getSimpleName();
CompositeDisposable compositeDisposable;
private TextView tvCity;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initViews();
initData();
initAdapter();
setListener();
initRxBus();
}
private void initRxBus() {
compositeDisposable = new CompositeDisposable();
RxBus.getInstance().toObservable(WeatherEvent.class)
.subscribe(new Observer() {
@Override
public void onSubscribe(Disposable d) {
compositeDisposable.add(d);
}
@Override
public void onNext(WeatherEvent weatherEvent) {
Log.e("weather", weatherEvent.getTemperature()+"-**-"+weatherEvent.getCityName());
tvCity.setText(String.format("%s %s", weatherEvent.getCityName(),weatherEvent.getTemperature()));
}
@Override
public void onError(Throwable e) {
}
@Override
public void onComplete() {
}
});
}
private void setListener() {
tvSend.setOnClickListener(this);
}
private void setViewTreeObserver() {
final ViewTreeObserver swipeRefreshLayoutVTO = llScroll.getViewTreeObserver();
swipeRefreshLayoutVTO.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
Rect r = new Rect();
llScroll.getWindowVisibleDisplayFrame(r);
int statusBarH = Utils.getStatusBarHeight();//状态栏高度
int screenH = llScroll.getRootView().getHeight();
if (r.top != statusBarH) {
//在这个demo中r.top代表的是状态栏高度,在沉浸式状态栏时r.top=0,通过getStatusBarHeight获取状态栏高度
r.top = statusBarH;
}
int keyboardH = screenH - (r.bottom - r.top);
Log.d(TAG, "screenH= " + screenH + " &keyboardH = " + keyboardH + " &r.bottom=" + r.bottom + " &top=" + r.top + " &statusBarH=" + statusBarH);
if (keyboardH == currentKeyboardH) {//有变化时才处理,否则会陷入死循环
return;
}
currentKeyboardH = keyboardH;
screenHeight = screenH;//应用屏幕的高度
editTextBodyHeight = llComment.getHeight();
if (keyboardH < 150) {//说明是隐藏键盘的情况
MainActivity.this.updateEditTextBodyVisible(View.GONE);
return;
}
//偏移listview
}
});
}
/**
* 初始化控件
*/
private void initViews() {
recyclerView = findViewById(R.id.recyclerView);
llComment = findViewById(R.id.ll_comment);
etComment = findViewById(R.id.et_comment);
tvSend = findViewById(R.id.tv_send_comment);
llScroll = findViewById(R.id.ll_scroll);
tvCity = findViewById(R.id.tv_city);
}
/**
* 初始化数据
*
* @param
*/
private void initData() {
strings = new ArrayList<>();
for (int i = 0; i < 14; i++) {
strings.add(content);
}
}
/**
* 设置adapter
*/
private void initAdapter() {
circleAdapter = new CircleAdapter(this, strings, this);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
recyclerView.addItemDecoration(new SpaceDecoration(this));
recyclerView.setAdapter(circleAdapter);
recyclerView.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View view, MotionEvent motionEvent) {
if (llComment.getVisibility() == View.VISIBLE) {
updateEditTextBodyVisible(View.GONE);
return true;
}
return false;
}
});
}
@Override
public void onClick(int position, View v) {
if (likePopupWindow == null) {
likePopupWindow = new LikePopupWindow(this, 0);
}
likePopupWindow.setOnPraiseOrCommentClickListener(new OnPraiseOrCommentClickListener() {
@Override
public void onPraiseClick(int position) {
Toast.makeText(MainActivity.this, "点赞成功", Toast.LENGTH_SHORT).show();
likePopupWindow.dismiss();
Intent intent = new Intent(MainActivity.this, AddCityActivity.class);
startActivity(intent);
}
@Override
public void onCommentClick(int position) {
llComment.setVisibility(View.VISIBLE);
etComment.requestFocus();
CommonUtils.showSoftInput(MainActivity.this, llComment);
likePopupWindow.dismiss();
}
@Override
public void onClickFrendCircleTopBg() {
}
@Override
public void onDeleteItem(String id, int position) {
}
}).setTextView(0).setCurrentPosition(position);
if (likePopupWindow.isShowing()) {
likePopupWindow.dismiss();
} else {
likePopupWindow.showPopupWindow(v);
}
}
public void updateEditTextBodyVisible(int visibility) {
llComment.setVisibility(visibility);
if (View.VISIBLE == visibility) {
llComment.requestFocus();
//弹出键盘
CommonUtils.showSoftInput(etComment.getContext(), etComment);
} else if (View.GONE == visibility) {
//隐藏键盘
CommonUtils.hideSoftInput(etComment.getContext(), etComment);
}
}
@Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.tv_send_comment:
if (TextUtils.isEmpty(etComment.getText().toString())) {
Toast.makeText(MainActivity.this, "请输入评论内容", Toast.LENGTH_SHORT).show();
return;
}
setViewTreeObserver();
break;
default:
break;
}
}
@Override
protected void onDestroy() {
super.onDestroy();
//取消订阅
RxBus.rxBusUnbund(compositeDisposable);
}
}
3.刚开始使用的是dispatchTouchEvent拦截事件
打断点调试发现点击屏幕外部时确实键盘和输入框都消失了,但是发现一个问题,输入文本内容后,只要一点击发送按钮,输入框和键盘就消失了,发送评论的接口都没有调用,以下是日志和断点截图:
4.dispatchTouchEvent事件分发原理:
dispatchTouchEvent函数在Activity,View 和ViewGroup中都有定义,并且处理的逻辑也是不同的。
Activity:
当发生点击事件后,最先响应的是Activity的dispatchTouchEvent()函数,Activity会把TouchEvent传给自身绑定的rootView(一般就是DecorView),由rootView进行处理。
如果TouchEvent一直没有消费掉,最后会调用Activity的onTouchEvent()函数来处理事件。
ViewGroup:
ViewGroup运行dispatchTouchEvent()函数时,首先会调用onInterceptTouchEvent()函数,这是个拦截函数,如果需要拦截子View的点击事件,可以在这里添加一些逻辑;默认返回值是false,此时会按照添加子View时的顺序将事件分发给各个子View,由各个子View处理事件;如果添加了拦截的逻辑,返回值为true的话,会给各个子View发送Action_Cancel指令,并且所有事件都变为已处理。
子View处理事件时逻辑类似,如果是ViewGroup则继续分发,如果是View的话,则执行View的逻辑。
View:
View处理事件时先看是否有onTouchListener,如果有的话,优先执行onTouchListener的onTouch函数,如果没有处理,则执行onTouchEvent函数。
ViewGroup本质也是View,子view都没消费事件的话,也会走View的逻辑。
当子view不想被拦截时可以通过requestDisallowInterceptTouchEvent(true)函数来阻止拦截。
由于Activity没有写onTouchListener事件,所以按照上面的事件分发原理,Activity不会拦截,点击屏幕外部时被消费了,输入评论内容后,点击发送按钮时也被消费了,所以dispatchTouchEvent拦截点击事件,这样肯定不行,于是看了下微信的效果,回想起事件分发原理,尝试了一下解决方法:
5.在recyclerview的触摸事件中处理onTouch事件,代码如下:
这时调试运行发现点击外部输入框和软键盘都消失了,发现这种方法是可行的。还有一个问题recyclerview滑动时也要消失,这又是一个问题,和同事讨论监听输入框的高度,参考资料后给出以下解决方法:
private void setViewTreeObserver() {
final ViewTreeObserver swipeRefreshLayoutVTO = llScroll.getViewTreeObserver();
swipeRefreshLayoutVTO.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
Rect r = new Rect();
llScroll.getWindowVisibleDisplayFrame(r);
int statusBarH = Utils.getStatusBarHeight();//状态栏高度
int screenH = llScroll.getRootView().getHeight();
if (r.top != statusBarH) {
//在这个demo中r.top代表的是状态栏高度,在沉浸式状态栏时r.top=0,通过getStatusBarHeight获取状态栏高度
r.top = statusBarH;
}
int keyboardH = screenH - (r.bottom - r.top);
Log.d(TAG, "screenH= " + screenH + " &keyboardH = " + keyboardH + " &r.bottom=" + r.bottom + " &top=" + r.top + " &statusBarH=" + statusBarH);
if (keyboardH == currentKeyboardH) {//有变化时才处理,否则会陷入死循环
return;
}
currentKeyboardH = keyboardH;
screenHeight = screenH;//应用屏幕的高度
editTextBodyHeight = llComment.getHeight();
if (keyboardH < 150) {//说明是隐藏键盘的情况
MainActivity.this.updateEditTextBodyVisible(View.GONE);
return;
}
}
});
}
以上就是解决点击屏幕外部和列表滑动时隐藏输入框和键盘的解决方法,如果小伙伴们有更好的方法可以留言一起交流成长.
项目的完整地址如下:
https://gitee.com/jackning_admin/ExpandTextView