一、点击按钮弹出卫星导航Button
1、背景:fragment中嵌套recyclerview,当点击功能键(三个点)的时候弹出如右图的导航菜单并伴随动画。
刚接到需求时,开始github上检索相似控件以提供灵感。最终采用这个。https://github.com/linglongxin24/CircleMenu (感谢)
2、灵感:采用根部局为framlayout将五个按钮堆在一起,并置于中间,当点击时,下面的四个按钮做动画,飞出来。点击item,recyclerview通过mRecyclerView.setTranslationY()方法进行上下移动,使buttons能全部弹出,不会被屏幕遮盖。监听到recyclerview位移动画结束后,进行buttons的弹出动画,同时将recyclerview置于不可滑动(通过重写recyclerview),增加蒙层在按钮下边,root_view上边。
3、实施:先上需要插入到root_view中xml item_buttons:(ps小技巧 : 进行测试时,当发现计算有错,或者区域绘制有错,应当给插入的布局一个底色)
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<com.imaginationunlimited.enigma.weight.roundedimageview.RoundedImageView
android:id="@+id/but_close"
android:layout_gravity="center"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#000000"
android:padding="10dp"
android:src="@drawable/play_cancel"
app:riv_corner_radius="48dp"
app:riv_mutate_background="true" />
<com.imaginationunlimited.enigma.weight.roundedimageview.RoundedImageView
android:id="@+id/but_again"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#ffffff"
android:layout_gravity="center"
android:padding="12.7dp"
android:src="@mipmap/saved_again"
app:riv_corner_radius="48dp"
app:riv_mutate_background="true" />
<com.imaginationunlimited.enigma.weight.roundedimageview.RoundedImageView
android:id="@+id/but_save"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#ffffff"
android:padding="12.7dp"
android:layout_gravity="center"
android:src="@mipmap/saved_save"
app:riv_corner_radius="48dp"
app:riv_mutate_background="true" />
<com.imaginationunlimited.enigma.weight.roundedimageview.RoundedImageView
android:id="@+id/but_share"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#ffffff"
android:layout_gravity="center"
android:padding="12.7dp"
android:src="@mipmap/saved_share"
app:riv_corner_radius="48dp"
app:riv_mutate_background="true" />
<com.imaginationunlimited.enigma.weight.roundedimageview.RoundedImageView
android:id="@+id/but_delete"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#ffffff"
android:layout_gravity="center"
android:padding="12.7dp"
android:src="@mipmap/mygallery_delete"
app:riv_corner_radius="48dp"
app:riv_mutate_background="true" />
</FrameLayout>
初始化recyclerview的需要动画
1 private void initAnim() {
2 moveRecycler = ValueAnimator.ofFloat(1, 0);
3 moveRecycler.setDuration(500);
4 moveRecycler.setInterpolator(new AccelerateDecelerateInterpolator());
5 moveRecycler.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
6 @Override
7 public void onAnimationUpdate(ValueAnimator valueAnimator) {
8 float value = (float) valueAnimator.getAnimatedValue();
9 mRecyclerView.setTranslationY(recoverTranslationY * (1 - value));
10 shadowView.setAlpha(1 - value);
11 }
12 });
13 moveRecycler.addListener(new AnimatorListenerAdapter() {
14 @Override
15 public void onAnimationEnd(Animator animation) {
16 butLists(mView);
17 }
18
19 @Override
20 public void onAnimationStart(Animator animation) {
21 ifCanClick = false;
22 shadowView.setVisibility(View.VISIBLE);
23 }
24 });
25 //recycler复位
26 restoreRecycler = ValueAnimator.ofFloat(0, 1);
27 restoreRecycler.setDuration(500);
28 restoreRecycler.setInterpolator(new AccelerateDecelerateInterpolator());
29 restoreRecycler.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
30 @Override
31 public void onAnimationUpdate(ValueAnimator valueAnimator) {
32 float value = (float) valueAnimator.getAnimatedValue();
33 mRecyclerView.setTranslationY(recoverTranslationY * (1 - value));
34 shadowView.setAlpha(1 - value);
35 }
36 });
37 restoreRecycler.addListener(new AnimatorListenerAdapter() {
38 @Override
39 public void onAnimationEnd(Animator animation) {
40 mRecyclerView.setIfScroll(true);
41 shadowView.setVisibility(View.GONE);
42 }
43
44 @Override
45 public void onAnimationStart(Animator animation) {
46 root_view.removeView(v);
47 }
48 });
49 }
初始化数据,设置adapter
1 private void refreshData() {
2 //realm中读取拼图
3 initRealmData();
4 if (resultsList != null) {
5 galleryAdapter = new GalleryAdapter(getActivity(), resultsList);
6 mRecyclerView.setAdapter(galleryAdapter);
7 LinearLayoutManager linearLayoutManager = new LinearLayoutManager(getActivity(), LinearLayoutManager.
8 VERTICAL, false);
9 mRecyclerView.setLayoutManager(linearLayoutManager);
10 galleryAdapter.setOnItemClickListener(new GalleryAdapter.OnItemClickListener() {
11 @Override
12 public void onItemClick(View view, int position) {
13 view.getLocationOnScreen(a);
14 mView = view;
15 // TODO: 2018/12/18 第一个是否需要特殊处理?
16 moveToMid(a);
17 mRecyclerView.setIfScroll(false);
18 galleryAdapter.notifyDataSetChanged();
19 }
20 });
21 } else {
22 empty_content.setVisibility(View.VISIBLE);
23 }
24 }
计算recyclerview偏移量,增加蒙层
1 private void moveToMid(final int[] a) {
2 final int screenHeight = DeviceUtils.getScreenHeight();
3 final int scrollHeight = screenHeight / 2 - a[1];
4 Log.e(TAG, "moveToMid: scrollHeight:" + scrollHeight + " itemY:" + a[1] + " ");
5
6 recoverTranslationY = scrollHeight;
7 buttonTranslationY = scrollHeight;
8
9 shadowView = new View(getActivity());
10 shadowView.setBackgroundColor(Color.parseColor("#66000000"));
11 root_view.addView(shadowView);
12 shadowView.setAlpha(0);
13 shadowView.setOnClickListener(new View.OnClickListener() {
14 @Override
15 public void onClick(View view) {
16
17 }
18 });
41 moveRecycler.start();
42 }
addview,设置监听(view为功能键 三个点)
1 public void butLists(final View view) {
2 view.setVisibility(View.GONE);
3 //添加布局
4 v = LayoutInflater.from(getActivity()).inflate(R.layout.item_buttons, null, false);
5
6 Log.e(TAG, "butLists: a[0]=" + a[0] + ";a[1]=" + a[1] + ";getScreenWidth=" + DeviceUtils.getScreenWidth());
7 v.setTranslationX(a[0] - DeviceUtils.getScreenWidth() / 2 + mView.getWidth() / 2); //-------------------写法有待考证
8 v.setTranslationY(mView.getHeight() / 2); //--------------------写法有待考证
9 ImageView but_close = v.findViewById(R.id.but_close);
10 but_close.setColorFilter(Color.WHITE);
11 //卫星菜单相关
12 final List<ImageView> imageViews = new ArrayList<>();
13 ImageView but_again = v.findViewById(R.id.but_again);
14 ImageView but_share = v.findViewById(R.id.but_share);
15 ImageView but_save = v.findViewById(R.id.but_save);
16 ImageView but_delete = v.findViewById(R.id.but_delete);
17 imageViews.add(but_again);
18 imageViews.add(but_share);
19 imageViews.add(but_save);
20 imageViews.add(but_delete);
21 //将弹出的四个按钮设置为功能键1.3倍 (产品需求)
22 // for (int i = 0; i < imageViews.size(); i++) {
23 // FrameLayout.LayoutParams l = new FrameLayout.LayoutParams(imageViews.get(i).getLayoutParams());
24 // l.width = (int) (view.getWidth() * 1.3);
25 // l.height = (int) (view.getHeight() * 1.3);
26 // imageViews.get(i).setLayoutParams(l);
27 // }
28 but_close.setOnClickListener(new View.OnClickListener() {
29 @Override
30 public void onClick(View view1) {
31 closeSectorMenu(imageViews, v);
32 }
33 });
34 but_again.setOnClickListener(new View.OnClickListener() {
35 @Override
36 public void onClick(View view) {
37 Toast.makeText(getActivity(), "but_again", Toast.LENGTH_SHORT).show();
38 }
39 });
40 but_share.setOnClickListener(new View.OnClickListener() {
41 @Override
42 public void onClick(View view) {
43 Toast.makeText(getActivity(), "but_share", Toast.LENGTH_SHORT).show();
44
45 }
46 });
47 but_save.setOnClickListener(new View.OnClickListener() {
48 @Override
49 public void onClick(View view) {
50 Toast.makeText(getActivity(), "but_save", Toast.LENGTH_SHORT).show();
51
52 }
53 });
54 but_delete.setOnClickListener(new View.OnClickListener() {
55 @Override
56 public void onClick(View view) {
57 Toast.makeText(getActivity(), "but_delete", Toast.LENGTH_SHORT).show();
58
59 }
60 });
61 showCircleMenu(imageViews, view);
62 root_view.addView(v);
63 RelativeLayout.LayoutParams ll = new RelativeLayout.LayoutParams(DeviceUtils.getScreenWidth(), DeviceUtils.getScreenHeight()); //将插入的布局设置成全屏
64 v.setLayoutParams(ll);
65 }
此前直接将需要addview的布局加到指定位置,结果出现了只有功能键有监听事件,其他弹出的按钮没有。后来通过将插入的xml背景颜色置于黑色,发现整个xml以功能键为原点向右侧和下侧展开。导致弹出来的几个按钮都无点击事件。
经过重新计算,发现如果将插入的xml设置成屏幕宽高,那么起始的几个buttons都在屏幕中间,也就是图中target1的距离。所以最终确定插入的xml位移为上图代码中7、8行。
接下来的代码就是打开和关闭卫星栏及一些状态的监听了
1 private void showCircleMenu(final List<ImageView> imageViews, View view) {
2 isShowing = true;
3 /***第一步,遍历所要展示的菜单ImageView*/
4 for (int i = 0; i < imageViews.size(); i++) {
5 final View v = imageViews.get(i);
6 // .setLayoutParams(new FrameLayout.LayoutParams(view.getWidth()*1.3,view.getWidth()*1.3));
7 PointF point = new PointF();
8 /***第二步,根据菜 .fit()
9 // 单个数计算每个菜单之间的间隔角度*/
10 int avgAngle = (135 / (imageViews.size() - 1));
11 /**第三步,根据间隔角度计算出每个菜单相对于水平线起始位置的真实角度**/
12 int angle = avgAngle * i - 45;
13 Log.e(TAG, "showCircleMenu: angle:" + angle);
14
15 //圆点坐标:(x0,y0)
16 //半径:r
17 //角度:a0
18 //则圆上任一点为:(x1,y1)
19 //x1 = x0 + r * cos(ao * 3.14 /180 )
20 //y1 = y0 + r * sin(ao * 3.14 /180 )
21
22
23 //第四步,根据每个菜单真实角度计算其坐标值
24 point.x = (float) -Math.cos(angle * (Math.PI / 180)) * radius1;
25 point.y = (float) Math.sin(angle * (Math.PI / 180)) * radius1;
26 Log.e(TAG, point.toString());
27 ObjectAnimator objectAnimatorX = ObjectAnimator.ofFloat(imageViews.get(i), "translationX", 0, point.x / 2);
28 ObjectAnimator objectAnimatorY = ObjectAnimator.ofFloat(imageViews.get(i), "translationY", 0, point.y / 2);
29 //动画集合,用来编排动画
30 AnimatorSet animatorSet = new AnimatorSet();
31 animatorSet.setDuration(300);
32 animatorSet.play(objectAnimatorX).with(objectAnimatorY);
33 animatorSet.addListener(new AnimatorListenerAdapter() {
34 @Override
35 public void onAnimationEnd(Animator animation) {
36 ifCanClick = true;
37 }
38
39 @Override
40 public void onAnimationStart(Animator animation) {
41 }
42 });
43 animatorSet.start();
44 }
45 }
关闭
1 private void closeSectorMenu(List<ImageView> imageViews, final View v) {
2 for (int i = 0; i < imageViews.size(); i++) {
3 PointF point = new PointF();
4 int avgAngle = (135 / (imageViews.size() - 1));
5 int angle = avgAngle * i - 45;
6 Log.d(TAG, "angle=" + angle);
7 point.x = (float) -Math.cos(angle * (Math.PI / 180)) * radius1;
8 point.y = (float) Math.sin(angle * (Math.PI / 180)) * radius1;
9 Log.d(TAG, point.toString());
10
11 ObjectAnimator objectAnimatorX = ObjectAnimator.ofFloat(imageViews.get(i), "translationX", point.x / 2, 0);
12 ObjectAnimator objectAnimatorY = ObjectAnimator.ofFloat(imageViews.get(i), "translationY", point.y / 2, 0);
13 AnimatorSet animatorSet = new AnimatorSet();
14 animatorSet.setDuration(300);
15 animatorSet.play(objectAnimatorX).with(objectAnimatorY);
16 animatorSet.addListener(new AnimatorListenerAdapter() {
17 @Override
18 public void onAnimationEnd(Animator animation) {
19 //点击叉,收起后recyclerview归位
20 mView.setVisibility(View.VISIBLE);
21 restoreRecycler.start();
22 }
23 });
24 animatorSet.start();
25 }
26 isShowing = false;
27 }
剩下就是adapter中的回调了,比较简单
1 //点击功能键
2 holder.but_func.setOnClickListener(new View.OnClickListener() {
3 @Override
4 public void onClick(View view) {
5 if (GalleryFragment.ifCanClick){
6 int position = holder.getAdapterPosition();
7 onItemClickListener.onItemClick(holder.but_func, position);
8 //标记点击的item的位置(没用上)
9 posList.clear();
10 posList.add(position);
11 Log.e(TAG, "点击功能键: " + position);
12 }
13 }
14 });
接口:包括set方法
1 /**
2 * 自定义的点击事件接口
3 *
4 * @author lish
5 */
6 public interface OnItemClickListener {
7 void onItemClick(View view, int position);
8 // void onItemLongClick(View view, int position); //长按
9 }
二、总结:
1、对view的绘制流程还是不熟练,LayoutParam不够深入了解,只会简单实用,不知原理
2、对动画的使用不够熟练
3、对计算偏移量不够熟练