您的位置:首页 > 移动开发 > Android开发

简单定制Android控件(3) - 打造通用的PopupWindow(四)

2016-01-17 18:21 573 查看
上一篇文章(链接点我),我们实现了一个从底部滑上来的popup,这一次,我们将会实现一个更好玩的,而且也是更常见的——仿微信朋友圈点赞的popup

在文章之前,请让我说明一下这次github代码的更新。

这一次我们的BasePopup多了两个方法,分别是:

public Animation getExitAnimation(){
return null;
}
public Animator getExitAnimator(){
return null;
}


它们在BasePopupWindow的108行和111行,详情请看github
这两行代码很明显,就是用来dismiss时用的,至于为什么不把它弄成抽象,是因为popup默认退出无需动画,也就是这个并非必须实现的,所以我们添加这个方法但不要求子类必须实现。

这两行代码对应的地方就是我们BasePopupWindow的dismiss()方法,通过给animation设置一个animationListener,在onAnimationEnd里面执行popup的dismiss方法,就做到了在退出前播放动画,然后消掉popup。

OK,介绍结束,下面开始我们的朋友圈popup实现:

本次实现的效果如下(添加了退出动画)



首先是毫无疑问的xml文件:

popup_comment.xml:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:background="@android:color/holo_red_light">

<LinearLayout
android:id="@+id/comment_popup_contianer"
android:layout_width="180dp"
android:orientation="horizontal"
android:layout_height="40dp"
android:background="@drawable/bg_comment_popup"
android:weightSum="2"
android:clipChildren="false"
>
<RelativeLayout
android:id="@+id/item_like"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="match_parent"
android:clipChildren="false"
android:gravity="center"
>
<TextView
android:id="@+id/tv_like"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="赞  "
android:layout_centerInParent="true"
android:textColor="@color/comment_popup_tx_bg"
android:textSize="12sp"
android:drawablePadding="5dp"
/>
<ImageView
android:id="@+id/iv_like"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:layout_toLeftOf="@id/tv_like"
android:layout_marginRight="5dp"
android:src="@drawable/ic_like"
/>
</RelativeLayout>
<View
android:id="@+id/v_line"
android:layout_width="0.5dp"
android:layout_gravity="center_vertical"
android:layout_height="15dp"
android:background="#6c6c6c"
/>
<RelativeLayout
android:id="@+id/item_comment"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="match_parent"
>
<TextView
android:layout_centerVertical="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="评论"
android:drawableLeft="@drawable/ic_comment"
android:textColor="@color/comment_popup_tx_bg"
android:textSize="12sp"
android:drawablePadding="5dp"
android:layout_centerInParent="true"
/>
</RelativeLayout>
</LinearLayout>

</RelativeLayout>


其中背景是手动绘制的,xml配置如下:

bg_comment_popup.xml:

<?xml version="1.0" encoding="utf-8"?>
<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: #292929

comment_popup_tx_bg: #c7c7c7

xml写完后,我们就开始我们的java,这一次我们依然继承我们的BasePopupWindow,不过与之前不同,这次我们需要针对性的修改一下。

首先是我们的构造器,因为我们的popup与之前的差别在于,我们这次的popup并不需要全屏,而是包裹内容,所以我们的构造器就需要这么写

