仿微信图库文件夹选择的交互
来源:互联网 发布:乳胶漆调色软件 编辑:程序博客网 时间:2024/06/06 04:06
前言
这是按照微信图库中文件夹选择的展示动画做出来的PopupWindow。之前自己做过图库部分,发现微信图库中这个文件夹显示和消失的动画很自然,很舒服,而之前做的不是那么自然,于是就做了这么个东西。
效果
PopupWindow
做这个交互用到的主要是popupwindow和动画相关的东西。这里先说PopupWindow的部分。
上代码
自定义的PopupWindow,重写了显示和消失的方法,加入了自定义的动画。
public class GalleryDirPopupWindow extends PopupWindow{ private View mConvertView; private ListView lv_dirs; private View view; private BaseAdapter mAdapter; private List<PhotoDir> mPhotoDirs; //动画相关 AnimatorSet animatorSet ; ObjectAnimator mPlaceViewShowAnimation; ObjectAnimator mPlaceViewDismissAnimation; ObjectAnimator mListViewShowAnimation; ObjectAnimator mListViewDismissAnimation; //是否正在消失,为了解决快速的两次点击出现的问题 boolean isDismissing = false; private Context mContext; /** * * @param context * @param photoDirs * @param height 之所以要设置高度而不用match_parent,是因为安卓7.0的window的属性有变化, * 设置为match_parent之后直接全屏,showasdrapdown的位移失效,所以必须设置精确的高度 */ public GalleryDirPopupWindow(Context context,List<PhotoDir> photoDirs,int height) { this.mPhotoDirs = photoDirs; this.mConvertView = LayoutInflater.from(context).inflate(R.layout.gallery_list_dir, null); this.mContext = context; // 设置SelectPicPopupWindow的View this.setContentView(mConvertView); // 设置SelectPicPopupWindow弹出窗体的宽 this.setWidth(ViewGroup.LayoutParams.MATCH_PARENT); // 设置SelectPicPopupWindow弹出窗体的高 this.setHeight(height); // 设置SelectPicPopupWindow弹出窗体可点击 this.setFocusable(true); // 加上它之后,setOutsideTouchable()才会生效;并且PopupWindow才会对手机的返回按钮有响应 this.setBackgroundDrawable(new BitmapDrawable()); // 设置popWindow的显示和消失动画 //this.setAnimationStyle(R.style.pop_anim_style); this.setOutsideTouchable(true); initViews(); initShowAnimation(); } public void initViews() { lv_dirs = (ListView) mConvertView.findViewById(R.id.lv_dirs); view = (View)mConvertView.findViewById(R.id.view); // mMenuView添加OnTouchListener监听判断获取触屏位置如果在选择框外面则销毁弹出框 mConvertView.setOnTouchListener(new View.OnTouchListener() { public boolean onTouch(View v, MotionEvent event) { int height = lv_dirs.getTop(); int y = (int) event.getY(); if (event.getAction() == MotionEvent.ACTION_UP) { if (y < height) { dismiss(); } } return true; } }); mAdapter =new GalleryDirsAdapter(mContext,mPhotoDirs); lv_dirs.setAdapter(mAdapter); } public void updatePopWindow() { mAdapter.notifyDataSetChanged(); } @Override public void dismiss() { if(isDismissing) return ; isDismissing = true; startDismissAnimator(); } //这种带Gravity参数的方法是API 19新引入的,所以为了适配应该去掉这个gravity,因为这里用不到 @Override public void showAsDropDown(View anchor,int x,int y,int gravity) { super.showAsDropDown(anchor,x,y,gravity); startShowAnimator(); } @Override public void showAtLocation(View parent, int gravity, int x, int y) { super.showAtLocation(parent, gravity, x, y); startShowAnimator(); } /** * 初始化动画 */ private void initShowAnimation(){ if(mPlaceViewShowAnimation==null){ //占位控件的出场动画和退场动画 mPlaceViewShowAnimation = ObjectAnimator.ofFloat(view, "alpha", 0f, 1f); mPlaceViewShowAnimation.setInterpolator(new AccelerateInterpolator ()); mPlaceViewShowAnimation.setDuration(300); mPlaceViewDismissAnimation = ObjectAnimator.ofFloat(view, "alpha", 1f, 0f); mPlaceViewDismissAnimation.setInterpolator(new AccelerateInterpolator ()); mPlaceViewDismissAnimation.setDuration(300); mPlaceViewDismissAnimation.addListener(new Animator.AnimatorListener() { @Override public void onAnimationStart(Animator animation) { } @Override public void onAnimationEnd(Animator animation) { mConvertView.setVisibility(View.GONE); GalleryDirPopupWindow.super.dismiss(); isDismissing = false; } @Override public void onAnimationCancel(Animator animation) { mConvertView.setVisibility(View.GONE); GalleryDirPopupWindow.super.dismiss(); isDismissing = false; } @Override public void onAnimationRepeat(Animator animation) { } }); measureView(lv_dirs); //ListView的出场动画和退场动画 mListViewShowAnimation = ObjectAnimator.ofFloat(lv_dirs, "translationY", lv_dirs.getMeasuredHeight(), 0f); mListViewShowAnimation.setDuration(300); mListViewShowAnimation.setInterpolator(new AccelerateDecelerateInterpolator()); mListViewDismissAnimation =ObjectAnimator.ofFloat(lv_dirs, "translationY", 0f, lv_dirs.getMeasuredHeight()); mListViewDismissAnimation.setInterpolator(new AccelerateDecelerateInterpolator()); mListViewDismissAnimation.setDuration(200); } } /** * 目前该方法只支持预计算宽高设置为准确值或wrap_content的情况, * 不支持match_parent的情况,因为view的父view还未预计算出宽高 * @param v 要预计算的view */ private void measureView(View v) { ViewGroup.LayoutParams lp = v.getLayoutParams(); if (lp == null) { return; } int width; int height; if (lp.width > 0) { // xml文件中设置了该view的准确宽度值,例如android:layout_width="150dp" width = View.MeasureSpec.makeMeasureSpec(lp.width, View.MeasureSpec.EXACTLY); } else { // xml文件中使用wrap_content设定该view宽度,例如android:layout_width="wrap_content" width = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED); } if (lp.height > 0) { // xml文件中设置了该view的准确高度值,例如android:layout_height="50dp" height = View.MeasureSpec.makeMeasureSpec(lp.height, View.MeasureSpec.EXACTLY); } else { // xml文件中使用wrap_content设定该view高度,例如android:layout_height="wrap_content" height = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED); } v.measure(width, height); } /** * 启动动画,设置Visibility为VISIABLE */ private void startShowAnimator(){ if(animatorSet!=null && animatorSet.isRunning()){ animatorSet.cancel(); } animatorSet = new AnimatorSet(); animatorSet.play(mListViewShowAnimation).with(mPlaceViewShowAnimation); animatorSet.start(); mConvertView.setVisibility(View.VISIBLE); } /** * 退场动画 */ private void startDismissAnimator(){ if(animatorSet!=null && animatorSet.isRunning()){ animatorSet.cancel(); } animatorSet = new AnimatorSet(); animatorSet.play(mListViewDismissAnimation).with(mPlaceViewDismissAnimation); animatorSet.start(); } class GalleryDirsAdapter extends BaseAdapter { private List<PhotoDir> mPhotoDirs; private Context mContext; public GalleryDirsAdapter(Context context,List<PhotoDir> photoDirs){ mContext = context; mPhotoDirs = photoDirs; } @Override public int getCount() { return mPhotoDirs.size(); } @Override public Object getItem(int position) { return mPhotoDirs.get(position); } @Override public long getItemId(int position) { return position; } @Override public View getView(final int position, View convertView, ViewGroup parent) { final PhotoDir item = mPhotoDirs.get(position); ImgViewHolder holder = null; if(convertView==null) { convertView = LayoutInflater.from(mContext).inflate(R.layout.gallery_list_dir_item, null); holder = new ImgViewHolder(); holder.tv_dir_name = (TextView) convertView.findViewById(R.id.tv_dir_name); holder.iv_dir_image=(ImageView)convertView.findViewById(R.id.iv_dir_image); holder.tv_dir_count = (TextView) convertView.findViewById(R.id.tv_dir_count); convertView.setTag(holder); } else { holder=(ImgViewHolder) convertView.getTag(); resetHolder(holder); } convertView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if(position==0){ ((GalleryActivity)mContext).resetPhotos(PhotoFactory.getInstance().getPhotos(),position); }else{ ((GalleryActivity)mContext).resetPhotos(item.getPhotos(),position); } dismiss(); } }); holder.tv_dir_name.setText(item.getName()); Glide.with(mContext).load(item.getFirstPhotoPath()).override(200,200).into(holder.iv_dir_image); return convertView; } class ImgViewHolder{ public TextView tv_dir_name; public ImageView iv_dir_image; public TextView tv_dir_count; } private void resetHolder(ImgViewHolder holder) { holder.tv_dir_name.setText(""); holder.tv_dir_count.setText(""); } }}
popupwindow布局文件
<?xml version="1.0" encoding="UTF-8"?><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" > <View android:id="@+id/view" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#b0000000" /> <ListView android:id="@+id/lv_dirs" android:layout_width="match_parent" android:layout_height="400dp" android:layout_alignParentBottom="true" android:paddingTop="5dp" android:divider="#EEE3D9" android:dividerHeight="1px" android:background="#ffffff" /></RelativeLayout>
外部调用
//设置的高度是屏幕高度减去状态栏高度,减去下方自定义的bottombar的高度,再减去toolbar的高度,实际跟gridview的高度一样dirPopupWindow = new GalleryDirPopupWindow(this,photoDirs,gv_photos.getHeight()); dirPopupWindow.showAsDropDown(findViewById(R.id.layout_bottom),0,-(dirPopupWindow.getHeight()+PixelUtil.dp2px(48)));// dirPopupWindow.showAtLocation(gv_photos,Gravity.NO_GRAVITY,0,toolbar.getHeight()+mStatusBarHeight);//toolbar的高度和通知栏高度
看完代码先说Popupwindow部分,popupwindow在使用的时候要注意的地方有两个,一个是一开始的参数的设置,这个网上介绍很多,不多说。第二个是showAsDropDown方法和showAtLocation方法的使用。
showAsDropDown
这个方法的参数有四个,分别是anchor(popupwindow会显示在该view之下),xoff(x轴上的位移,正值往右偏,负值往左偏),yoff(y轴上的位移,正值往下偏,负值往上偏,跟屏幕的坐标系有关系),gravity(相对于anchor指定对齐)。默认的原点是anchor的左下角。
注意:在android 7.0的系统上,当Popupwindow的宽高是match_parent的时候,则设为match_parent的轴边的偏移失效,全屏显示。所以在使用的时候应该为了兼容7.0的系统而计算Popupwindow的宽高。showAtLocation
这个方法的参数基本上可以参照showAsDropDown,区别是坐标原点是屏幕的坐标原点,即屏幕左上角,gravity也是相对于屏幕的。7.0上位移失效的问题同样存在,所以仍然建议计算宽高。
动画
为了实现多个子view同时开始动画,选用属性动画。动画方面比较简单,就是针对各个view编写动画,然后通过AnimatorSet来实现同时播放动画。
在使用过程中要注意的地方有两个,一个是view的隐藏,在隐藏view时,需要把隐藏的动作放到动画播放之后,不然会直接隐藏,而不展现动画效果。还有一个是设置动画的参数值,一定要确保参数有效,在上面代码中,listview的动画用到了listview的高度,所以必须保证在设置动画的参数之前,将listview的高度计算出来。
最后
这个图库还没有完善,要笔记的东西也有一些,这里只是先把一部分写出来,源码链接还是给出来,想看的同学可以看看,项目名称是Rxjava2Demo,里面的gallery部分。
- 仿微信图库文件夹选择的交互
- 本地选择图库照片,保存在本地新建文件夹
- 图库选择
- android 调用图库中选择的图片
- # 快速、轻量级自定义图库选择库(仿微信图库选择界面)
- android媒体--图库与API层MediaPlayer的交互
- android媒体--图库与API层MediaPlayer的交互
- Android保存截图到系统图库和指定的文件夹
- 选择文件夹的对话框
- 选择文件夹的对话框
- 选择文件夹的对话框
- Android从系统图库中读取选择后的图片
- Android中 调用图库 选择 图片的参数理解
- android 调用图库并显示选择的图片
- Android调用图库选择本地图片的功能
- Android从系统图库中选择图片的源代码
- Android中 调用图库选择图片的参数详解
- Android从图库选择照片并获取图片的path
- 模板不支持分离编译。
- 合成全景图中计算机视觉技术的知识和原理
- Altisum designer制板实用功能及对应快捷键小结:
- [问题解决] maven仓库下载缓慢
- Shiro的异常处理
- 仿微信图库文件夹选择的交互
- 机器学习--AdaBoost算法
- swift地图定位(二十)百度地图的使用(POI)
- 签名方法
- 一款车载GPS定位产品后端服务器架构的填坑之路(一)
- 错误代码 ILLEGAL_PARTNER_EXTERFACE 解决
- 螺旋数组的输出
- 通过JDBC连接oracle数据库的十大技巧
- Android右划喜欢左划不喜欢的View