03.实例篇:微信主页面模仿——ActionBar和ViewPager的使用(下篇)

来源:互联网 发布:百度站长seo 编辑:程序博客网 时间:2024/06/04 18:49

第一部分:ActionBar重要知识点汇总

1.什么ActionBar

主要用于向用户当前Activity的标题、导航按钮或者其它动作,使用它,统一了样式和更好适应不同的屏幕;
ActionBar 在Android3.0 API 11开始使用,通过Support Library在2.1(API 7)版本及其以上可以使用;
如果仅支持3.0以上设备,导入import android.app.ActionBar
支持2.1以上的import android.support.v7.app.ActionBar

public abstract class ActionBar extends Object : android.app.ActionBar抽象类,不是视图view

2.给Activity添加ActionBar

    两点操作:
    Activity继承ActionBarActivity
    同时,Activity主题设置为<activity android:theme="@style/Theme.AppCompat.Light" ... >(该主题的子类也可以)

3.动态移除ActionBar

    //API 11以上, getActionBar即可
    ActionBar actionBar = getSupportActionBar();
    actionBar.hide();
    
    actionBar.show(); //显示
    
    注意:动态移除或显示,会引发重新布局,如果不需要该操作,可以考虑overlay mode
    设置:windowActionBarOverlay=true

4.ActionBar选项菜单

   ActionBar上面有各种图标和文字按钮,还有个 overflow Button(不够空间或者不重要的,收缩)
   Activity通过onCreateOptionsMenu()方法填充ActionBar的Items;
    
   logo代替icon:默认显示icon,在<application>和<activity>的logo属性设置
    
   在menu res定义所有的Action Items;
   res/menu/main_activity_actions.xml
  <menu xmlns:android="http://schemas.android.com/apk/res/android" >
   
<item android:id="@+id/action_search"
         
android:icon="@drawable/ic_action_search"
         
android:title="@string/action_search"/>
   
<item android:id="@+id/action_compose"
         
android:icon="@drawable/ic_action_compose"
         
android:title="@string/action_compose" />
  </menu> 
    
   @Override
  public boolean onCreateOptionsMenu(Menu menu) {
   
// Inflate the menu items for use in the action bar
   
MenuInflater inflater = getMenuInflater();
    inflater
.inflate(R.menu.main_activity_actions, menu);
   
return super.onCreateOptionsMenu(menu);
  } 

    yourapp:名字自己起的,因为是支持库,系统没有内置showAsAction属性,所以需要自定义空间
    showAsAction:  ifRoom如果有空间,就直接显示在ActionBar上,没有空间,就收缩起来   
    <menu xmlns:android="http://schemas.android.com/apk/res/android"       
        xmlns:yourapp="http://schemas.android.com/apk/res-auto" >
    <item android:id="@+id/action_search"
         
android:icon="@drawable/ic_action_search"
         
android:title="@string/action_search"
         
yourapp:showAsAction="ifRoom"  />
    ...
</menu>
  如果有空间同时显示文字:
  <item yourapp:showAsAction="ifRoom|withText" ... />
    如果想让某个Item一直出现ActionButton,设置为 always, 尽量不用该选项,避免狭窄屏幕问题
    
    Action Item事件:
    Activity首先处理ItemSelected事件,Fragment在后面;
    当Activity不做处理时,应该调用super,以让其他Fragment有机会处理,而不是仅仅返回false
    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
   
// Handle presses on the action bar items
   
switch (item.getItemId()) {
       
case R.id.action_search:
            openSearch
();
           
return true;
       
case R.id.action_compose:
            composeMessage
();
           
return true;
       
default:
           
return super.onOptionsItemSelected(item);
   
}
}

5.使用分割的ActionBar

当手机屏幕狭窄时,在屏幕底部提供单独的bar,显示所有的Action Items
<application> <activity> 中设置uiOptions="splitActionBarWhenNarrow"
支持老版本,使用<meta-data>
<manifest ...>
   
<activity uiOptions="splitActionBarWhenNarrow" ... >
       
<meta-data android:name="android.support.UI_OPTIONS"
                   
android:value="splitActionBarWhenNarrow" />
   