public CommentPopup(Activity context) {
this(context, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
}

public CommentPopup(Activity context, int w, int h) {
super(context, w, h);

viewLocation = new int[2];
mHandler=new Handler();

mLikeAnimaView = (ImageView) mPopupView.findViewById(R.id.iv_like);
mLikeText = (TextView) mPopupView.findViewById(R.id.tv_like);

mLikeClikcLayout = (RelativeLayout) mPopupView.findViewById(R.id.item_like);
mCommentClickLayout = (RelativeLayout) mPopupView.findViewById(R.id.item_comment);

mLikeClikcLayout.setOnClickListener(this);
mCommentClickLayout.setOnClickListener(this);

buildAnima();
}


对外而言,我们仍然只需要调用new CommentPopup((Activity)context);

但对内而言,我们就给定popup的大小为wrap_content,当然,我们的xml最外层父控件也需要wrap_content,然后主体我们限定一个宽高,这样这个popup就不是全屏的了。

接下来就是定义我们的动画,这个动画用于我们点赞后,那个心心放大,关于这个动画,我们需要注意到xml的心心所在的父view需要包含一个参数:clipChildren="false",有了这个参数,我们的动画才能顺利的突破父view的区域,否则就会被裁剪掉。

代码如下:

private void buildAnima() {
ScaleAnimation mScaleAnimation = new ScaleAnimation(1f, 2f, 1f, 2f, Animation.RELATIVE_TO_SELF, 0.5f,
Animation.RELATIVE_TO_SELF, 0.5f);
mScaleAnimation.setDuration(200);
mScaleAnimation.setInterpolator(new AccelerateDecelerateInterpolator());
mScaleAnimation.setFillAfter(false);

AlphaAnimation mAlphaAnimation = new AlphaAnimation(1, .2f);
mAlphaAnimation.setDuration(400);
mAlphaAnimation.setInterpolator(new AccelerateDecelerateInterpolator());
mAlphaAnimation.setFillAfter(false);

mAnimationSet=new AnimationSet(false);
mAnimationSet.setDuration(400);
mAnimationSet.addAnimation(mScaleAnimation);
mAnimationSet.addAnimation(mAlphaAnimation);
mAnimationSet.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {

}

@Override
public void onAnimationEnd(Animation animation) {
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
dismiss();
}
}, 150);

}

@Override
public void onAnimationRepeat(Animation animation) {

}
});
}


动画代码没啥好解释的。。。

接下来就是普通的实现我们父类的方法

@Override
public Animation getAnimation() {
return getScaleAnimation(0.0f, 1.0f, 1.0f, 1.0f, Animation.RELATIVE_TO_SELF, 1.0f,
Animation.RELATIVE_TO_SELF, 0.0f);
}

@Override
public Animator getAnimator() {
return null;
}

@Override
public Animation getExitAnimation() {
return getScaleAnimation(1.0f, 0.0f, 1.0f, 1.0f, Animation.RELATIVE_TO_SELF, 1.0f,
Animation.RELATIVE_TO_SELF, 0.0f);
}

@Override
public View getPopupView() {
return LayoutInflater.from(mContext).inflate(R.layout.popup_comment, null);
}

@Override
public View getAnimaView() {
return mPopupView.findViewById(R.id.comment_popup_contianer);
}


如果有看前面几篇文章的朋友,对这个应该理解不难,值得注意的是,scaleAnimation()我们的x方向参照点是popup的最右边,所以我们设置为RELATIVE_TO_SELF,1.0F,从0.0~1.0代表着x方向的宽度百分比。

上面这一些弄完后,就是我们的重头戏了。

大家注意到,朋友圈是一个listview,每个listview都有一个评论按钮,那么我们怎样才能正确的将popup展示到我们点击的评论按钮上呢。

于是今天我们不再用showPopup(),而是用showPopup(View v)

showPopup(View v)在我们的父类上已经写了,其根本依然是调用popupWindow.showAtLocation,但是除了第一个参数可以是view外,另外还有一些参数

简单的说,popupWindow.showAtLocation(View parent, int gravity, int x, int y)一共有四个参数

第一个不用说,就是参照的view,这个view可以是任何的view,比如说顶级的decorview,事实上我们的showPopup()这个无参方法用的view就是顶级decorview,其id是android.R.id.content。

第二个参数有一定的误导性,很容易理解为popup的内容的gravity,但事实上并非如此,第二个参数实质上是指参考屏幕的哪个位置。

这一个我们可以通过源码来看看:

showAtLocation的源码:

