当前位置: 首页 > article >正文

一起撸个朋友圈吧 (Step6) 评论对齐(未完全版本)【上】

项目地址:https://github.com/razerdp/FriendCircle

上篇链接:http://www.jianshu.com/p/58894dfb3f09

下篇链接:http://www.jianshu.com/p/513e2eccd7a8

食用注意:

  • 本餐为非完全体,仅仅实现针对动态评论的输入框对齐功能,剩余菜式(后台交互等)敬请期待
  • 本餐存在一定的bug(超多评论时有一定的bug),待补
  • 本餐餐牌稍难理解,我尽量写的易懂一点。
  • 图片较多,文字很多,流量党请注意

预览

开始之前,按照惯例,先弄上preview吧:

什么?你说你看不出什么特别的? 那好,咱么再上一张图:

这回录制短了一点,比较两张gif,不难看出两者的区别:

  • 第一张图在点击评论的时候,会自动将动态的底部对齐评论框顶部
  • 第二张图仅仅是单纯的弹出输入框,没有任何其他操作(所以咱们录制的时间就短了←_←)。

就用户体验来说,肯定是第一张图的比较好,同时,这也是微信的做法,所以很多地方微信的细节真的抓的很好啊。


思路

OK,既然比较结果出来了,接下来就得思考一下做法了。 因为咱们不是微信的开发员,所以只能按照我的想法去做了。

首先想想listview针对item的位移操作有哪些:

  • setselection:不推荐,因为是即刻就到,没有过渡
  • smoothscrolltoposition:可以用,但不能完全满足我们的需求。
  • setselectionfromtop:不推荐,理由同一
  • smoothscrolltopositionfromtop:骚年,别想了,就是它了。

常用的方法和理由都写在上面了,这里我们打算采用smoothscrolltopositionfromtop,理由很简单:

  • 其一它有过渡的scroll效果
  • 其二,它能移到指定位置
  • 其三 ,它还有一个位移,在到达位置后进行一段位移。

OK,采用的方法也有了,接下来就是要想想怎么利用这个方法了。

smoothscrolltopositionfromtop常用的方法有两个参数,第一个是item的位置,第二个是位移。第一个很好办,我们可以在点击的时候将位置抛出来,但第二个就有点难办了,因为这个位移量并非那么好计算的。

这时候也许就会有一种难以入手的感觉了。

既然不知道从哪方面入手,咱们不妨先看看最终效果:

如图,我们点了上面那个item,此时输入框弹了上来,但是我们的预期是希望item的底部能够对齐到输入框的顶部,很明显,现在没有达到我们的预期。

那么如果按照图中的效果,我们需要listview自动滑动一段距离,在现在这张图,我们的偏移量很好看,不就是图中箭头的那段距离么。

理论上的确如此,我们可以得到item的bottom,减去输入框的top得到偏移量,然而在实际测试过程中,我们得到的位移量并不准确,当然,也有可能是我的计算有问题,这也许是一个很好的思路,但暂时来说我们先放到一边。

回到本篇,我们不妨看一下,在输入框弹上来之后,我们的可以见到的view的范围,为了更加直观,我们直接上图:

如图,在键盘弹上来之后,整个黄色的蒙层区域就是我们当前可见的视图层。在图上我们也标注了一些必要参数,因此很明显,我们的可见区域范围计算如下:

contentHeight = ScreenHeight - StatusBarHeight - KeyBoardHeight - InputLayoutHeight

那么得到这个有什么用呢?别急,还记得我们上面说过的方法吗?

smoothscrolltopositionfromtop,第一个参数跟setselection差不多,移动到指定的item。

我们试试调用smoothscrolltopositionfromtop(当前item的position,0),得到下图的结果:

为什么与我们想象的不一样?Item的top不应该在titlebar的下方么?

别急。。。。

还记得我们第一篇的布局吗,titlebar的层是在listview的上方,所以item的顶部被遮挡了。

如果我们调用smoothscrolltopositionfromtop(当前item的position,titlebar.getHeight)就会得到我们想要的结果了,为了篇幅,咱们就不上图了。

在这两次小小的测试调用中,我们得到了两个信息:

  • smoothscrolltopositionfromtop可以让listview顺利的滑倒指定item
  • offset方向,offset>0时,listview等同于我们手指向下拉,否则反之

