一、背景:fragment中嵌套recyclerview,當點擊功能鍵(三個點)的時候彈出如右圖的導航菜單並伴隨動畫。android
剛接到需求時,開始github上檢索類似控件以提供靈感。最終採用這個。https://github.com/linglongxin24/CircleMenu (感謝)git
二、靈感:採用根部局爲framlayout將五個按鈕堆在一塊兒,並置於中間,當點擊時,下面的四個按鈕作動畫,飛出來。點擊item,recyclerview經過mRecyclerView.setTranslationY()方法進行上下移動,使buttons能所有彈出,不會被屏幕遮蓋。監聽到recyclerview位移動畫結束後,進行buttons的彈出動畫,同時將recyclerview置於不可滑動(經過重寫recyclerview),增長蒙層在按鈕下邊,root_view上邊。github
三、實施:先上須要插入到root_view中xml item_buttons:(ps小技巧 : 進行測試時,當發現計算有錯,或者區域繪製有錯,應當給插入的佈局一個底色)app
<?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的須要動畫ide
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以功能鍵爲原點向右側和下側展開。致使彈出來的幾個按鈕都無點擊事件。spa
通過從新計算,發現若是將插入的xml設置成屏幕寬高,那麼起始的幾個buttons都在屏幕中間,也就是圖中target1的距離。因此最終肯定插入的xml位移爲上圖代碼中七、8行。rest
接下來的代碼就是打開和關閉衛星欄及一些狀態的監聽了
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 }
一、對view的繪製流程仍是不熟練,LayoutParam不夠深刻了解,只會簡單實用,不知原理
二、對動畫的使用不夠熟練
三、對計算偏移量不夠熟練