public void showAtLocation(View parent, int gravity, int x, int y) {
showAtLocation(parent.getWindowToken(), gravity, x, y);
}


我们继续点下去

public void showAtLocation(IBinder token, int gravity, int x, int y) {
if (isShowing() || mContentView == null) {
return;
}

TransitionManager.endTransitions(mDecorView);

unregisterForScrollChanged();

mIsShowing = true;
mIsDropdown = false;

final WindowManager.LayoutParams p = createPopupLayoutParams(token);
preparePopup(p);

// Only override the default if some gravity was specified.
if (gravity != Gravity.NO_GRAVITY) {
p.gravity = gravity;
}

p.x = x;
p.y = y;

invokePopup(p);
}


可以看到,我们的gravity最终是赋值给p,而p是属于windowManager,那么我们很有理由相信,p的作用是给windowManager来addView进而凭空生成一个view的,事实也是如此,我们点进invokePopup

private void invokePopup(WindowManager.LayoutParams p) {
if (mContext != null) {
p.packageName = mContext.getPackageName();
}

final PopupDecorView decorView = mDecorView;
decorView.setFitsSystemWindows(mLayoutInsetDecor);

setLayoutDirectionFromAnchor();

mWindowManager.addView(decorView, p);

if (mEnterTransition != null) {
decorView.requestEnterTransition(mEnterTransition);
}
}


很明显看到,我们的gravity最终是用到mWindowManager.addView里面,所以我们的第二个参数实质上是参照屏幕的位置

那么第三个和第四个参数就很明朗了,就是偏移值嘛。

OK,既然我们知道了位置,那么久接下来为了让我们的popup正确的显示在我们的评论按钮,我们就需要重写我们basepopupwindow的showPopup(View v),代码如下:

@Override
public void showPopupWindow(View v) {
try {
//得到v的位置
v.getLocationOnScreen(viewLocation);
//展示位置:
//参照点为屏幕的右上角,偏移值为:x方向距离参照view的一定倍数距离
//垂直方向自身减去popup自身高度的一半(确保在中间)
mPopupWindow.showAtLocation(v, Gravity.RIGHT | Gravity.TOP, (int) (v.getWidth() * 1.8),
viewLocation[1] - DimensUtils.dipToPx(mContext,15f));
if (getAnimation() != null && getAnimaView() != null) {
getAnimaView().startAnimation(getAnimation());
}
} catch (Exception e) {
Log.w("error","error");
}
}
在展示前,我们先取得view(也就是评论的那个imageView)在屏幕的位置,然后通过showAtLocation,偏移量在Y上面就是我们view所在的位置减去我们popup的高度的一半(或许会有5dp的出入,暂时不清楚原因)。

然后就是跟父类代码一样执行就可以了。

接下来就是为了解耦,我们的点赞/评论的点击事件通过接口暴露给外面进行回调操作,这样我们的朋友圈点赞就完成了。

全部代码如下:

