上篇链接:http://www.jianshu.com/p/280058de01bd
下篇链接:http://www.jianshu.com/p/deda1849a084
嗯。。。今天要做的效果是这样滴(点击事件暂时未做)- -
在本篇开始之前,我觉得您有必要看看basepopup那篇文章 (点我查看),因为那篇文章就是为本篇所准备的,本篇所使用的popup就是基于那篇文章所说,为了篇幅,所以就不多解释了。ps:羽翼君我其实并不太喜欢贴太多代码的,一般而言一篇文章我是不会贴过多的代码,因为如果满屏的代码带来的是阅读的疲劳,所以一般而言,如果实现流程比较长,代码量较多,我都会分上下篇来讲述。给您带来断断续续的阅读体验,这里在下说声抱歉了。
方案
按照国际惯例,咱们还是先聊聊如何实现。众所周知,popupWindow有个showAtLocation,很明显,我们朋友圈的评论弹出的popup也需要这个方法,而问题就是在于,我们该如何得到这三个小点点的具体位置呢?
这里就不得不说到这个方法:getLocationOnScreen
,当然,还有一个相似的getLocationInWindow
这两者的区别在于前者获取当前view相对于整个屏幕的xy pos,后者则是当前view相对于的父窗口的xy pos。不过,两者调用的,其实还是getLocationInWindow
我们可以在源码看到:
好的,扯得有点远,那这个方法用来干嘛呢?其实就是用来做一件事:让popup正确的显示在某个小点点的左边。也就是效果图的那个效果。
开工(上篇仅完成重要部分代码的实现,下篇完成点击事件,后台处理等)
OK,前戏搞完,开始进入XXOO的阶段。
step 1
首先,我们继承BasePoup,是的,如果您看过basepopup那篇文章,就知道我们需要实现4个方法:
public class CommentPopup extends BasePopupWindow {
public CommentPopup(Activity context) {
super(context, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
}
@Override
protected Animation getShowAnimation() {
return null;
}
@Override
protected View getClickToDismissView() {
return null;
}
@Override
public View getPopupView() {
return null;
}
@Override
public View getAnimaView() {
return null;
}
}
复制代码
为了更方便使用,我们直接调用父类的另一个构造方法,传入的是wrap_contnet
(*注:因为默认是match_parent,但我们并不需要我们的评论popup占满整个屏幕,所以构造器就传入wrap_content,当然,对于外部调用者来说,他仅仅需要传入context)。
step 2
接下来我们开始进行布局。首先去drawable画出我们的背景:
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@color/comment_popup_bg"/>
<corners android:radius="4dp"/>
</shape>
复制代码
(其中comment_popup_bg色值为:#4c5154) 然后新建布局popup_comment(详情去github看吧,为了篇幅,就不贴了,直接上图算了):
嗯,如您所见,我这里的构造是多层嵌套,是的,这会导致过度绘制。但为何我还要选择这么做呢(ps:如果您有更好的方法,希望能在评论区贴一下或者在github提交pr)
-
其一是因为我们的图标在文字的左边,虽然可以选择只用一个textview加drawableLeft 但是如您所见,我们的textview的宽度是平分整个父view的,所以如果选择单一的textview,就会导致我们的图标与文字的距离十分的远。
-
其二,考虑到我们的popup并非常驻于界面,所以此处多层嵌套影响并不大。
另外,需要注意的是我们需要给viewgroup给定一个android:clipChildren="false"
,这么做是为了不让viewgroup阻断我们的动画展示。
ok,布局完成。
step 3
布局完成后我们开始实现java代码:
首先为了方便管理参数,同时也为了防止服务器经常性修改JSON下发参数,我们新建一个工具类来管理:
/**
* Created by 大灯泡 on 2016/3/6.
* 各种设定值
*/
public class CommonValue {
// 点赞状态
@Retention(RetentionPolicy.SOURCE)
@IntDef({NOT_PRAISE,HAS_PRAISE})
public @interface PraiseState{}
public static final int NOT_PRAISE = 0;
public static final int HAS_PRAISE = 1;
}
复制代码
在我们这个系列里,点过赞的状态下发的是1,没有则是下发的0。同时我们定义一个注解,用于限定传参。
接下来到我们的CommentPopup,定义各种变量:
// 点赞的图标
private ImageView mLikeView;
private TextView mLikeText;
// 点赞/评论的layout
private RelativeLayout mLikeClikcLayout;
private RelativeLayout mCommentClickLayout;
// 动态信息
private DynamicInfo mDynamicInfo;
// 位置数组
private int[] viewLocation;
// handler
private WeakHandler handler;
复制代码
其中WeakHandler是一个弱引用的静态内部类:
static class WeakHandler extends Handler{
private final WeakReference<Context> contenxt;
public WeakHandler(Context contenxt) {
this.contenxt = new WeakReference<Context>(contenxt);
}
}
复制代码
至于这个handler用来干嘛。。。。嗯,备用 - -
接下来需要定义一下我们的show方法:
@Override
public void showPopupWindow(View v) {
try {
//得到v的位置
v.getLocationOnScreen(viewLocation);
//展示位置:
//参照点为window的右上角,偏移值为:x方向距离参照view的一定倍数距离
//垂直方向自身减去popup自身高度的一半(确保在中间)
mPopupWindow.showAtLocation(v, Gravity.RIGHT | Gravity.TOP, (int) (v.getWidth() * 1.8),
viewLocation[1] - UIHelper.dipToPx(mContext, 10f));
if (getShowAnimation() != null && getAnimaView() != null) {
getAnimaView().startAnimation(getShowAnimation());
}
} catch (Exception e) {
Log.w("error", "error");
}
}
复制代码
这里需要说说showAtLocation这个方法。
public void showAtLocation(View parent, int gravity, int x, int y)
复制代码
这个方法有一定的误导性,一开始我还以为gravity是相对于显示的view来说的,打个比方,假如我showAtLocation(view,Gravity.RIGHT,0,0);按照我一开始的说法,我会以为这个popup在view的右边显示。
结果并不是。你会发现,popup在屏幕右边正中间显示。
百思不得其解,百度过后得到的答案不太清晰,只好去看源码了。
点进这个方法的源码,我们看到调用了showAtLocation(parent.getWindowToken(), gravity, x, y);
再追溯下去,可以看到我们的Gravity的赋值
也就是说,在show的时候会创建一个LayoutParams,并把gravity赋值(如果gravity有值的话),追溯到这里,有没有感觉像平时写viewgroup时,给的gravity。
我们继续追溯下去,在invokePopup方法里面,我们的layoutparams会传递下去
ok,看到我们的addView没有,我们的view将会被add到当前window,也就是说我们的Gravity的参考点,其实是整个window,而与我们的view无关。
那么我们要把popup显示到正确位置,就需要后面的两个参数了。
通过方法看出,showAtLocation后面两个参数正是x,y的偏移量。
所以我们的取值方案如下:
- 得到view的位置(getLocationOnScreen,得到view左上角原点坐标位置)
- Gravity取window的右上角(right|top)
- x方向偏移量取view的x同时乘以一定倍数(使popup的位置在view的左边)
- y方向偏移量去view的y并减去自身的一半高度。
- showPopup
代码在上面。
在展示完成后,最后需要根据我们的点赞状态(是否点赞)来进行对应的设置:
public void setDynamicInfo(DynamicInfo info) {
if (info == null) return;
if (info.praiseState == CommonValue.HAS_PRAISE) {
mLikeText.setText("取消");
}
else {
mLikeText.setText("赞 ");
}
}
复制代码
然后回到我们的BaseItemDelegate执行如下操作(初始化popup略过):
@Override
public void onClick(View v) {
switch (v.getId()){
// 评论按钮
case R.id.comment_button:
if (mInfo==null)return;
mCommentPopup.setDynamicInfo(mInfo.dynamicInfo);
mCommentPopup.showPopupWindow(commentImage);
break;
default:
break;
}
}
复制代码
本篇完。
下一篇将会做一个比较大的改动,我们需要将事件处理从viewholder(即BaseItemDelegate)分离出来,防止耦合度过高,同时完成我们的点击事件和后台交互。