</activity>
</manifest>
通过设置,允许收缩Action Bar(顶部的)
setDisplayShowHomeEnabled(false) and setDisplayShowTitleEnabled(false).

6.向前返回 导航

  Up导航不同于Back,Back由系统按照screen历史记录,up根据应用的层次结构设置;
   (1)让app icon成为Up Button,设置
   ActionBar actionBar = getSupportActionBar();
 actionBar.setDisplayHomeAsUpEnabled(true);
 (2)定义Up Parent Activity:两种方式
  <activity
        android:name="com.example.myfirstapp.DisplayMessageActivity"
       
android:label="@string/title_activity_display_message"
       
android:parentActivityName="com.example.myfirstapp.MainActivity" >
       
<!-- Parent activity meta-data to support API level 7+ -->
       
<meta-data
           
android:name="android.support.PARENT_ACTIVITY"
           
android:value="com.example.myfirstapp.MainActivity" />
 
</activity>
    
   或者重写,适合每个页面返回不一样的Activity
    getSupportParentActivityIntent() 
    onCreateSupportNavigateUpTaskStack()
   

7.添加Action View: Action Button的替代:一定注意使用yourapp,否则低版本的应用无法显示;

   通过actionLayout或者actionViewClass定义
   添加一个搜索框
    <?xml version="1.0" encoding="utf-8"?>
  <menu xmlns:android="http://schemas.android.com/apk/res/android"
     
xmlns:yourapp="http://schemas.android.com/apk/res-auto" >
   
<item android:id="@+id/action_search"
         
android:title="@string/action_search"
         
android:icon="@drawable/ic_action_search"
         
yourapp:showAsAction="ifRoom|collapseActionView"
         
yourapp:actionViewClass="android.support.v7.widget.SearchView" />
</menu>
  注意:collapseActionView 必须添加,说明该view被收缩
  获取ActionView:
  @Override
  public boolean onCreateOptionsMenu(Menu menu) {
    getMenuInflater
().inflate(R.menu.main_activity_actions, menu);
   
MenuItem searchItem = menu.findItem(R.id.action_search);
   
SearchView searchView = (SearchView) MenuItemCompat.getActionView(searchItem);
   
// Configure the search info and add any event listeners
   
...
   
return super.onCreateOptionsMenu(menu);
}
  处理收缩的Action View:
  @Override
public boolean onCreateOptionsMenu(Menu menu) {
    getMenuInflater
().inflate(R.menu.options, menu);
   
MenuItem menuItem = menu.findItem(R.id.actionItem);
   
...

   
// When using the support library, the setOnActionExpandListener() method is
   
// static and accepts the MenuItem object as an argument
   
MenuItemCompat.setOnActionExpandListener(menuItem, new OnActionExpandListener() {
       
@Override
       
public boolean onMenuItemActionCollapse(MenuItem item) {
           
// Do something when collapsed
           
return true;  // Return true to collapse action view
       
}

       
@Override
       
public boolean onMenuItemActionExpand(MenuItem item) {
           
// Do something when expanded
           
return true;  // Return true to expand action view
       
}
   
});
}

8.添加 Action Provider

   和Action View类似,但是可以完全控制Action的行为,可以定义每个按钮的子菜单
   使用内置的ActionProvider或者继承ActionProvider
   每个ActionProvider,可以定义自己的行为,无须在Activity中定义onOptionsItemsSelected,如果定义,不要返回true,返回false,以便于其它ActionProvider处理;
   <?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
     
xmlns:yourapp="http://schemas.android.com/apk/res-auto" >
   
<item android:id="@+id/action_share"
         
android:title="@string/share"
         
yourapp:showAsAction="ifRoom"
         
yourapp:actionProviderClass="android.support.v7.widget.ShareActionProvider"
         
/>
    ...
</menu>
private ShareActionProvider mShareActionProvider;

@Override
public boolean onCreateOptionsMenu(Menu menu) {
    getMenuInflater
().inflate(R.menu.main_activity_actions, menu);

   
// Set up ShareActionProvider's default share intent
   
MenuItem shareItem = menu.findItem(R.id.action_share);
    mShareActionProvider
= (ShareActionProvider)
           
MenuItemCompat.getActionProvider(shareItem);
    mShareActionProvider
.setShareIntent(getDefaultIntent());

   
return super.onCreateOptionsMenu(menu);
}

