程序员

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

2017-06-12  本文已影响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

上一篇下一篇

猜你喜欢

热点阅读