android qq右上加号,仿QQ空间点击加号弹出菜单特效

本文介绍了如何使用ViewPager和GridView来实现一个仿QQ空间点击加号弹出菜单的动画效果,包括左右滑动、多排显示和动态加载。通过封装和扩展,使得功能更具灵活性,能够应对更多按钮的需求。文章还提供了关键代码示例和详细的实现步骤。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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

QQ空间图:

67f32b3584c6

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

我最后实现的效果:

67f32b3584c6

device-2017-06-12-115849.png

67f32b3584c6

device-2017-06-12-115841.png

67f32b3584c6

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 mDatas;

private LayoutInflater inflater;

/**

* 页数下标,从0开始(当前是第几页)

*/

private int curIndex;

/**

* 每一页显示的个数

*/

private int pageSize;

public GridViewAdapter(Context context, List 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 mViewList;

public ViewPagerAdapter(List 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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值