Adapter mode practice-restructuring the circle menu of Hongyang's Android Construction Bank
For many developers, cool UI effects are the most attractive to them. Many also learn some well-known UI libraries for these cool effects. The premise of making cool effects is that you must understand the custom View. As a 90-year-old citizen, it is no exception. Especially for the small people who were just in the early stages of development and who felt mysterious and handsome about the custom View, the small people decided to study the custom View and related knowledge points in depth.
Before that, let's take a look at the original version of Yang Shen:
I remember that it was the first snow event in 2014, a little later than in the past. Yang Shu, a colleague of Xiaomin, is a senior R & D engineer who is good at writing UI special effects and is well known in the development field. Recently, Yang shugang released a good circular menu, where each Item is arranged in a ring and can be rotated. Xiao Min decided to implement the effect of imitating Yang Shu, but for Xiao Min at this stage, it would be nice to implement the ring layout. The rotation part is used as the function of the next version, as a custom View exercise.
After google's custom View-related knowledge points, Tom wrote the circular menu Layout View. The Code is as follows:
// Circular menu public class CircleMenuLayout extends ViewGroup {// circular diameter private int mRadius; // default size of the child item in the container private static final float RADIO_DEFAULT_CHILD_DIMENSION = 1/4f; // The padding of the container, regardless of the padding attribute. Use the variable private static final float RADIO_PADDING_LAYOUT = 1/12f if the padding is required. // the padding of the container, ignore the padding attribute. Use the private float mPadding variable if margins are required. // The start angle of the layout is private double mStartAngle = 0. // The text private String [] mItem of the menu item. Texts; // The private int [] mItemImgs icon of the menu item; // The number of menus private int mMenuItemCount; // The Menu layout resource id private int mMenuItemLayoutId = R. layout. vertex; // Click Event interface of MenuItem private OnItemClickListener mOnMenuItemClickListener; public CircleMenuLayout (Context context, AttributeSet attrs) {super (context, attrs); // ignore padding setPadding (0, 0, 0, 0, 0);} // set the icon and text of the menu bar public void setMenuItemIconsAndTexts (Int [] images, String [] texts) {if (images = null & texts = null) {throw new IllegalArgumentException ("set at least one menu item text and image");} mItemImgs = images; mItemTexts = texts; // initialize mMenuCount mMenuItemCount = images = null? Texts. length: images. length; if (images! = Null & texts! = Null) {mMenuItemCount = Math. min (images. length, texts. length);} // build menu item buildMenuItems ();} // build menu item private void buildMenuItems () {// based on user-set parameters, initialize menu item for (int I = 0; I <mMenuItemCount; I ++) {View itemView = inflateMenuView (I); // initialize menu item initMenuItem (itemView, I ); // Add the view to the container addView (itemView);} private View inflateMenuView (final int childIndex) {LayoutInflater mInflater = Layo UtInflater. from (getContext (); View itemView = mInflater. inflate (mMenuItemLayoutId, this, false); itemView. setOnClickListener (new OnClickListener () {@ Override public void onClick (View v) {if (mOnMenuItemClickListener! = Null) {mOnMenuItemClickListener. onClick (v, childIndex) ;}}); return itemView;} private void initMenuItem (View itemView, int childIndex) {ImageView iv = (ImageView) itemView. findViewById (R. id. id_circle_menu_item_image); TextView TV = (TextView) itemView. findViewById (R. id. id_circle_menu_item_text); iv. setVisibility (View. VISIBLE); iv. setImageResource (mItemImgs [childIndex]); TV. setVisibility (View. VISIBLE); TV. setText (mItemTexts [childIndex]);} // you must call public void setMenuItemLayoutId (int mMenuItemLayoutId) {this. mMenuItemLayoutId = mMenuItemLayoutId;} // set the MenuItem Click Event interface public void setOnItemClickListener (OnItemClickListener listener) {this. mOnMenuItemClickListener = listener;} // Code omitted}
The idea of Tom is roughly like this. First, let the user pass the icon and text of the menu item through the setMenuItemIconsAndTexts function, and build the menu item based on these icons and text, the Layout View of menu items is stored by mMenuItemLayoutId. The default value of mMenuItemLayoutId iscircle_menu_item.xml
The xml layout is an ImageView that is displayed on a text control. To customize menu items, Tom also added a setMenuItemLayoutId function to allow users to set the layout of menu items. He hoped that users could Customize various menu styles. After the user sets the relevant data for the menu items, the small people will build and initialize an equal number of menu items based on the number of icons and texts set by the user, add these menu items to the circular menu CircleMenuLayout. Then, a setOnItemClickListener function is added to set the processing interface for users to click menu items, so that menu click events can be customized.
After adding menu items to CircleMenuLayout, You need to measure the size and layout of these menu items. Let's first look at the code for measuring the size, as shown below:
// Set the layout width and height, and policy menu item width and height @ Override protected void onMeasure (int widthMeasureSpec, int heightMeasureSpec) {// measure the size of measureMyself (widthMeasureSpec, heightMeasureSpec ); // measure menu item size measureChildViews ();} private void measureMyself (int widthMeasureSpec, int heightMeasureSpec) {int resWidth = 0; int resHeight = 0; // according to the input parameter, obtain the Measurement Mode and measured value int width = MeasureSpec. getSize (widthMeasureSpec); int widthMod E = MeasureSpec. getMode (widthMeasureSpec); int height = MeasureSpec. getSize (heightMeasureSpec); int heightMode = MeasureSpec. getMode (heightMeasureSpec); // if the width or height of the measurement mode is not precise, 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);} private void measureChildViews () {// 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; makeMeasureSpec = MeasureSpec. makeMeasureSpec (childSize, childMode); child. measure (makeMeasureSpec, makeMeasureSpec);} mPadding = RADIO_PADDING_LAYOUT * mRadius ;}
The code is relatively simple, that is, the size of CircleMenuLayout is measured first, and then the size of each menu item is measured. After obtaining the dimension, the layout step is reached, which is also the core of the entire circular menu. The Code is as follows:
// Layout menu item position @ Override protected void onLayout (boolean changed, int l, int t, int r, int B) {final int childCount = getChildCount (); int left, top; // the size of menu item int itemWidth = (int) (mRadius * RADIO_DEFAULT_CHILD_DIMENSION); // based on the number of menu items, calculates the layout occupation of items. float angleDelay = 360/childCount; // traverses all menu items and sets their positions for (int I = 0; I <childCount; I ++) {final View child = getChildAt (I); if (child. getVisibility () = GONE) {continue;} // start angle of the menu item mStartAngle % = 360; // calculated, float distanceFromCenter = mRadius/2f-itemWidth/2-mPadding; // distanceFromCenter cosa is the left coordinate left = mRadius/2 + (int) of the center of the menu item) math. round (distanceFromCenter * Math. cos (Math. toRadians (mStartAngle) *-1/2f * itemWidth); // distanceFromCenter sina, that is, top = mRadius/2 + (int) Math of the menu item. round (distanceFromCenter * Math. sin (Math. toRadians (mStartAngle) *-1/2f * itemWidth); // layout child view child. layout (left, top, left + itemWidth, top + itemWidth); // superimposed size mStartAngle + = angleDelay ;}}
The onLayout function looks a little complex, but its meaning is to layout all menu items in the form of an arc. The entire circle is 360 degrees. If the angle occupied by each menu item is 60 degrees, the angle of the first menu item is 0 ~ 60, the second menu item angle is 60 ~ 120, and so on. First, calculate the left and top positions of each menu item and calculate the graphical representation of the formula.
The circle in the lower right corner is our menu item, so its left coordinate is mRadius/2 + tmp * coas, and the top coordinate is mRadius/2 + tmp * sina. Tmp is the distanceFromCenter variable in our code. After this step, the first round menu is complete.
Next we will integrate this circular menu.
After creating a project, first add the circular Menu Control to the layout xml. The Code is as follows:
For better display, we added a background image to the previous layer of the circular menu and the book of the circular menu in the layout xml. Set Menu item data and click events in MainActivity. The Code is as follows:
Public class MainActivity extends Activity {private CircleMenuLayout mCircleMenuLayout; // menu title private String [] mItemTexts = new String [] {"security center", "special service", "Investment and Financial Management ", "transfer and remittance", "my account", "Credit Card"}; // menu icon 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}; @ Override protected void onCreate (Bundle savedInstanceState) {super. onCreate (savedInstanceState); setContentView (R. layout. activity_main); // initialize the circular menu mCircleMenuLayout = (CircleMenuLayout) findViewById (R. id. id_menulayout); // sets the menu data item mCircleMenuLayout. setMenuItemIconsAndTexts (mItemImgs, mItemTexts); // set the menu item to click the event mCircleMenuLayout. setOnItemClickListener (new OnItemClickListener () {@ Override public void onClick (View view, int pos) {Toast. makeText (MainActivity. this, mItemTexts [pos], Toast. LENGTH_SHORT ). show ();}});}}
The running effect is shown in the animation above.
Xiaomin came up with a smug word: Cool! At the same time, I am proud of my learning ability. My face is filled with satisfaction and pride. I feel that I have taken a step closer to a senior engineer.
"Isn't this a circular menu written by Yang Shu, and Xiao Min also downloaded it ?" The supervisor preparing for work asked about the UI effect. Tom had to explain the reasons and implementation methods to his supervisor. Tom also emphasized the customizable CircleMenuLayout type and set the layout id of the menu item through the setMenuItemLayoutId function, in this way, the UI Effect of menu items can be customized by users. The supervisor scanned the code of the minor and seemed to be aware of something. So I turned to Yang Shu, who was still studying the code, and briefly introduced the implementation of Xiaomin, yang Shu found the problem after scanning the code.
"Tom, you just said that the user can use the setMenuItemLayoutId function to set the UI effect of the menu item. The problem arises. What is implemented by default in your CircleMenuLayout?circle_menu_item.xml
For example, after loading the menu item layout, the findViewById is used to find the child views in the layout and bind the data. For example, set the icon and text, but this iscircle_menu_item.xml
The specific implementation of this layout. If you set the menu item layoutother_menu_item.xml
And each menu item is changed to a Button. In this case, he must modify the code for initializing the menu item in CircleMenuLayout. Because the layout has changed, the sub-View type in the menu item has also changed, and the data required by the menu has also changed. For example, a menu item does not need icons, but only text. In this way, you need to modify the CircleMenuLayout class once every time you change the menu style, and the interface for setting menu data also needs to be changed. In this way, there is no custom type, and it is a clear violation of the open and closed principles. Repeated modifications to CircleMenuLayout will inevitably introduce various problems ......" Dear Mr Yang, I am very impressed! Xiaomin found the problem, so he asked Yang Shu how to handle it.
"In this case, you should use the Adapter, just like the Adapter in ListView, to allow users to customize the layout, resolution, data binding, and other work of menu items, all you need to know is that each menu item is a View. In this way, the changes are isolated through the Adapter layer. You only rely on the Adapter abstraction. Each user can have different implementations. You only need to measure and layout the circular menu. In this way, we can embrace changes and ensure customization. Of course, you can provide a default Adapter, that is, use yourcircle_menu_item.xml
Layout implementation menu, so that users without customization needs can use this default implementation ." Xiaomin nodded frequently and often said yes. "This is indeed because I have not considered it well before, but it is also lack of experience. I will reconstruct it again ." After discovering the problem, the minor also admitted his shortcomings. The two predecessors watched the minor as studious and accompanied the minor to restructure the code.
Under the guidance of the two predecessors, after less than five minutes of restructuring, the small people's CircleMenuLayout became the following.
// Circular menu public class CircleMenuLayout extends ViewGroup {// field omitted // set Adapter public void setAdapter (ListAdapter mAdapter) {this. mAdapter = mAdapter;} // build menu item private void buildMenuItems () {// initialize menu item for (int I = 0; I <mAdapter. getCount (); I ++) {final View itemView = mAdapter. getView (I, null, this); final int position = I; itemView. setOnClickListener (new OnClickListener () {@ O Verride public void onClick (View v) {if (mOnMenuItemClickListener! = Null) {mOnMenuItemClickListener. onClick (itemView, position) ;}}); // Add the view to the container addView (itemView) ;}@ Override protected void onAttachedToWindow () {if (mAdapter! = Null) {buildMenuItems ();} super. onAttachedToWindow ();} // measurement, layout Code omitted}
The current CircleMenuLayout removes the specific work for parsing xml and initializing menu items, adds an Adapter, after the user sets the Adapter, in the onAttachedToWindow function, call the getCount function of the Adapter to obtain the number of menu items, use the getView function to obtain each View, and add the View of these menu items to the circular menu, the circle menu layout can be further arranged to a specific position.
We can see how CircleMenuLayout is used. First, define an object class MenuItem to store menu item icons and text information. The Code is as follows:
static class MenuItem { public int imageId; public String title; public MenuItem(String title, int resId) { this.title = title; imageId = resId; }}
Then implement an Adapter, which is of the ListAdapter type. We need to load the menu item xml and bind data in getView. The related code is as follows:
Static class CircleMenuAdapter extends BaseAdapter {ListMMenuItems; public CircleMenuAdapter (ListMenuItems) {mMenuItems = menuItems;} // load the menu item layout and initialize each menu @ Override public View getView (final int position, View convertView, ViewGroup parent) {LayoutInflater mInflater = LayoutInflater. from (parent. getContext (); View itemView = mInflater. inflate (R. layout. circle_menu_item, parent, false); initMenuItem (itemView, position); return itemView;} // initialize the menu item private void initMenuItem (View itemView, int position) {// obtain the data item final MenuItem item = getItem (position); ImageView iv = (ImageView) itemView. findViewById (R. id. id_circle_menu_item_image); TextView TV = (TextView) itemView. findViewById (R. id. id_circle_menu_item_text); // data binding iv. setImageResource (item. imageId); TV. setText (item. title);} // The code for retrieving item count is omitted}
This is consistent with the use of Adapter in ListView. It implements functions such as getView and getCount, loads the layout file of each item in getView, and binds data. Finally, the menu View is returned, and the View is added to CircleMenuLayout. The operations in this step were originally placed in CircleMenuLayout and are now isolated by Adapter. In this way, the easily changed parts are abstracted and isolated through the Adapter. Even if the user has thousands of menu item UI effects, the Adapter can easily be used for expansion and implementation, you do not need to modify the code in CircleMenuLayout every time. The CircleMenuLayout layout class provides a circular layout abstraction. It does not need to be concerned about what a subview looks like. It is so easy to isolate changes and embrace changes through the Adapter.
"The original reason is that ListView and RecyclerView use an Adapter to separate the changeable parts and hand them over to the user for processing. In addition, the observer mode decouples the data and UI, so that the View and data are not dependent. A piece of data can act on multiple UIS to cope with the ease of modification of the UI. This is the case !" Summary.
For example, when our product changes and the circular menu needs to be changed to a normal ListView style, we need to do something very simple: to change CircleMenuLayout in the xml layout to ListView, set the Adapter to ListView. The Code is as follows:
Public class MainActivity extends Activity {private ListView mListView; ListMMenuItems = new ArrayList(); @ Overrideprotected void onCreate (Bundle savedInstanceState) {super. onCreate (savedInstanceState); setContentView (R. layout. activity_main); // simulate data mockMenuItems (); mListView = (ListView) findViewById (R. id. id_menulayout); // sets the adapter mListView. setAdapter (new CircleMenuAdapter (mMenuItems); // sets the Click Event mListView. setOnItemClickListener (new OnItemClickListener () {@ Override public void onItemClick (AdapterView
Parent, View view, int position, long id) {Toast. makeText (MainActivity. this, mMenuItems. get (position ). title, Toast. LENGTH_SHORT ). show ();}});}
In this way, the UI replacement is completed, and the cost is very low, and other errors are basically not caused. This is why ListAdapter is used in CircleMenuLayout to ensure compatibility with existing components such as ListView and GridView. Of course, there is no need to redefine an Adapter type,From then on, we can modify the menu Item style at will, ensuring the flexibility of this component !!The following figure shows the effect of replacing it with ListView:
Signature + uz1Le5oaMmbGRxdW87xMe + signature + zbOv18XCpc/CtcTO18m9v77T47Xq19/IpaGjPC9wPg0KPGgyIGlkPQ = "209 conclusion"> 20.9 conclusion
The classic Implementation of the Adapter mode is to integrate incompatible interfaces so that they can cooperate well. However, in actual development, the Adapter mode also has some flexible implementations. For example, the isolated changes in ListView make the entire UI architecture more flexible and can embrace changes. The Adapter mode is widely used in development. Therefore, it is necessary to master the Adapter mode.