/** Defines a default (dummy) share intent to initialize the action provider.
  * However, as soon as the actual content to be used in the intent
  * is known or changes, you must update the share intent by again calling
  * mShareActionProvider.setShareIntent()
  */
private Intent getDefaultIntent() {
   
Intent intent = new Intent(Intent.ACTION_SEND);
    intent
.setType("image/*");
   
return intent;
}

9.创建定制的ActionProvider

  方便重用和管理ActionItem的行为,不需要在Fragment或者Activity中处理;
    继承ActionProvider,实现回调方法即可:
    ActionProvider(Context context): 构造器,会传递一个context,可以保存到一个字段中,方便使用
    onCreateActionView(MenuItem): 对于每一个的MenuItem,创建ActionView
    public View onCreateActionView(MenuItem forItem) {
    // Inflate the action view to be shown on the action bar.
   
LayoutInflater layoutInflater = LayoutInflater.from(mContext);
   
View view = layoutInflater.inflate(R.layout.action_provider, null);
   
ImageButton button = (ImageButton) view.findViewById(R.id.button);
    button
.setOnClickListener(new View.OnClickListener() {
       
@Override
       
public void onClick(View v) {
           
// Do something...
       
}
   
});
   
return view;
}
    onPerformDefaultAction():当action overflow的菜单项被选择,调用执行默认的Action
     如果Action View有子菜单,onPrepareSubMenu()调用,不会调用该方法;
     如果在Activity或者Fragment中已经处理 onOptionsItemSelected() (返回true的话),也不会调用;

10.添加导航的Tab

    布局中必须有一个Id的ViewGroup,绑定Fragment和Tab
    如果每个Tab内容填充整个屏幕,可以使用内置的布局,通过android.R.id.content 引用
    基本过程:
    实现ActionBar.TabListener接口,响应用户点击切换tab事件;【滑动切换,用ViewPager】
    动态添加每个Tab: 初始化ActionBar.Tab, setTabListener, setText,setIcon,addTab
     getSelectedNavigationIndex():获取tab索引位置
    注意:系统调用commit,自己不用调用,调用将失败;不能够将tab压入stack中
    public static class TabListener<T extends Fragment> implements ActionBar.TabListener {
    private Fragment mFragment;
   
private final Activity mActivity;
   
private final String mTag;
   
private final Class<T> mClass;

   
/** Constructor used each time a new tab is created.
      * @param activity  The host Activity, used to instantiate the fragment
      * @param tag  The identifier tag for the fragment
      * @param clz  The fragment's Class, used to instantiate the fragment
      */
   
public TabListener(Activity activity, String tag, Class<T> clz) {
        mActivity
= activity;
        mTag
= tag;
        mClass
= clz;
   
}

   
/* The following are each of the ActionBar.TabListener callbacks */

   
public void onTabSelected(Tab tab, FragmentTransaction ft) {
       
// Check if the fragment is already initialized
       
if (mFragment == null) {
           
// If not, instantiate and add it to the activity
            mFragment
= Fragment.instantiate(mActivity, mClass.getName());
            ft
.add(android.R.id.content, mFragment, mTag);
       
} else {
           
// If it exists, simply attach it in order to show it
            ft
.attach(mFragment);
       
}
   
}

   
public void onTabUnselected(Tab tab, FragmentTransaction ft) {
       
if (mFragment != null) {
           
// Detach the fragment, because another one is being attached
            ft
.detach(mFragment);
       
}
   
}

   
public void onTabReselected(Tab tab, FragmentTransaction ft) {
       
// User selected the already selected tab. Usually do nothing.
   
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
   
super.onCreate(savedInstanceState);
   
// Notice that setContentView() is not used, because we use the root
   
// android.R.id.content as the container for each fragment

   
// setup action bar for tabs
   
ActionBar actionBar = getSupportActionBar();
    actionBar
.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
    actionBar
.setDisplayShowTitleEnabled(false);

   
Tab tab = actionBar.newTab()
                       
.setText(R.string.artist)
                       
.setTabListener(new TabListener<ArtistFragment>(
                               
this, "artist", ArtistFragment.class));
    actionBar
.addTab(tab);

    tab
= actionBar.newTab()
                   
.setText(R.string.album)
                   
.setTabListener(new TabListener<AlbumFragment>(
                           
this, "album", AlbumFragment.class));
    actionBar
.addTab(tab);
}

11.添加下拉列表式导航

     当导航的内容不是经常切换的时候,使用下拉列表式(spinner),如果内容经常切换,使用tab
     创建下拉列表的基本过程:
  • SpinnerAdapter:提供数据项和布局视图
  • 实现ActionBar.onNavigationListener接口,监听列表项选择事件
  • 在Activity的onCreate方法,设置ActionBar的mode,setNavigationMode(NAVIGATION_MODE_LIST).
  • actionBar.setListNavigationCallbacks(mSpinnerAdapter, mNavigationCallback);
  • SpinnerAdapter mSpinnerAdapter = ArrayAdapter.createFromResource(this,
            R
    .array.action_list, android.R.layout.simple_spinner_dropdown_item);
    mOnNavigationListener = new OnNavigationListener() {
     
    // Get the same strings provided for the drop-down's ArrayAdapter
     
    String[] strings = getResources().getStringArray(R.array.action_list);

     
    @Override
     
    public boolean onNavigationItemSelected(int position, long itemId) {
       
    // Create new fragment from our own Fragment class
       
    ListContentFragment newFragment = new ListContentFragment();
       
    FragmentTransaction ft = getSupportFragmentManager().beginTransaction();

       
    // Replace whatever is in the fragment container with this fragment
       
    // and give the fragment a tag name equal to the string at the position
       
    // selected
        ft
    .replace(R.id.fragment_container, newFragment, strings[position]);

       
    // Apply changes
        ft
    .commit();
       
    return true;
     
    }
    };
    public class ListContentFragment extends Fragment {
       
    private String mText;

       
    @Override
       
    public void onAttach(Activity activity) {
         
    // This is the first callback received; here we can set the text for
         
    // the fragment as defined by the tag specified during the fragment
         
    // transaction
         
    super.onAttach(activity);
          mText
    = getTag();
       
    }

       
    @Override
       
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
               
    Bundle savedInstanceState) {
           
    // This is called to define the layout for the fragment;
           
    // we just create a TextView and set its text to be the fragment tag
           
    TextView text = new TextView(getActivity());
            text
    .setText(mText);
           
    return text;
       
    }
    }