OK,我们现在可以让item在可是区域的顶部了,但是底部还没有对齐,如上图,我在图中用红色虚线标明了该item的底部。

所以这时候我们的offset其实很容易计算: offset = -1 * ( ItemHeight - contentHeight );

这样,当item底部大于contentHeight时,listview会朝y轴负方向移动,使item底部对齐contentHeight,即inputlayout的top,否则反之。


#代码 呼呼,思路终于确定。接下来就是代码方面了。

在上一篇的重构中,我们的评论框调用方法是这样的:

@Override 
public void showInputBox(int currentDynamicPos, @CommonValue.CommentType int commentType, CommentInfo commentInfo) { }
复制代码

根据type来判断当前评论是评论动态还是回复评论,但是这样太冗余了,所以这次又将它改了一下:

@Override
public void showInputBox(int currentDynamicPos, CommentWidget commentWidget, DynamicInfo dynamicInfo){ }
复制代码

我们直接把commentWidget抛出来,这样对这个控件空引用判断就能知道是评论动态还是回复评论了。

首先我们补全showInputBox代码,为了节省篇幅,输入框的xml布局就不展示了,可以到github看完整代码:

  @Overridepublic void showInputBox(int currentDynamicPos, CommentWidget commentWidget, DynamicInfo dynamicInfo) {this.currentDynamicPos = currentDynamicPos;this.mCommentWidget = commentWidget;if (!TextUtils.isEmpty(draftStr)) {mInputBox.setText(draftStr);mInputBox.setSelection(draftStr.length());}if (commentWidget == null) {// 评论动态mInputLayout.setVisibility(View.VISIBLE);InputMethodUtils.showInputMethod(mInputBox);}else {// 回复评论}}
复制代码

在输入框弹出来时,如果草稿不空,则将草稿设置到edittext中,否则就不设置。(其中草稿在点击发送的时候清空,在输入法隐藏的时候保存)

在思考那部分,我们知道contentHeight的计算方法,但问题就在于输入法的高度获取问题,幸好,网上的大神们已经提供了方法,在谷歌一番后,我们得到了以下这个方法(方法来源:http://blog.csdn.net/daguaio_o/article/details/47127993 ):

不过这个方法有一点点小问题,因为OnGlobalLayoutListener在view改变时会被调用,所以即使输入法隐藏了,接口依然被调用,所以我稍微改变了一下(写到UIHelper.java里面):

 /*** 监听软键盘高度和状态** source web link:* http://blog.csdn.net/daguaio_o/article/details/47127993*/public static void observeSoftKeyboard(Activity activity, final OnSoftKeyboardChangeListener listener) {final View decorView = activity.getWindow().getDecorView();decorView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {int previousKeyboardHeight = -1;Rect rect = new Rect();boolean lastVisibleState = false;@Overridepublic void onGlobalLayout() {rect.setEmpty();decorView.getWindowVisibleDisplayFrame(rect);int displayHeight = rect.bottom - rect.top;int height = decorView.getHeight();int keyboardHeight = height - displayHeight;if (previousKeyboardHeight != keyboardHeight) {boolean hide = (double) displayHeight / height > 0.8;if (hide!=lastVisibleState) {listener.onSoftKeyBoardChange(keyboardHeight, !hide);lastVisibleState=hide;}}previousKeyboardHeight = height;}});}
复制代码

首先将Rect矩形的创建移到回调外,防止多次创建,然后记录软键盘的状态,当且仅当软键盘的可视性与上一次不同的时候,才会回调OnSoftKeyboardChangeListener 。

OnSoftKeyboardChangeListener 在那个博客文章上有,这里就不阐述了,接下来到我们的Activity层使用:

...import/*** Created by 大灯泡 on 2016/2/25.* 朋友圈demo窗口*/
public class FriendCircleDemoActivity extends FriendCircleBaseActivityimplements DynamicView, View.OnClickListener, OnSoftKeyboardChangeListener {
...变量定义@Overrideprotected void onCreate(Bundle savedInstanceState) {...与之前一样UIHelper.observeSoftKeyboard(this, this);}...之前的方法不变//============================================================= tools method@Overridepublic void onSoftKeyBoardChange(int softKeybardHeight, boolean visible) {Log.d("keyboardheight", "" + softKeybardHeight + "         visible=     " + visible);// 保存软键盘高度if ((int) PreferenceUtils.INSTANCE.getSharedPreferenceData("KeyBoardHeight", 0) < softKeybardHeight) {PreferenceUtils.INSTANCE.setSharedPreferenceData("KeyBoardHeight", softKeybardHeight);}}
}复制代码

在onSoftKeyBoardChange我们实现listview的偏移。因为我们对代码实现过一些改变,所以我们可以确保这个回调只会在软键盘可视性改变时才会调用,所以不担心死循环问题。

接下来写出我们计算偏移量的方法:

    private int screenHeight = 0;private int statusBarHeight = 0;private int calculateListViewOffset(int currentDynamicPos, CommentWidget commentWidget, int keyBoardHeight) {int result = 0;if (screenHeight == 0) screenHeight = UIHelper.getScreenPixHeight(this);if (statusBarHeight == 0) statusBarHeight = UIHelper.getStatusHeight(this);if (commentWidget == null) {// 评论控件为空,证明回复的是整个动态result = getOffsetOfDynamic(currentDynamicPos, keyBoardHeight);}else {// 评论控件不空,证明回复的是评论}return result;}
复制代码

screenHeight 和statusBarHeight我们设置为本类全局变量,这样就不用每次都消耗系统资源。然后针对commentWidget 是否为空再分别计算。

接下来是最重要的部分getOffsetOfDynamic:

// 得到动态高度private int getOffsetOfDynamic(int currentDynamicPos, int keyBoardHeight) {int result = 0;ListView contentListView = null;if (mListView.getContentView() instanceof ListView) {contentListView = (ListView) mListView.getContentView();}if (contentListView == null) return 0;int firstItemPos = contentListView.getFirstVisiblePosition();int dynamicItemHeight = 0;View currentDynamicItem = contentListView.getChildAt(currentDynamicPos - firstItemPos + contentListView.getHeaderViewsCount());if (currentDynamicItem != null) {dynamicItemHeight = currentDynamicItem.getHeight();Log.d("dynamicItemHeight", "dynamicItemHeight=========    " + dynamicItemHeight);}int contentHeight = 0;contentHeight = screenHeight - keyBoardHeight - mInputLayout.getHeight();result = dynamicItemHeight - contentHeight;return -result;}
复制代码

这部分代码我基本没怎么写注释,因为我打算在文章里面记录,所以就没怎么写注释了。

不过应该不难理解。

首先,因为我们使用百万哥的ultr下拉刷新控件,并且再度封装,所以我们的真正的listview其实是ptrFrameLayout的contentView,因此我们需要得到listview。

接下来需要得到当前位置的item,得到item的view有两个方法:

  • adapter.getView:
    • 没错,这个就是我们写adapter时重载的getView方法,经常写adapter的我们都知道,三个参数里面我们知道的有position和parent(即listview),但convertView不知道,所以传入null,此时adapter会因为我们的重载会重新inflate出来,所以我们通过这个方法得到的convertView需要手动调用measure进行测量,否则是不会有属性信息的。
  • listview.getChildAt:
    • 因为listview可以算是一个viewgroup,所以可以直接得到对应的子view,不过需要留意的是,因为listview的复用机制,我们不可以直接传入position,而是需要得到listview顶部展示的view的position,然后用真正的itemPosition减去第一个可见的,如果有headerView则加上headerView的数量,这样才能正确得到指定item,并且不需要重新测量。

得到了item后,我们就可以得到其高度。

最后只是套用上面我们思路的两条公式(ps:本例并没有减去statusBarHeight,因为我发现查到的博客地址里面包含有,当输入法不可见时,就会有50这个高度,这个高度就是statusBarHeight高度,这也是为什么在写入sharePreference时会判断键盘高度的原因)

得到偏移量,我们就可以在keyboard变化的回调中操作了

 @Overridepublic void onSoftKeyBoardChange(int softKeybardHeight, boolean visible) {Log.d("keyboardheight", "" + softKeybardHeight + "         visible=     " + visible);// 保存软键盘高度if ((int) PreferenceUtils.INSTANCE.getSharedPreferenceData("KeyBoardHeight", 0) < softKeybardHeight) {PreferenceUtils.INSTANCE.setSharedPreferenceData("KeyBoardHeight", softKeybardHeight);}// listview偏移final int offset = calculateListViewOffset(currentDynamicPos, mCommentWidget, softKeybardHeight);Log.d("offset", "offset===========    " + offset);// http://stackoverflow.com/questions/11431832/android-smoothscrolltoposition-not-working-correctlyfinal int pos = currentDynamicPos + 1;mListView.smoothScrollToPositionFromTop(pos, offset);}
复制代码

因为我们的公式是针对可视范围,所以当keyboard隐藏的时候依然会触发这个回调,因此会重新计算一次,所以我们在隐藏的时候,item依然会自动对齐到输入框的顶部。 (值得留意的是,我们的朋友圈headerview只有一个,所以我们的position要+1哦,这里可以改成加上listview.getHeaderViewCount())


Finally

最后,我们需要补充一下在软键盘可见时,如果点击了listview,则需要消掉键盘并保存草稿。

做法很简单,我们只需要在listview的onTouch回调做手脚,但问题在于,百万哥的ptrFrameLayout的事件分发是在dispatchTouchEvent里面实现的,这就导致了我们即使setOnTouchListener也会被截断。

所以我们需要重写一下,在调用框架的dispatchTouchEvent前实现:

来到FriendCirclePtrListView,重载dispatchTouchEvent:

 @Overridepublic boolean dispatchTouchEvent(MotionEvent e) {if (mDispatchTouchEventListener!=null)mDispatchTouchEventListener.OnDispatchTouchEvent(e);return super.dispatchTouchEvent(e);}
复制代码

其中OnDispatchTouchEventListener:

 public interface OnDispatchTouchEventListener{boolean OnDispatchTouchEvent(MotionEvent ev);}
复制代码

最后在activity调用:

 mListView.setOnDispatchTouchEventListener(new FriendCirclePtrListView.OnDispatchTouchEventListener() {@Overridepublic boolean OnDispatchTouchEvent(MotionEvent ev) {if (mInputLayout.getVisibility() == View.VISIBLE) {draftStr = mInputBox.getText().toString().trim();mInputLayout.setVisibility(View.GONE);InputMethodUtils.hideInputMethod(mInputBox);return true;}return false;}});
复制代码

目前已知的bug:

  • 评论数过多时,无法每次都正确对齐(如例子中的第二条朋友圈,50条评论)
  • 有时候如果上一个item并没有完全滑出屏幕外,点下一个item时会导致跳到上一个item的底部(原因在于position是在getView中传出去的,这部分下一篇进行下修改)

【END】

下一篇将会完成剩余的评论功能

ps:文字很多,写的或许还不是很清晰,估计看完的人不多(话说,会有人看么。。。),看完了懂的人更不多。。。。如果有不明白的,可以评论区留下您的脚印或者简信在下。

http://www.lryc.cn/news/2420603.html

相关文章:

  • ROS的STM32电机驱动
  • 背包问题之回溯法
  • dell灵越笔记本后盖怎么拆_dell笔记本拆机详解【图文教程】
  • jQuery hover事件鼠标滑过图片半透明标题文字滑动显示隐藏
  • [ArcPy] 遥感影像去黑边-第六届全国大学生GIS技能大赛试题
  • 分享17个微信创新应用案例 应用场景的不同应用
  • DAVINCI DM3730开发攻略——xload-1.51移植
  • _desktop.ini病毒的清除
  • 开机启动项怎么设置
  • 「网络设备模拟器」EVE-NG安装操作指导
  • CRT常见故障问答
  • 实验二 基于MATLAB的离散时间系统的响应
  • 中国微电机行业需求规模与竞争格局研究报告2022版
  • google地图网页版_网站地图(Sitemap)的制作方法
  • Flex的itemRenderer属性使用例子
  • Internet Explorer 无法打开该 Internet 站点。请求的站点不可用
  • 在Python中如何使用模块进行代码组织
  • rootkit原理与编写教程
  • 中兴手机android版本升级包下载,刷机
  • 打造全新的Windows Live™ Spaces
  • AutoJs学习-天天爱消除脚本
  • 属兔的人今日运势-360星座网_【12月10日】 十二生肖明日运势
  • 属兔的人今日运势-360星座网_第一运程 2021年1月4日十二生肖运势解析
  • 如何制作优质的YouTube视频
  • Stream常用方法
  • 英语论文关于计算机网络,COMPUTER NETWORK 计算机网络(英语论文).doc
  • 联邦学习介绍
  • ospf动态路由协议——(最详解)
  • jQuery绑定事件的四种方式:bind、live、delegate、on
  • 三款企业必备企业上网监控软件盘点|上网行为监控软件有哪些