Android's eye-catching circle menu is a second-level high-imitation CCB circle menu

Source: Internet
Author: User
Tags asin hypot set background

Android's eye-catching circle menu is a second-level high-imitation CCB circle menu

 

1. Overview

Today, I opened the China Construction Bank to check my deposits. When I saw my grief, I looked at it again. The circular menu was quite dazzling. So, in order to conceal my grief, I decided to achieve this effect. Well, there is actually another reason. I remember that my app was despised when I first started android and said that my menu was not as good as that of CCB. Today, it's time to prove myself. I decided to use the circular Menu Control I made to implement a CCB menu for him within 32 s. By the way, I would like to teach him ~~

Let's take a look at the joke:

OK, this is our main effect today ~~ Scroll directly with your finger and scroll quickly. Click Item ~~~

The background of this effect was that after I had been kneeling for more than two hours, iegg spent 32 s doing it for me. Thanks again to iegg.

 

Next, we will use this control to implement the circle menu of CCB ~~

OK, in minutes ~~

It is a bit disgusting. The introduction on the left of NIMA Construction Bank and the background of the menu on the right are two pictures, and they want to achieve seamless connection, A few more lines of code are written in the measurement of the circular menu.

 

2. Usage

Let's take a look at the usage and have an intuitive understanding.

 

1. MainActivity

 

 

Package com. zhy. ccbCricleMenu; import android. app. activity; import android. OS. bundle; import android. view. view; import android. widget. toast; import com. zhy. view. circleMenuLayout; import com. zhy. view. circleMenuLayout. onMenuItemClickListener; public class MainActivity extends Activity {private CircleMenuLayout mCircleMenuLayout; private String [] mItemTexts = new String [] {Security Center, special service, investment and financial management, transfer and remittance, my account, credit card}; private int [] mItemImgs = new int [] {R. drawable. home_mbank_1_normal, R. drawable. home_mbank_2_normal, R. drawable. home_mbank_3_normal, R. drawable. home_mbank_4_normal, R. drawable. home_mbank_5_normal, R. drawable. home_mbank_6_normal}; @ Overrideprotected void onCreate (Bundle savedInstanceState) {super. onCreate (savedInstanceState); setContentView (R. layout. activity_main02); mCircleMenuLayout = (CircleMenuLayout) findViewById (R. id. id_menulayout); mCircleMenuLayout. setMenuItemIconsAndTexts (mItemImgs, mItemTexts); mCircleMenuLayout. setOnMenuItemClickListener (new OnMenuItemClickListener () {@ Overridepublic void itemClick (View view, int pos) {Toast. makeText (MainActivity. this, mItemTexts [pos], Toast. LENGTH_SHORT ). show () ;}@ Overridepublic void itemCenterClick (View view) {Toast. makeText (MainActivity. this, you can do something just like ccb, Toast. LENGTH_SHORT ). show ();}});}}

2. layout files

 

     
          
               
                
             
     
    
       
  
 

A control in the CircleMenuLayout of the layout file is the View in the middle of the circular menu. Of course, you can leave it unspecified ~

 

The use of the entire control should not be too simple. One line of code: setMenuItemIconsAndTexts can be used to set text and images ~~~

To listen to click events, use the setOnMenuItemClickListener interface.

 

OK. I wonder if you have noticed that the id of the view in the center of our layout is as follows: android: id = @ id/id_circle_menu_item_center, where is visa, because our custom control depends on our id, I do not want users to specify this id, but define some IDs in advance for users to use. In fact, this is also very common. When you use some controls, some IDS also need to be done. What controls do you forget ~

So how to define such an id resource?

Create an ids. xml file under res/values:

 

 
     
      
       
    
   
  
 

We can see that three IDs are written in it, which will be used later in the first two. Don't forget to define them here.

 

 

3. Simple Analysis

For the above effect, we decided to customize a ViewGroup called CircleMenuLayout;

For menu items, text + images, you can set either of them or all of them ~~ Then we only need to set their positions in layout of CircleMenuLayout.

Of course, before layout (), we need to perform onMeasure to measure, set our own width and height, and set the width and height of the item;

Finally, the interaction with the user is rolled out:

We will rewrite the dispatchTouchEvent event and write the code that follows the finger movement in it ~~ Why don't I write in onTouchEvent any more, because if I write in onTouchEvent, when the user touches the item, our menu cannot be moved, because the item is clickable and will be used as our targetView, and then consume our MOVE event ~~~ For details about event distribution, see Android ViewGroup event distribution mechanism.

Of course, there are still many details, such as how to quickly scroll and when to trigger the item click event.

Skip this step if you are not clear about it ~~ Continue reading ~~

 

4. onMeasure of CircleMenuLayout

Before measurement, let's take a look at the published setMenuItemIconsAndTexts, which should be before measurement.

 

/*** Set the icon and text of the menu bar ** @ param resIds */public void setMenuItemIconsAndTexts (int [] resIds, String [] texts) {mItemImgs = resIds; mItemTexts = texts; // check if (resIds = null & texts = null) {throw new IllegalArgumentException (set at least one of the menu item text and image );} // initialize mMenuCountmMenuItemCount = resIds = null? Texts. length: resIds. length; if (resIds! = Null & texts! = Null) {mMenuItemCount = Math. min (resIds. length, texts. length);} addMenuItems ();}/*** add menu item */private void addMenuItems () {LayoutInflater mInflater = LayoutInflater. from (getContext ();/*** initialize view */for (int I = 0; I <mMenuItemCount; I ++) based on the parameters set by the user) {final int j = I; View view = mInflater. inflate (R. layout. circle_menu_item, this, false); ImageView iv = (ImageView) view. findViewById (R. id. id_circle_menu_ I Tem_image); TextView TV = (TextView) view. findViewById (R. id. id_circle_menu_item_text); if (iv! = Null) {iv. setVisibility (View. VISIBLE); iv. setImageResource (mItemImgs [I]); iv. setOnClickListener (new OnClickListener () {@ Overridepublic void onClick (View v) {if (mOnMenuItemClickListener! = Null) {mOnMenuItemClickListener. itemClick (v, j) ;}});} if (TV! = Null) {TV. setVisibility (View. VISIBLE); TV. setText (mItemTexts [I]);} // Add the view to the container addView (view );}}

It is relatively simple to get two pieces of data, calculate the number of menus, and traverse them. Set the value based on the preset R. layout. circle_menu_item.

 

Let's take a look at our R. layout. circle_menu_item.

 

 
     
      
   
  
 

It is actually a layout file with a TV and an iv in it. Pay attention to the two IDs in it. We have mentioned before ~~

 

Will anyone have any questions here? Why should I develop an independent layout? Why not write it in the code ~~~

Well, I am not self-willed. If my code is written to death, the requirement is that the right side of the text is an icon on the left. What do you do to change the source code? What if we are independent? You can change the layout yourself ~~~ Of course, you can also publish the layout in one way, such as setMenuItemLayoutId.

By the way, the click event is also involved. This is so easy, with several lines of code:

 

/*** MenuItem Click Event interface ** @ author zhy **/public interface OnMenuItemClickListener {void itemClick (View view, int pos); void itemCenterClick (View view View );} /*** MenuItem Click Event interface */private OnMenuItemClickListener mOnMenuItemClickListener;/*** set the MenuItem Click Event interface ** @ param mOnMenuItemClickListener */public void listener (mOnMenuItemClickListener) {this. mOnMenuItemClickListener = mOnMenuItemClickListener ;}

I'm not talking about it. You 've seen it for countless times ~~

 

 

Now let's take a look at the declared variables and onMeasure.

 

Public class CircleMenuLayout extends ViewGroup {private int mRadius;/*** default size of the child item in the container */private static final float RADIO_DEFAULT_CHILD_DIMENSION = 1/4f; /*** the default size of the child in the center of the menu */private float RADIO_DEFAULT_CENTERITEM_DIMENSION = 1/3f;/*** the padding of the container, regardless of the padding attribute, use this variable */private static final float RADIO_PADDING_LAYOUT = 1/12f if margins are required./*** when the angle of movement per second reaches this value, fast Moving */private static final int FLINGAB LE_VALUE = 300;/*** if the angle of Movement reaches this value, click */private static final int NOCLICK_VALUE = 3; /*** when the angle of movement per second reaches this value, it is regarded as fast moving */private int mFlingableValue = FLINGABLE_VALUE;/*** the padding of the container, regardless of the padding attribute, use this variable */private float mPadding for margins;/* the start angle of the layout */private double mStartAngle = 0; /*** Text of the menu item */private String [] mItemTexts;/*** icon of the menu item */private int [] mItemImgs; /*** Number of menus */private int mMenuItemCount;/*** check the press To the angle of rotation when lifting */private float mTmpAngle;/*** detects the time used when pressing to lift */private long mDownTime; /*** determine whether automatic scrolling */private boolean isFling; public CircleMenuLayout (Context context, AttributeSet attrs) {super (context, attrs); // ignore paddingsetPadding (0, 0, 0, 0);}/*** set the layout width and height, and set the menu item width and height */@ Overrideprotected void onMeasure (int widthMeasureSpec, int heightMeasureSpec) {int resWidth = 0; int resHeight = 0;/*** based on input parameters Obtain the Measurement Mode and measurement value */int width = MeasureSpec. getSize (widthMeasureSpec); int widthMode = MeasureSpec. getMode (widthMeasureSpec); int height = MeasureSpec. getSize (heightMeasureSpec); int heightMode = MeasureSpec. getMode (heightMeasureSpec);/*** if the width or height of the measurement mode is not accurate */if (widthMode! = MeasureSpec. EXACTLY | heightMode! = MeasureSpec. EXACTLY) {// The height of the background image is resWidth = getSuggestedMinimumWidth (); // if no background image is set, the default value is resWidth = 0? GetDefaultWidth (): resWidth; resHeight = getSuggestedMinimumHeight (); // if no background image is set, set it to the default value of resHeight = 0? GetDefaultWidth (): resHeight;} else {// if both are set to exact values, a small value is taken directly; resWidth = resHeight = Math. min (width, height);} setMeasuredDimension (resWidth, resHeight); // obtain the radius mRadius = Math. max (getMeasuredWidth (), getMeasuredHeight (); // Number of menu items final int count = getChildCount (); // menu item size int childSize = (int) (mRadius * RADIO_DEFAULT_CHILD_DIMENSION); // menu item Measurement Mode int childMode = MeasureSpec. EXACTLY; // iterative measurement for (int I = 0; I <count; I ++) {final View child = getChildAt (I); if (child. getVisibility () = GONE) {continue;} // calculates the size of the menu item, and measures the item in the configured mode. int makeMeasureSpec =-1; if (child. getId () = R. id. id_circle_menu_item_center) {makeMeasureSpec = MeasureSpec. makeMeasureSpec (int) (mRadius * RADIO_DEFAULT_CENTERITEM_DIMENSION), childMode);} else {makeMeasureSpec = MeasureSpec. makeMeasureSpec (childSize, childMode);} child. measure (makeMeasureSpec, makeMeasureSpec);} mPadding = RADIO_PADDING_LAYOUT * mRadius ;}

First, let's talk about the variables: in fact, there are comments, mRadius is the width of our entire View; several constants are the ratio of the width of our menu item to mRadius; RADIO_PADDING_LAYOUT indicates the proportion of the padding. For the rest, see the comments ~~

 

What about measurement?

First, we obtain the width and height values and the mode ~~~ Based on widthMeasureSpec and heightMeasureSpec ~~~

Will someone ask what this value is? How can we get the width and height through it? It doesn't matter. We happen to be ViewGroup. We need to measure the sub-View, just to pass these two parameters:

You can see child. measure (makeMeasureSpec, makeMeasureSpec); two values are passed in. You are looking at how these two values are formed,

MakeMeasureSpec = MeasureSpec. makeMeasureSpec (childSize, childMode); Package the size and mode together through MeasureSpec ~~ Well, I can't talk about it any more. I have the opportunity to write a summary blog of custom controls to elaborate on this.

After obtaining the size and mode, let's determine whether the mode is EXACTLY (if you do not know about the three modes, refer to: Android manual to teach you how to customize ViewGroup (1 ))

If it is EXACTLY, it is as simple as taking the minimum values of the two.

If no, no, the default size is used based on the size of the Set background image. If no background image exists, the default size is actually the screen width and the small value of high school;

 

/*** Get the default layout size ** @ return */private int getDefaultWidth () {WindowManager wm = (WindowManager) getContext (). getSystemService (Context. WINDOW_SERVICE); DisplayMetrics outMetrics = new DisplayMetrics (); wm. getdefadisplay display (). getMetrics (outMetrics); return Math. min (outMetrics. widthPixels, outMetrics. heightPixels );}

After obtaining the size of the parent control, we can set setMeasuredDimension ~~ Then, based on the width and height of the parent control, set the proportions of our preset constants to set the width and height for our menu item:

 

The width is calculated. Here, the width is the exact value, so we set the menu item mode to EXACTLY, and finally pass it to the child through the MeasureSpec encapsulation. measure (makeMeasureSpec, makeMeasureSpec.

After the measurement is completed, prepare the layout ~~

 

5. onLayout of CircleMenuLayout

In onLayout, we set the menu item to the specified position. In theory, our circular menu looks like this ~~

 

/*** Set the menu item position */@ Overrideprotected void onLayout (boolean changed, int l, int t, int r, int B) {int layoutRadius = mRadius; // Laying out the child viewsfinal int childCount = getChildCount (); int left, top; // the size of the menu item int cWidth = (int) (layoutRadius * RADIO_DEFAULT_CHILD_DIMENSION ); // based on the number of menu items, the calculation angle is float angleDelay = 360/(getChildCount ()-1); // The Position of the menuitem is set for (int I = 0; I <c HildCount; I ++) {final View child = getChildAt (I); if (child. getId () = R. id. id_circle_menu_item_center) continue; if (child. getVisibility () = GONE) {continue;} mStartAngle % = 360; // calculate the float tmp = layoutRadius/2f-cWidth/2-mPadding distance from the center of the menu item to the center of the menu item; // tmp cosa: left = layoutRadius/2 + (int) Math. round (tmp * Math. cos (Math. toRadians (mStartAngle)-1/2f * cWidth); // tmp sina is men Top = layoutRadius/2 + (int) Math. round (tmp * Math. sin (Math. toRadians (mStartAngle)-1/2f * cWidth); child. layout (left, top, left + cWidth, top + cWidth); // overlay the size of mStartAngle + = angleDelay;} // locate the center view, if the onclick event View cView = findViewById (R. id. id_circle_menu_item_center); if (cView! = Null) {cView. setOnClickListener (new OnClickListener () {@ Overridepublic void onClick (View v) {if (mOnMenuItemClickListener! = Null) {mOnMenuItemClickListener. itemCenterClick (v) ;}}); // sets the center item Position int cl = layoutRadius/2-cView. getMeasuredWidth ()/2; int cr = cl + cView. getMeasuredWidth (); cView. layout (cl, cl, cr, cr );}}

Measurement is nothing more than traversing, and then calculating left and top. Of course, here we are round, so we will use some mathematical knowledge. Tmp * cosa is the abscissa of the menu item center, and tmp * sina is the ordinate of the menu item. For details about this calculation, refer to the Android SurfaceView practice to create a lottery turntable. Of course, I have also drawn a picture for you:

 

Assume that the circle is our menu item, and its coordinates are mRadius/2 + tmp * coas and mRadius/2 + tmp * sina.

If you only need to implement a circular menu and do not need to scroll with your fingers, you can do it here. Thank you for taking it away.

 

Let's continue to scroll down.

 

6. dispatchTouchEvent of CircleMenuLayout

 

/*** Record the last x, y coordinate */private float mLastX; private float mLastY;/*** automatically scrolling Runnable */private AutoFlingRunnable mFlingRunnable; @ Overridepublic boolean dispatchTouchEvent (MotionEvent event) {float x = event. getX (); float y = event. getY (); Log. e (TAG, x = + x +, y = + y); switch (event. getAction () {case MotionEvent. ACTION_DOWN: mLastX = x; mLastY = y; mDownTime = System. currentTimeMillis (); mTmpAngle = 0; // If Quick rolling if (isFling) {// remove the callback removeCallbacks (mFlingRunnable); isFling = false; return true;} break; case MotionEvent. ACTION_MOVE:/*** get the starting angle */float start = getAngle (mLastX, mLastY);/*** get the current angle */float end = getAngle (x, y); // Log. e (TAG, start = + start +, end = + end); // if it is one or four quadrants, end-start is used directly, and the angle values are all positive values if (getQuadrant (x, y) = 1 | getQuadrant (x, y) = 4) {mStartAngle + = end-start; mTmpAngle + = End-start;} else // Second and Third-image restrictions. The color angle value is the pay value {mStartAngle + = start-end; mTmpAngle + = start-end ;} // re-layout requestLayout (); mLastX = x; mLastY = y; break; case MotionEvent. ACTION_UP: // calculation, float anglePerSecond angle of movement per second = mTmpAngle * 1000/(System. currentTimeMillis ()-mDownTime); // Log. e (TAG, anglePrMillionSecond +, mTmpAngel = + // mTmpAngle); // if this value is reached, it is considered as a fast moving if (Math. abs (anglePerSecond)> mFlingableValue &&! IsFling) {// post a task to automatically scroll the post (mFlingRunnable = new AutoFlingRunnable (anglePerSecond); return true ;} // if the current rotation angle exceeds NOCLICK_VALUE, click if (Math. abs (mTmpAngle)> NOCLICK_VALUE) {return true;} break;} return super. dispatchTouchEvent (event );}

OK, the Code is not long ~~ When you go DOWN, record mLastX, mLastY, current time, and reset mTmpAngle to 0. If you are currently rolling automatically, stop this operation.

 

When ACTION_MOVE is performed, mLastX and mLastY get an angle, and then get another scheduling based on the current x and y, constantly changing mStartAngle and re-layout the interface.

Of course, in the getAngle (x, y) method, if the obtained angle is one or four quadrants, end-start is used directly. The angle values are positive values. In the second and third image limits, the end-start angle value is a negative value, so subtract it.

When the value is UP, the angle of movement per second is calculated. If the value reaches the mFlingableValue size, automatic scrolling is required, and post (mFlingRunnable = new AutoFlingRunnable (anglePerSecond) is executed.

If the current rotation angle exceeds NOCLICK_VALUE, click "return true. Why? Because the subview click is triggered in super. dispatchTouchEvent (event);, we directly return.

Let's take a look at some of the methods mentioned above, first look at: getAngle:

 

/*** Calculate the Angle Based on the touch position ** @ param xTouch * @ param yTouch * @ return */private float getAngle (float xTouch, float yTouch) {double x = xTouch-(mRadius/2d); double y = yTouch-(mRadius/2d); return (float) (Math. asin (y/Math. hypot (x, y) * 180/Math. PI );}

For example:

 

Math. sqrt (x * x + y * y) indicates the length of the oblique edge, and multiplied by sin a indicates the length of y;

The opposite is the angle of a: Math. asin (y/Math. hypot (x, y); [hypot is x * x + y * y]

In this way, the moving angle computing will be OK ~~

Different quadrants assume that the start-end value may be negative, so we need to change the order of subtraction, because we need a positive value for the last angle;

Code for determining quadrants:

 

/*** Calculate quadrant based on the current position ** @ param x * @ param y * @ return */private int getQuadrant (float x, float y) {int tmpX = (int) (x-mRadius/2); int tmpY = (int) (y-mRadius/2); if (tmpX> = 0) {return tmpY> = 0? 4: 1;} else {return tmpY> = 0? 3: 2 ;}}

I took the coordinates and got to understand it now ~~~

 

What is left at the end?

See our automatic AutoFlingRunnable and auto-rolling tasks ~~

Post (mFlingRunnable = new AutoFlingRunnable (anglePerSecond );

 

/*** Auto-rolling task ** @ author zhy **/private class AutoFlingRunnable implements Runnable {private float angelPerSecond; public AutoFlingRunnable (float velocity) {this. angelPerSecond = velocity;} public void run () {// if less than 20, stop if (int) Math. abs (angelPerSecond) <20) {isFling = false; return;} isFling = true; // continuously change mStartAngle to scroll, /30 to avoid scrolling too fast mStartAngle + = (angelPerSecond/30); // gradually decrease the value angelPerSecond/= 1.0666F; postDelayed (this, 30 ); // re-layout requestLayout ();}}

The code is short. We pass in the angle value for moving every second, then add mStartAngle according to this value, and then requestLayout (); that is, it automatically scrolls ~~ Of course, you need to roll slowly and stop, so you need

 

// Gradually decrease the value angelPerSecond/= 1.0666F; and when the last movement is slow, we stop:

// If the value is less than 20, stop
If (int) Math. abs (angelPerSecond) <20)
{
IsFling = false;
Return;
}

At this point, all codes have been parsed ~~~ Hey hey ~

 

 

 

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.