第二部分:ViewPager组件的使用

1.public class ViewPager extends android.view.ViewGroup 

   android.support.v4.view.ViewPager
   布局管理器,实现通过滑动切换页面,提供接口PagerAdapter的实现产生Page
   ViewPager经常与Fragment联合使用,并提供标准的Adapter:FragmentPagerAdapter和FragmentStatePagerAdapter

2.public abstract class PagerAdapter  extends Object  [android.support.v4.view.PagerAdapter]

   抽象类,提供基本接口,产生ViewPager需要的page,子类必须实现抽象方法或者重写:
   Object instantiateItem(ViewGroup container, int position):创建给定位置的视图Page
   void destroyItem(ViewGroup container, int position, Object object):移除给定位置的Page
   abstract boolean isViewFromObject(View view, Object object):判断Page和返回的对象是否相等,return view == object
   abstract int getCount():返回可用的Pager数目
    
   ViewPager关联Page到一个Object,而不是视图View,调用的过程:
   startUpdate(ViewGroup):表明即将开始更新页面
   instantiateItem(ViewGroup, int) and/or destroyItem(ViewGroup, int, Object):多次调用
   finishUpdate(ViewGroup):结束更新,返回对象;
   每当Adapter发生改变,应该 notifyDataSetChanged() ,通知更新;

   两个直接抽象子类:FragmentPagerAdapter和FragmentStatePagerAdapter
   FragmentPagerAdapter:实现了PagerAdapter,并且返回的每个Page是Fragment,适合静态的一定量的页面,对于大量的页面集合,使用FragmentStatePagerAdapter
   只需要实现的getItem(int)和getCount()方法接口,同时ViewPager必须有一个有效的Id
   FragmentStatePagerAdapter:返回Fragment,同时处理保存和恢复Fragment的状态,适合保存大量的Page,工作方式类似于ListView,当Fragment不可见,将可能完全销毁,仅仅保存状态,相对于前面的Adapter,使用更少的内存;
   实例代码:
  •     public class FragmentPagerSupport extends FragmentActivity {
        static final int NUM_ITEMS = 10;

       
    MyAdapter mAdapter;

       
    ViewPager mPager;

       
    @Override
       
    protected void onCreate(Bundle savedInstanceState) {
           
    super.onCreate(savedInstanceState);
            setContentView
    (R.layout.fragment_pager);

            mAdapter
    = new MyAdapter(getSupportFragmentManager());

            mPager
    = (ViewPager)findViewById(R.id.pager);
            mPager
    .setAdapter(mAdapter);

           
    // Watch for button clicks.
           
    Button button = (Button)findViewById(R.id.goto_first);
            button
    .setOnClickListener(new OnClickListener() {
               
    public void onClick(View v) {
                    mPager
    .setCurrentItem(0);
               
    }
           
    });
            button
    = (Button)findViewById(R.id.goto_last);
            button
    .setOnClickListener(new OnClickListener() {
               
    public void onClick(View v) {
                    mPager
    .setCurrentItem(NUM_ITEMS-1);
               
    }
           
    });
       
    }

       
    public static class MyAdapter extends FragmentPagerAdapter {
           
    public MyAdapter(FragmentManager fm) {
               
    super(fm);
           
    }

           
    @Override
           
    public int getCount() {
               
    return NUM_ITEMS;
           
    }

           
    @Override
           
    public Fragment getItem(int position) {
               
    return ArrayListFragment.newInstance(position);
           
    }
       
    }

       
    public static class ArrayListFragment extends ListFragment {
           
    int mNum;

           
    /**
             * Create a new instance of CountingFragment, providing "num"
             * as an argument.
             */
           
    static ArrayListFragment newInstance(int num) {
               
    ArrayListFragment f = new ArrayListFragment();

               
    // Supply num input as an argument.
               
    Bundle args = new Bundle();
                args
    .putInt("num", num);
                f
    .setArguments(args);

               
    return f;
           
    }

           
    /**
             * When creating, retrieve this instance's number from its arguments.
             */
           
    @Override
           
    public void onCreate(Bundle savedInstanceState) {
               
    super.onCreate(savedInstanceState);
                mNum
    = getArguments() != null ? getArguments().getInt("num") : 1;
           
    }

           
    /**
             * The Fragment's UI is just a simple text view showing its
             * instance number.
             */
           
    @Override
           
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                   
    Bundle savedInstanceState) {
               
    View v = inflater.inflate(R.layout.fragment_pager_list, container, false);
               
    View tv = v.findViewById(R.id.text);
               
    ((TextView)tv).setText("Fragment #" + mNum);
               
    return v;
           
    }

           
    @Override
           
    public void onActivityCreated(Bundle savedInstanceState) {
               
    super.onActivityCreated(savedInstanceState);
                setListAdapter
    (new ArrayAdapter<String>(getActivity(),
                        android
    .R.layout.simple_list_item_1, Cheeses.sCheeseStrings));
           
    }

           
    @Override
           
    public void onListItemClick(ListView l, View v, int position, long id) {
               
    Log.i("FragmentList", "Item clicked: " + id);
           
    }
       
    }
    }

    The R.layout.fragment_pager resource of the top-level fragment is:

    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
           
    android:orientation="vertical" android:padding="4dip"
           
    android:gravity="center_horizontal"
           
    android:layout_width="match_parent" android:layout_height="match_parent">

       
    <android.support.v4.view.ViewPager
               
    android:id="@+id/pager"
               
    android:layout_width="match_parent"
               
    android:layout_height="0px"
               
    android:layout_weight="1">
       
    </android.support.v4.view.ViewPager>

       
    <LinearLayout android:orientation="horizontal"
               
    android:gravity="center" android:measureWithLargestChild="true"
               
    android:layout_width="match_parent" android:layout_height="wrap_content"
               
    android:layout_weight="0">
           
    <Button android:id="@+id/goto_first"
               
    android:layout_width="wrap_content" android:layout_height="wrap_content"
               
    android:text="@string/first">
           
    </Button>
           
    <Button android:id="@+id/goto_last"
               
    android:layout_width="wrap_content" android:layout_height="wrap_content"
               
    android:text="@string/last">
           
    </Button>
       
    </LinearLayout>
    </LinearLayout>

    The R.layout.fragment_pager_list resource containing each individual fragment's layout is:

    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
       
    android:orientation="vertical"
       
    android:layout_width="match_parent"
       
    android:layout_height="match_parent"
       
    android:background="@android:drawable/gallery_thumb">

       
    <TextView android:id="@+id/text"
           
    android:layout_width="match_parent" android:layout_height="wrap_content"
           
    android:gravity="center_vertical|center_horizontal"
           
    android:textAppearance="?android:attr/textAppearanceMedium"
           
    android:text="@string/hello_world"/>

       
    <!-- The frame layout is here since we will be showing either
        the empty view or the list view.  -->
       
    <FrameLayout
           
    android:layout_width="match_parent"
           
    android:layout_height="0dip"
           
    android:layout_weight="1" >
           
    <!-- Here is the list. Since we are using a ListActivity, we
                 have to call it "@android:id/list" so ListActivity will
                 find it -->
           
    <ListView android:id="@android:id/list"
               
    android:layout_width="match_parent"
               
    android:layout_height="match_parent"
               
    android:drawSelectorOnTop="false"/>

           
    <!-- Here is the view to show if the list is emtpy -->
           
    <TextView android:id="@android:id/empty"
               
    android:layout_width="match_parent"
               
    android:layout_height="match_parent"
               
    android:textAppearance="?android:attr/textAppearanceMedium"
               
    android:text="No items."/>

       
    </FrameLayout>

    </LinearLayout>
    

