简单定制Android控件(3) - 打造通用的PopupWindow(四)
2016-01-17 18:21
573 查看
上一篇文章(链接点我),我们实现了一个从底部滑上来的popup,这一次,我们将会实现一个更好玩的,而且也是更常见的——仿微信朋友圈点赞的popup
在文章之前,请让我说明一下这次github代码的更新。
这一次我们的BasePopup多了两个方法,分别是:
它们在BasePopupWindow的108行和111行,详情请看github
这两行代码很明显,就是用来dismiss时用的,至于为什么不把它弄成抽象,是因为popup默认退出无需动画,也就是这个并非必须实现的,所以我们添加这个方法但不要求子类必须实现。
这两行代码对应的地方就是我们BasePopupWindow的dismiss()方法,通过给animation设置一个animationListener,在onAnimationEnd里面执行popup的dismiss方法,就做到了在退出前播放动画,然后消掉popup。
OK,介绍结束,下面开始我们的朋友圈popup实现:
本次实现的效果如下(添加了退出动画)
首先是毫无疑问的xml文件:
popup_comment.xml:
其中背景是手动绘制的,xml配置如下:
bg_comment_popup.xml:
色值如下:
comment_popup_bg: #292929
comment_popup_tx_bg: #c7c7c7
xml写完后,我们就开始我们的java,这一次我们依然继承我们的BasePopupWindow,不过与之前不同,这次我们需要针对性的修改一下。
首先是我们的构造器,因为我们的popup与之前的差别在于,我们这次的popup并不需要全屏,而是包裹内容,所以我们的构造器就需要这么写
对外而言,我们仍然只需要调用new CommentPopup((Activity)context);
但对内而言,我们就给定popup的大小为wrap_content,当然,我们的xml最外层父控件也需要wrap_content,然后主体我们限定一个宽高,这样这个popup就不是全屏的了。
接下来就是定义我们的动画,这个动画用于我们点赞后,那个心心放大,关于这个动画,我们需要注意到xml的心心所在的父view需要包含一个参数:clipChildren="false",有了这个参数,我们的动画才能顺利的突破父view的区域,否则就会被裁剪掉。
代码如下:
动画代码没啥好解释的。。。
接下来就是普通的实现我们父类的方法
如果有看前面几篇文章的朋友,对这个应该理解不难,值得注意的是,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的源码:
我们继续点下去
可以看到,我们的gravity最终是赋值给p,而p是属于windowManager,那么我们很有理由相信,p的作用是给windowManager来addView进而凭空生成一个view的,事实也是如此,我们点进invokePopup
很明显看到,我们的gravity最终是用到mWindowManager.addView里面,所以我们的第二个参数实质上是参照屏幕的位置
那么第三个和第四个参数就很明朗了,就是偏移值嘛。
OK,既然我们知道了位置,那么久接下来为了让我们的popup正确的显示在我们的评论按钮,我们就需要重写我们basepopupwindow的showPopup(View v),代码如下:
然后就是跟父类代码一样执行就可以了。
接下来就是为了解耦,我们的点赞/评论的点击事件通过接口暴露给外面进行回调操作,这样我们的朋友圈点赞就完成了。
全部代码如下:
下一篇,我们将实现一个含有输入框的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。
相关文章推荐
- Android视频应用去广告学习实践
- Android Studio 将Library上传到JCenter
- android stdio 的github
- Android调用系统相机、自己定义相机、处理大图片
- Android学习笔记-使用Scroller来滚动视图
- 开源框架KImageLoader开发及原理剖析(一)
- Android之CursorAdapter用法
- android&nbsp;Criteria的使用
- Android应用开发笔记(10):制作自…
- Android&nbsp;文件打开方式
- Android中的Environment.getExtern…
- Android中visibility属性VISIBLE、…
- 无需root,清除清理安卓,android的c…
- Android&nbsp;Afinal使用与总结
- Android调用系统相机和自定义相机…
- android animation解析
- Android Studio 快捷键
- android studio没有浮现函数用法和属性说明?
- Android RecyclerView 的基本使用之GridView
- Android listview优化