美文网首页程序员
仿QQ空间点击加号弹出菜单特效

仿QQ空间点击加号弹出菜单特效

作者: 一个不掉头发的开发 | 来源:发表于2017-06-12 14:22 被阅读214次

    最近项目需要,前几天写了一个仿微信相册(包括编辑相册)功能,审核代码的时候发现同事要实现一个类似仿QQ空间点击加号弹出菜单特效,于是看了一些他的代码,我发现虽然他实现了功能,但是不够完善,所以我又花了半天时间写了一个。废话不多说,先看看需求:
    QQ空间图:


    Screenshot_2017-06-12-11-50-16-603_com.qzone.png

    我最后实现的效果:


    device-2017-06-12-115849.png
    device-2017-06-12-115841.png
    device-2017-06-12-115829.png
    这个功能看起来简单,思路广泛,写法很多。最简单的一种写法,也是我同事用到的思路,就是在布局里定义8个按钮,分别添加点击事件和动画,但是这种写法局限性太大,没法扩展,万一有一天产品说需要20个这样的按钮呢?这时候要么重新开发,要么定义20个按钮。

    我的思路:

    (1)要实现左右滑动最先想到的是viewPager
    (2)实现多排最先想到GridView
    (3)每个图片和文字显示的样式一模一样,可以抽取封装成一个小View
    (4)分页显示,总的页数=总数/每页数量,并取整
    根据以上1,2,3条思路,我最后选择viewPager+GridView实现这种效果。

    部分主要代码##

    1,部分属性

     /**
         * 动画执行的 属性值数组
         */
        float animatorProperty[] = null;
        /**
         * 第一排图 距离屏幕底部的距离
         */
        int top = 0;
        /**
         * 第二排图 距离屏幕底部的距离
         */
        int bottom = 0;
    
        /**
         * 总的页数
         */
        private int pageCount;
        /**
         * 每一页显示的个数
         */
        private int pageSize = 10;
        /**
         * 当前显示的是第几页
         */
        private int curIndex = 0;
    
        /**
         * 创建 popupWindow 内容
         *
         * @param context context
         */
    
        /**
         * dp转化为px
         *
         * @param context  context
         * @param dipValue dp value
         * @return 转换之后的px值
         */
        public static int dip2px(Context context, float dipValue) {
            final float scale = context.getResources().getDisplayMetrics().density;
            return (int) (dipValue * scale + 0.5f);
        }
    

    初始化View

     private void _createView(final Context context) {
            rootVew = LayoutInflater.from(context).inflate(R.layout.popup_menu, null);
            popupWindow = new PopupWindow(rootVew,
                    LinearLayout.LayoutParams.MATCH_PARENT,
                    LinearLayout.LayoutParams.MATCH_PARENT);
            //设置为失去焦点 方便监听返回键的监听
            popupWindow.setFocusable(false);
    
            // 如果想要popupWindow 遮挡住状态栏可以加上这句代码
            //popupWindow.setClippingEnabled(false);
            popupWindow.setBackgroundDrawable(new BitmapDrawable());
            popupWindow.setOutsideTouchable(false);
    
            if (animatorProperty == null) {
                top = dip2px(context, 310);
                bottom = dip2px(context, 210);
                animatorProperty = new float[]{bottom, 60, -30, -20 - 10, 0};
            }
    
            initLayout(context);
        }
    
     /**
         * 初始化 view
         */
        private void initLayout(final Context context) {
            //初始化数据源
            initDatas(context);
            rlClick = (RelativeLayout) rootVew.findViewById(R.id.pop_rl_click);
            ivBtn = (ImageView) rootVew.findViewById(R.id.pop_iv_img);
            mPager = (ViewPager) rootVew.findViewById(R.id.viewpager);
            mLlDot = (LinearLayout) rootVew.findViewById(R.id.ll_dot);
    
    
            rlClick.setOnClickListener(new MViewClick(0, context));
            mPager.setOnClickListener(new MViewClick(1, context));
            mLlDot.setOnClickListener(new MViewClick(2, context));
    
            inflater = LayoutInflater.from(context);
            //总的页数=总数/每页数量,并取整
            pageCount = (int) Math.ceil(mDatas.size() * 1.0 / pageSize);
            mPagerList = new ArrayList<>();
            for (int i = 0; i < pageCount; i++) {
                // 每个页面都是inflate出一个新实例
                GridView gridView = (GridView) inflater.inflate(R.layout.gridview, mPager, false);
                gridView.setAdapter(new GridViewAdapter(context, mDatas, i, pageSize));
                mPagerList.add(gridView);
    
                gridView.setOnItemClickListener((parent, view, position, id) -> {
                    int pos = position + curIndex * pageSize;
                    Toast.makeText(context, mDatas.get(pos).getName(), Toast.LENGTH_SHORT).show();
                });
            }
            //设置适配器
            mPager.setAdapter(new ViewPagerAdapter(mPagerList));
            //设置圆点
            setOvalLayout();
        }
    
    
        /**
         * 设置圆点
         */
        public void setOvalLayout() {
            for (int i = 0; i < pageCount; i++) {
                mLlDot.addView(inflater.inflate(R.layout.dot, null));
            }
            // 默认显示第一页
            mLlDot.getChildAt(0).findViewById(R.id.v_dot)
                    .setBackgroundResource(R.drawable.dot_selected);
            mPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
                public void onPageSelected(int position) {
                    // 取消圆点选中
                    mLlDot.getChildAt(curIndex)
                            .findViewById(R.id.v_dot)
                            .setBackgroundResource(R.drawable.dot_normal);
                    // 圆点选中
                    mLlDot.getChildAt(position)
                            .findViewById(R.id.v_dot)
                            .setBackgroundResource(R.drawable.dot_selected);
                    curIndex = position;
                }
    
                public void onPageScrolled(int arg0, float arg1, int arg2) {
                }
    
                public void onPageScrollStateChanged(int arg0) {
                }
            });
        }
    

    3,初始化数据

       /**
         * 初始化数据源
         */
        private void initDatas(Context mContext) {
            mDatas = new ArrayList<>();
            for (int i = 0; i < titles.length; i++) {
                //动态获取资源ID,第一个参数是资源名,第二个参数是资源类型例如drawable,string等,第三个参数包名
                int imageId = mContext.getResources().getIdentifier("ic_category_" + i, "mipmap", mContext.getPackageName());
                mDatas.add(new Model(titles[i], imageId));
            }
        }
    

    4,两个Adapter(ViewPager和GridView的适配器)

    public class GridViewAdapter extends BaseAdapter {
        private List<Model> mDatas;
        private LayoutInflater inflater;
        /**
         * 页数下标,从0开始(当前是第几页)
         */
        private int curIndex;
        /**
         * 每一页显示的个数
         */
        private int pageSize;
    
        public GridViewAdapter(Context context, List<Model> mDatas, int curIndex, int pageSize) {
            inflater = LayoutInflater.from(context);
            this.mDatas = mDatas;
            this.curIndex = curIndex;
            this.pageSize = pageSize;
        }
    
        /**
         * 先判断数据集的大小是否足够显示满本页,如果够,则直接返回每一页显示的最大条目个数pageSize,如果不够,则有几项就返回几,(也就是最后一页的时候就显示剩余item)
         */
        @Override
        public int getCount() {
            return mDatas.size() > (curIndex + 1) * pageSize ? pageSize : (mDatas.size() - curIndex * pageSize);
        }
    
        @Override
        public Object getItem(int position) {
            return mDatas.get(position + curIndex * pageSize);
        }
    
        @Override
        public long getItemId(int position) {
            return position + curIndex * pageSize;
        }
    
        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            ViewHolder viewHolder;
            if (convertView == null) {
                convertView = inflater.inflate(R.layout.item_gridview, parent, false);
                viewHolder = new ViewHolder();
                viewHolder.tv = (TextView) convertView.findViewById(R.id.textView);
                viewHolder.iv = (ImageView) convertView.findViewById(R.id.imageView);
                convertView.setTag(viewHolder);
            } else {
                viewHolder = (ViewHolder) convertView.getTag();
            }
            /**
             * 在给View绑定显示的数据时,计算正确的position = position + curIndex * pageSize
             */
            int pos = position + curIndex * pageSize;
            viewHolder.tv.setText(mDatas.get(pos).name);
            viewHolder.iv.setImageResource(mDatas.get(pos).iconRes);
            return convertView;
        }
    
        class ViewHolder {
            public TextView tv;
            public ImageView iv;
        }
    }
    
    
    public class ViewPagerAdapter extends PagerAdapter {
        private List<View> mViewList;
    
        public ViewPagerAdapter(List<View> mViewList) {
            this.mViewList = mViewList;
        }
    
        @Override
        public void destroyItem(ViewGroup container, int position, Object object) {
            container.removeView(mViewList.get(position));
        }
    
        @Override
        public Object instantiateItem(ViewGroup container, int position) {
            container.addView(mViewList.get(position));
            return (mViewList.get(position));
        }
    
        @Override
        public int getCount() {
            if (mViewList == null)
                return 0;
            return mViewList.size();
        }
    
        @Override
        public boolean isViewFromObject(View view, Object object) {
            return view == object;
        }
    }
    
    

    5,最后添加动画

    /**
         * 关闭 popupWindow执行的动画
         */
        public void _rlClickAction() {
            if (ivBtn != null && rlClick != null) {
    
                ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(ivBtn, "rotation", 135f, 0f);
                objectAnimator.setDuration(300);
                objectAnimator.start();
                _closeAnimation(mPager, 300, top);
    
            /*    _closeAnimation(mPager, 300, top);
                _closeAnimation(mPager, 200, top);
                _closeAnimation(mPager, 200, top);
                _closeAnimation(mPager, 300, top);
                _closeAnimation(mPager, 300, bottom);
                _closeAnimation(mPager, 200, bottom);
                _closeAnimation(mPager, 200, bottom);
                _closeAnimation(mPager, 300, bottom);*/
    
                rlClick.postDelayed(() -> _close(), 300);
    
            }
        }
    
    /**
         * 关闭 popupWindow 时的动画
         *
         * @param view     mView
         * @param duration 动画执行时长
         * @param next     平移量
         */
        private void _closeAnimation(View view, int duration, int next) {
            ObjectAnimator anim = ObjectAnimator.ofFloat(view, "translationY", 0f, next);
            anim.setDuration(duration);
            anim.start();
        }
    
        /**
         * 启动动画
         *
         * @param view     view
         * @param duration 执行时长
         * @param distance 执行的轨迹数组
         */
        private void _startAnimation(View view, int duration, float[] distance) {
            ObjectAnimator anim = ObjectAnimator.ofFloat(view, "translationY", distance);
            anim.setDuration(duration);
            anim.start();
        }
    

    大概功能介绍这么多,有其他方面的意见或者建议可以跟我沟通,

    特别感谢

    这里特别感谢@MjCodeTinker,在撸代码的过程中参考了他的思路和部分代码片段,实现了一个扩展性更强的功能,本项目仅仅是为了学习技术开发,没有其他任何目的,更没有想侵权或者随意该别人劳动成果的意思。
    详细地址:https://github.com/ZQ330093887/WindowMenu

    相关文章

      网友评论

        本文标题:仿QQ空间点击加号弹出菜单特效

        本文链接:https://www.haomeiwen.com/subject/gbklqxtx.html