/**
* Created by 大灯泡 on 2016/1/16.
* 微信朋友圈评论弹窗
*/
public class CommentPopup extends BasePopupWindow implements View.OnClickListener {

private ImageView mLikeAnimaView;
private TextView mLikeText;

private RelativeLayout mLikeClikcLayout;
private RelativeLayout mCommentClickLayout;

private int[] viewLocation;

private OnCommentPopupClickListener mOnCommentPopupClickListener;

private Handler mHandler;
public CommentPopup(Activity context) {
this(context, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
}

public CommentPopup(Activity context, int w, int h) {
super(context, w, h);

viewLocation = new int[2];
mHandler=new Handler();

mLikeAnimaView = (ImageView) mPopupView.findViewById(R.id.iv_like);
mLikeText = (TextView) mPopupView.findViewById(R.id.tv_like);

mLikeClikcLayout = (RelativeLayout) mPopupView.findViewById(R.id.item_like);
mCommentClickLayout = (RelativeLayout) mPopupView.findViewById(R.id.item_comment);

mLikeClikcLayout.setOnClickListener(this);
mCommentClickLayout.setOnClickListener(this);

buildAnima();
}

private AnimationSet mAnimationSet;

private void buildAnima() {
ScaleAnimation mScaleAnimation = new ScaleAnimation(1f, 2f, 1f, 2f, Animation.RELATIVE_TO_SELF, 0.5f,
Animation.RELATIVE_TO_SELF, 0.5f);
mScaleAnimation.setDuration(200);
mScaleAnimation.setInterpolator(new AccelerateDecelerateInterpolator());
mScaleAnimation.setFillAfter(false);

AlphaAnimation mAlphaAnimation = new AlphaAnimation(1, .2f);
mAlphaAnimation.setDuration(400);
mAlphaAnimation.setInterpolator(new AccelerateDecelerateInterpolator());
mAlphaAnimation.setFillAfter(false);

mAnimationSet=new AnimationSet(false);
mAnimationSet.setDuration(400);
mAnimationSet.addAnimation(mScaleAnimation);
mAnimationSet.addAnimation(mAlphaAnimation);
mAnimationSet.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {

}

@Override
public void onAnimationEnd(Animation animation) {
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
dismiss();
}
}, 150);

}

@Override
public void onAnimationRepeat(Animation animation) {

}
});
}

@Override
public void showPopupWindow(View v) {
try {
//得到v的位置
v.getLocationOnScreen(viewLocation);
//展示位置:
//参照点为view的右上角,偏移值为:x方向距离参照view的一定倍数距离
//垂直方向自身减去popup自身高度的一半(确保在中间)
mPopupWindow.showAtLocation(v, Gravity.RIGHT | Gravity.TOP, (int) (v.getWidth() * 1.8),
viewLocation[1] - DimensUtils.dipToPx(mContext,15f));

if (getAnimation() != null && getAnimaView() != null) {
getAnimaView().startAnimation(getAnimation());
}
} catch (Exception e) {
Log.w("error","error");
}
}

@Override
public Animation getAnimation() {
return getScaleAnimation(0.0f, 1.0f, 1.0f, 1.0f, Animation.RELATIVE_TO_SELF, 1.0f,
Animation.RELATIVE_TO_SELF, 0.0f);
}

@Override
public Animator getAnimator() {
return null;
}

@Override
public Animation getExitAnimation() {
return getScaleAnimation(1.0f, 0.0f, 1.0f, 1.0f, Animation.RELATIVE_TO_SELF, 1.0f,
Animation.RELATIVE_TO_SELF, 0.0f);
}

@Override
public View getPopupView() {
return LayoutInflater.from(mContext).inflate(R.layout.popup_comment, null);
}

@Override
public View getAnimaView() {
return mPopupView.findViewById(R.id.comment_popup_contianer);
}
//=============================================================Getter/Setter

public OnCommentPopupClickListener getOnCommentPopupClickListener() {
return mOnCommentPopupClickListener;
}

public void setOnCommentPopupClickListener(OnCommentPopupClickListener onCommentPopupClickListener) {
mOnCommentPopupClickListener = onCommentPopupClickListener;
}

//=============================================================clickEvent
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.item_like:
if (mOnCommentPopupClickListener != null) {
mOnCommentPopupClickListener.onLikeClick(v, mLikeText);
mLikeAnimaView.clearAnimation();
mLikeAnimaView.startAnimation(mAnimationSet);
}
break;
case R.id.item_comment:
if (mOnCommentPopupClickListener != null) {
mOnCommentPopupClickListener.onCommentClick(v);
dismiss();
}
break;
}
}

//=============================================================InterFace
public interface OnCommentPopupClickListener {
void onLikeClick(View v, TextView likeText);

void onCommentClick(View v);
}
//=============================================================abortMethods

@Override
public View getInputView() {
return null;
}

@Override
public View getDismissView() {
return null;
}
}


下一篇,我们将实现一个含有输入框的popup。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: