流水不争先,争的是滔滔不绝

Android手把手朋友圈实战教程(十五)控件篇(评论popup上)

即时通讯软件开发 云聊IM 974℃

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

嗯。。。今天要做的效果是这样滴(点击事件暂时未做)- –

preview

在本篇开始之前,我觉得您有必要看看basepopup那篇文章(点我查看),因为那篇文章就是为本篇所准备的,本篇所使用的popup就是基于那篇文章所说,为了篇幅,所以就不多解释了。

ps:羽翼君我其实并不太喜欢贴太多代码的,一般而言一篇文章我是不会贴过多的代码,因为如果满屏的代码带来的是阅读的疲劳,所以一般而言,如果实现流程比较长,代码量较多,我都会分上下篇来讲述。给您带来断断续续的阅读体验,这里在下说声抱歉了。

方案

按照国际惯例,咱们还是先聊聊如何实现。众所周知,popupWindow有个showAtLocation,很明显,我们朋友圈的评论弹出的popup也需要这个方法,而问题就是在于,我们该如何得到这三个小点点的具体位置呢?

这里就不得不说到这个方法:getLocationOnScreen,当然,还有一个相似的getLocationInWindow这两者的区别在于前者获取当前view相对于整个屏幕的xy pos,后者则是当前view相对于的父窗口的xy pos。不过,两者调用的,其实还是getLocationInWindow

我们可以在源码看到:

getLocationOnScreen

好的,扯得有点远,那这个方法用来干嘛呢?其实就是用来做一件事:让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看吧,为了篇幅,就不贴了,直接上图算了):

comment_popup_bg

嗯,如您所见,我这里的构造是多层嵌套,是的,这会导致过度绘制。但为何我还要选择这么做呢(ps:如果您有更好的方法,希望能在评论区贴一下或者在github提交pr)

  1. 其一,是因为我们的图标在文字的左边,虽然可以选择只用一个textview加drawableLeft。但是如您所见,我们的textview的宽度是平分整个父view的,所以如果选择单一的textview,就会导致我们的图标与文字的距离十分的远。
  2. 其二,考虑到我们的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 contenxt;

    public WeakHandler(Context contenxt) {
        this.contenxt = new WeakReference(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);

showAtLocation

再追溯下去,可以看到我们的Gravity的赋值

showAtLocation

也就是说,在show的时候会创建一个LayoutParams,并把gravity赋值(如果gravity有值的话),追溯到这里,有没有感觉像平时写viewgroup时,给的gravity。

我们继续追溯下去,在invokePopup方法里面,我们的layoutparams会传递下去

invokePopup

ok,看到我们的addView没有,我们的view将会被add到当前window,也就是说我们的Gravity的参考点,其实是整个window,而与我们的view无关。

那么我们要把popup显示到正确位置,就需要后面的两个参数了。

通过方法看出,showAtLocation后面两个参数正是x,y的偏移量。

所以我们的取值方案如下:

  1. 1.得到view的位置(getLocationOnScreen,得到view左上角原点坐标位置)
  2. 2.Gravity取window的右上角(right|top)
  3. 3.x方向偏移量取view的x同时乘以一定倍数(使popup的位置在view的左边)
  4. 4.y方向偏移量去view的y并减去自身的一半高度。
  5. 5.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)分离出来,防止耦合度过高,同时完成我们的点击事件和后台交互。

版权声明:部分文章、图片等内容为用户发布或互联网整理而来,仅供学习参考。如有侵犯您的版权,请联系我们,将立刻删除。
点击这里给我发消息