3.使用方法,经常与Tab ActionBar结合,支持点击和滑动换页,具体见 csdn博客android微信实例开发

   使用要点:【关键是Tab点击事件和ViewPager滑动选择事件联合起来】
   XML或代码定义ViewPager,但是必须设置有效的id;
    
   设置ActionBar的tab导航模式;
   实现ActionBar.TabListener:选择事件改变当前的ViewPager的页面  
    
   实现FragmentPagerAdapter,和Fragment对象;
   实现ViewPager.OnPagerChangeListener:滑动事件改变当前的ViewPager页面和Tab选择情况
    
   public static interface  ActionBar.TabListener
           abstract void onTabReselected(ActionBar.Tab tab, FragmentTransaction ft)
           abstract void onTabSelected(ActionBar.Tab tab, FragmentTransaction ft)
           abstract void onTabUnselected(ActionBar.Tab tab, FragmentTransaction ft)
   
 public static interface ViewPager.OnPageChangeListener:
            abstract void onPageScrollStateChanged(int state) :
                   当scroll滚动的状态发生改变时,可以发现用户什么时候开始拖动、正在拖动设置中、以及完成拖动设置好页面;
                   三种状态:SCROLL_STATE_IDLE(0)、SCROLL_STATE_DRAGGING(1) 、SCROLL_STATE_SETTLING(2)
            public abstract void onPageScrolled (int position, float positionOffset, int positionOffsetPixels):
                   当前的页面被拖动时调用,position当前拖动的页面index,offset不为0,则position+1可见,取值范围[0,1)
            
            public abstract void onPageSelected (int position):
                   当一个新的已经被选择后,调用(动画不必完成)    

            刚刚启动ViewPager时,会调用一次onPageScrolled方法,当滑动切换时,上述三个方法的调用顺序是:
开始点击滑动时,调用onPageScrollStateChanged一次(此时状态为SCROLL_STATE_DRAGGING),然后一直调用onPageScrolled计算滑动的位置(多次调用),当你放手,结束滑动,调用onPageScrollStateChanged一次(此时状态为SCROLL_STATE_SETTLING),然后调用onPageSelected一次(确定选中的Page),接着还要调用onPageScrolled多次,滑动到指定的Page,然后再次调用onPageScrollStateChanged一次(此时状态为SCROLL_STATE_IDLE),具体见图,当滑动“聊天”到“发现”页面时,截图;

   
转载请附上本页链接:来自CSDN博客——MePlusPlus(Me++)的专栏 (http://blog.csdn.net/meplusplus)
欢迎留言交流。

0 0
原创粉丝点击