• Android 高级UI组件(二)


    1、ExpandableListView

    显示垂直滚动两级列表的条目,只允许两个层次

    整体思路:

    要给ExpandableListView设置适配器,那么必须先设置数据源。

    数据源,就是此处的适配器类ExpandableAdapter,需要重写里面的几个方法

    数据源中用到了自定义的View布局,此时根据自己的需求来设置组和子项的布局样式,getChildView()和getGroupView()方法设置自定义布局

        <ExpandableListView
            android:id="@+id/expand"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"/>
            expand.setAdapter(new MyExpandableAdapter());
            expand.setOnGroupClickListener(new ExpandableListView.OnGroupClickListener() {
                @Override
                public boolean onGroupClick(ExpandableListView expandableListView, View view, int i, long l) {
    
                    return false;
                }
            });
            expand.setOnChildClickListener(new ExpandableListView.OnChildClickListener() {
                @Override
                public boolean onChildClick(ExpandableListView expandableListView, View view, int i, int i1, long l) {
                    Toast.makeText(MainActivity.this, childs[i][i1].toString(), Toast.LENGTH_SHORT).show();
                    return false;
                }
            });
    
            expand.setOnGroupExpandListener(new ExpandableListView.OnGroupExpandListener() {
                @Override
                public void onGroupExpand(int groupPosition) {
    
                    // 保证每次只展开一组
                    int count = expand.getExpandableListAdapter().getGroupCount();
    
                    for (int i = 0; i < count; i++) {
    
                        if (groupPosition != i) {
    
                            expand.collapseGroup(i);
                        }
                    }
                }
            });
    //适配器
        class MyExpandableAdapter extends BaseExpandableListAdapter {
    
            @Override
            public int getGroupCount() {
                return groups.length;
            }
    
            @Override
            public int getChildrenCount(int groupPosition) {
                return childs[groupPosition].length;
            }
    
            @Override
            public Object getGroup(int groupPosition) {
                return groups[groupPosition];
            }
    
            @Override
            public Object getChild(int groupPosition, int childPostion) {
                return childs[groupPosition][childPostion];
            }
    
            @Override
            public long getGroupId(int groupPosition) {
                return groupPosition;
            }
    
            @Override
            public long getChildId(int groupPosition, int childPostion) {
                return childPostion;
            }
    
            @Override
            public boolean hasStableIds() {
                return false;
            }
    
            @Override
            public View getGroupView(int groupPosition, boolean isExpanded, View convertView, ViewGroup parent) {
                if (convertView == null) {
                    convertView = getLayoutInflater().inflate(R.layout.group_layout, null);
                }
                ImageView ime = (ImageView) convertView.findViewById(R.id.im_ex);
                TextView tve = (TextView) convertView.findViewById(R.id.tv_ex);
                tve.setText(groups[groupPosition]);
                return convertView;
            }
    
            @Override
            public View getChildView(int groupPosition, int childPostion, boolean isLastChild, View convertView, ViewGroup viewGroup) {
                if (convertView == null) {
                    convertView = getLayoutInflater().inflate(R.layout.child_layout, null);
                }
                ImageView imec = (ImageView) convertView.findViewById(R.id.im_exc);
                TextView tvec = (TextView) convertView.findViewById(R.id.tv_exc);
                tvec.setText(childs[groupPosition][childPostion]);
                return convertView;
            }
    
            //二级菜单是否能被选中
            @Override
            public boolean isChildSelectable(int groupPosition, int childPostion) {
    
                return true;
            }
        }
    group_layout.xml
    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center_vertical">
    
        <ImageView
            android:id="@+id/im_ex"
            android:layout_width="64dp"
            android:layout_height="64dp"
            android:src="@drawable/pic1" />
    
        <TextView
            android:id="@+id/tv_ex"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="not Data"
            android:textSize="18sp"/>
    
    </LinearLayout>

     2、ImageSwitcher

    ImageSwitcher是Android中控制图片展示效果的一个控件,如:幻灯片效果,粗略的理解就是ImageView的选择器

    原理:ImageSwitcher有两个子View:ImageView,当左右滑动的时候,就在这个两个ImageView之间来回切换来显示图片

    既然有两个ImageView,我们要创建两个ImageView给ImageSwitcher,创建ImageSwitcher是通过工厂来实现的:ViewFactory

        <ImageSwitcher
            android:id="@+id/imgs"
            android:layout_width="match_parent"
            android:layout_height="match_parent"/>
    public class MainActivity extends Activity implements ViewSwitcher.ViewFactory, View.OnTouchListener {
    
        private ImageSwitcher imgs;
        private int[] images = {R.drawable.ic_welcome, R.drawable.iv_welcome, R.drawable.iv_welcome1, R.drawable.iv_welcome2};
        private int index = 0;
    
        float startX = 0.0f;
        float endX = 0.0f;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            imgs = (ImageSwitcher) findViewById(R.id.imgs);
            imgs.setFactory(this);
            imgs.setOnTouchListener(this);
    
        }
    
        @Override
        public View makeView() {
            ImageView imageView = new ImageView(this);
            imageView.setImageResource(images[0]);
            return imageView;
        }
    
        //触屏事件
        @Override
        public boolean onTouch(View view, MotionEvent motionEvent) {
    
            //获取当前事件的动作
            int action = motionEvent.getAction();
            //按住屏幕
            if (action == MotionEvent.ACTION_DOWN) {
                startX = motionEvent.getX();
                return true;
            }
            if (action == MotionEvent.ACTION_UP) {
                endX = motionEvent.getX();
                if (startX - endX > 20) {
                    index = index + 1 < images.length ? ++index : 0;
                    //添加动画
                    imgs.setInAnimation(this, android.R.anim.fade_in);
                    imgs.setInAnimation(this, android.R.anim.fade_out);
                } else if (endX - startX > 20) {
                    index = index - 1 >= 0 ? --index : images.length - 1;
                    imgs.setInAnimation(this, android.R.anim.fade_in);
                    imgs.setInAnimation(this, android.R.anim.fade_out);
                }
                imgs.setImageResource(images[index]);
            }
            return true;
        }
    
    }

    3、Menu 

    创建选项菜单的步骤:

    覆盖Activity的onCreateOptionMenu(Menu menu)方法,当菜单第一次北大开始调用

    调用Menu的add()方法添加菜单项(MenuItem),同时可以调用MenuItem的setIcon()方法来为菜单项设置图标

    当菜单项被选择是,覆盖Activity的onOptionsItemSelected(MenuItem item)响应事件

    @Override
        public boolean onCreateOptionsMenu(Menu menu) {
            //方法1:直接加载menu布局,推荐使用
            getMenuInflater().inflate(R.menu.menu_main, menu);
    
        }
    
            @Override
        public boolean onOptionsItemSelected(MenuItem item) {
            int id = item.getItemId();
            if (id == R.id.recycler) {
                RecyclerActivity.openActivity(MainActivity.this);
            } else if (id == R.id.mySql) {
                MySqlActivity.openActivity(MainActivity.this);
            }
            return super.onOptionsItemSelected(item);
        }

    menu_main.xml
    <?xml version="1.0" encoding="utf-8"?>
    <menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">
    <item
        android:id="@+id/recycler"
        android:title="Recycler"
        app:showAsAction="never" />
    <item
        android:id="@+id/mySql"
        android:title="mySql"
        app:showAsAction="never" />
    </menu> 
        @Override
        public boolean onCreateOptionsMenu(Menu menu) {
    
            //方法2:单个添加
            //添加菜单项(参数:组ID没当前选项ID,排序,标题)
            menu.add(0, 100, 1, "设置");
            menu.add(0, 200, 2, "开始");
            menu.add(0, 300, 3, "关闭");
            return true;
        }
    
            @Override
        public boolean onOptionsItemSelected(MenuItem item) {
            int id = item.getItemId();
            if (id == 100) {
                Toast.makeText(this,"设置",Toast.LENGTH_SHORT).show();
            } 
            return super.onOptionsItemSelected(item);
        }

    Context menu

    顾名思义,与长下文有关,操作室需要长时间按住某个东西不放,出现菜单。

    覆盖Activity的onCreateContextMenu(Menu menu)方法调用Menu的add()方法添加菜单项(MenuItem)

    覆盖Activity的onContextItemSelected(MenuItem item)来响应事件

    调用registerForContextMenu()方法来为视图注册上下文菜单。

    public class MainActivity extends Activity {
        private TextView text;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            text = (TextView) findViewById(R.id.text);
            //注册上下文菜单到text组件
            registerForContextMenu(text);
    
        }
    
    
        //创建上下文菜单
        @Override
        public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
            super.onCreateContextMenu(menu, v, menuInfo);
            getMenuInflater().inflate(R.menu.menu_main, menu);
        }
    
        //点击事件
        @Override
        public boolean onContextItemSelected(MenuItem item) {
            int id = item.getItemId();
            if (id == R.id.recycler) {
                text.setTextColor(Color.BLUE);
            } else {
                text.setTextColor(Color.RED);
            }
            return super.onContextItemSelected(item);
        }
    }

    4.SearchView

    SearchView是Android原生的搜索框控件,它提供了一个用户界面,用于用户搜索查询。 
    SearchView默认是展示一个search的icon,点击icon展开搜索框,如果你想让搜索框默认就展开,可以通过setIconifiedByDefault(false);实现。

    SearchView属性

    属性名称 相关方法 描述
    android:iconifiedByDefault setIconifiedByDefault(boolean) 设置搜索图标是否显示在搜索框内
    android:imeOptions setImeOptions(int) 设置输入法搜索选项字段,默认是搜索,可以是:下一页、发送、完成等
    android:inputType setInputType(int) 设置输入类型
    android:maxWidth setMaxWidth(int) 设置最大宽度
    android:queryHint setQueryHint(CharSequence) 设置查询提示字符串

     

    使用:

        <SearchView
            android:id="@+id/searchView"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:iconifiedByDefault="false"
            android:queryHint="请输入搜索内容" />
            // 设置搜索文本监听
            mSearchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
                // 当点击搜索按钮时触发该方法
                @Override
                public boolean onQueryTextSubmit(String query) {
                    //操作
                    return true;
                }
    
                // 当搜索内容改变时触发该方法
                @Override
                public boolean onQueryTextChange(String newText) {
                  
                    return false;
                }
            });

    设置SearchView

    mSearchView = (SearchView) findViewById(R.id.search);
            /**
             * 默认情况下, search widget是"iconified“的,只是用一个图标 来表示它(一个放大镜),
             * 当用户按下它的时候才显示search box . 你可以调用setIconifiedByDefault(false)让search
             * box默认都被显示。 你也可以调用setIconified()让它以iconified“的形式显示。
             */
            mSearchView.setIconifiedByDefault(true);
            /**
             * 默认情况下是没提交搜索的按钮,所以用户必须在键盘上按下"enter"键来提交搜索.你可以同过setSubmitButtonEnabled(
             * true)来添加一个提交按钮("submit" button)
             * 设置true后,右边会出现一个箭头按钮。如果用户没有输入,就不会触发提交(submit)事件
             */
            mSearchView.setSubmitButtonEnabled(true);
            /**
             * 初始是否已经是展开的状态
             * 写上此句后searchView初始展开的,也就是是可以点击输入的状态,如果不写,那么就需要点击下放大镜,才能展开出现输入框
             */
            mSearchView.onActionViewExpanded();
            // 设置search view的背景色
            mSearchView.setBackgroundColor(0x22ff00ff);
            /**
             * 默认情况下, search widget是"iconified“的,只是用一个图标 来表示它(一个放大镜),
             * 当用户按下它的时候才显示search box . 你可以调用setIconifiedByDefault(false)让search
             * box默认都被显示。 你也可以调用setIconified()让它以iconified“的形式显示。
             */
            mSearchView.setIconifiedByDefault(true);

    配置监听器

    mSearchView.setOnQueryTextListener(new OnQueryTextListener() {
    
                private String TAG = getClass().getSimpleName();
    
                /*
                 * 在输入时触发的方法,当字符真正显示到searchView中才触发,像是拼音,在输入法组词的时候不会触发
                 */
                @Override
                public boolean onQueryTextChange(String queryText) {
                    Log.d(TAG, "onQueryTextChange = " + queryText);
                    return true;
                }
    
                /*
                 * 输入完成后,提交时触发的方法,一般情况是点击输入法中的搜索按钮才会触发。表示现在正式提交了
                 */
                @Override
                public boolean onQueryTextSubmit(String queryText) {
                    Log.d(TAG, "onQueryTextSubmit = " + queryText);
    
                    if (mSearchView != null) {
                        // 得到输入管理对象
                        InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
                        if (imm != null) {
                            // 这将让键盘在所有的情况下都被隐藏,但是一般我们在点击搜索按钮后,输入法都会乖乖的自动隐藏的。
                            imm.hideSoftInputFromWindow(mSearchView.getWindowToken(), 0); // 输入法如果是显示状态,那么就隐藏输入法
                        }
                        mSearchView.clearFocus(); // 不获取焦点
                    }
                    return true;
                }
            });

    在ActionBar上使用SearchView

    <menu xmlns:android="http://schemas.android.com/apk/res/android">
        <item android:id="@+id/search"
              android:title="@string/search"
              android:icon="@drawable/ic_menu_search"
              android:showAsAction="collapseActionView|ifRoom"
              android:actionViewClass="android.widget.SearchView" />
    </menu>
        public boolean onCreateOptionsMenu(Menu menu) {
    
            super.onCreateOptionsMenu(menu);
            getMenuInflater().inflate(R.menu.search, menu);
    
            MenuItem search = menu.findItem(R.id.action_search);
            /**
             * 初始是否已经是展开的状态
             * 写上此句后searchView初始展开的,也就是是可以点击输入的状态,如果不写,那么就需要点击下放大镜,才能展开出现输入框
             */
            search.collapseActionView();
            SearchView searchview = (SearchView) MenuItemCompat.getActionView(search);
            searchview.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
                @Override
                public boolean onQueryTextSubmit(String query) {
                    lvJxwh.requestFocus();
                    srch_bt = query;
                    request();
                    return true;
                }
    
                @Override
                public boolean onQueryTextChange(String newText) {
                    return false;
                }
            });
    
            return true;
        }

    5、弹出式菜单

    弹出菜单是停靠在一个View上的模式菜单。如果View对象下方有空加,那么弹出菜单将显示在停靠对象的下方,否则会显示在上方。

    public class MainActivity extends Activity {
        private Button btn;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            btn = (Button) findViewById(R.id.btn);
    
            btn.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    //创建弹出式菜单
                    PopupMenu popupMenu = new PopupMenu(MainActivity.this, view);
                    MenuInflater inflater = popupMenu.getMenuInflater();
                    inflater.inflate(R.menu.menu_main, popupMenu.getMenu());
                    popupMenu.show();
    
                    //设置点击事件
                    popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
                        @Override
                        public boolean onMenuItemClick(MenuItem menuItem) {
                            switch (menuItem.getItemId()) {
                                case R.id.recycler:
                                    Toast.makeText(MainActivity.this, "recycler", Toast.LENGTH_SHORT).show();
                                    break;
                                case R.id.mySql:
                                    Toast.makeText(MainActivity.this, "mySql", Toast.LENGTH_SHORT).show();
                                    break;
                            }
                            return false;
                        }
                    });
                }
            });
    
        }
    
    }

    6、ViewPager

    VeiwPager 是一个ViewGroup,可以包含很多的View,当手指在屏幕上左右滑动的时候,可以切换界面。可以做很多事情,从最简单的导航,到页面菜单等。用法与ListView类似,也需要一个适配器,就是PagerAdapter。

    ViewPager一般与Fragment结合使用比较方便。

    ViewPager+Fragment可以做出多页面滑动的效果,让我们的应用程序界面操作起来更加灵活

    ViewPager滑动事件讲解:

    首先ViewPager在处理滑动事件时要用到OnPageChangeListener

    OnPageChangeListener这个接口需要实现三个方法:(onPageScrollStateChanged,onPageScrolled ,onPageSelected)

    onPageScrollStateChanged(int arg0)   ,此方法是在状态改变的时候调用,其中arg0这个参数有三种状态(0,1,2)。arg0 ==1的时表示正在滑动,arg0==2的时表示滑动完毕了,arg0==0的时表示什么都没做。

    onPageScrolled(int arg0,float arg1,int arg2)    ,当页面在滑动的时候会调用此方法,在滑动被停止之前,此方法回一直得到调用。其中三个参数的含义分别为:

    arg0 :当前页面,及你点击滑动的页面

    arg1:当前页面偏移的百分比

    arg2:当前页面偏移的像素位置   

    onPageSelected(int arg0) :此方法是页面跳转完后得到调用,arg0是你当前选中的页面的Position(位置编号)。

     一、使用ViewPager

      ViewPager的使用与ListView使用类似,也要绑定相应的适配器。

      ViewPager的使用主要分3个步骤:

      1、在布局文件中添加ViewPager控件。注意:写这个控件的时候要写全称(包名+类名)

    <android.support.v4.view.ViewPager
    android:id="@+id/main_viewPager"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_above="@+id/rg">

    <android.support.v4.view.PagerTabStrip
    android:id="@+id/pagertab"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="top" />
    </android.support.v4.view.ViewPager>

     PagerTabTrip要当做ViewPager的一个子标签来用,可以用android:layout_gravity=TOP|BOTTOM来制定title的位置,如果要显示出PagerTabTrip的某一项Title,需要在ViewPager的Adapter中实现getPageTitle(int)。

    instantiateItem(ViewGroup,int)//实例化页卡
    
    destroyItem(ViewGroup,int,Object)//删除页卡
    
    getCount()//返回页卡的数量
    
    isViewFromObject(View,Object)//判断两个对象是否相等
    
    getPageTitle(int position)//设置标签显示的标题

    设置指示标签的属性:

    pagerTabStrip.setTabIndicatorColor();//指示器的颜色
    
    pagerTabStrip.setBackgroundColor();//背景色
    
    pagerTabStrip.setTextColor(Color.WHITE);//设置字体颜色

    2、加载要显示的界面,并把要显示的页面放入数组或List集合中

    LayoutInflater lf = getLayoutInflater().from(this);  
    
          view1 = lf.inflate(R.layout.layout1, null);  
    
          view2 = lf.inflate(R.layout.layout2, null);  
    
          view3 = lf.inflate(R.layout.layout3, null);  
    
          
    
          viewList = new ArrayList<View>();
    
          viewList.add(view1);  
    
          viewList.add(view2);  
    
          viewList.add(view3);  

      3、在Activity里实例化ViewPager组件,并设置它的Adapter,一般需要重写PagerAdapter。(和ListView使用差不多,使用步骤也类似。)

     

    ViewPager的方法:

    1. setAdapter():ViewPager通过setAdapter()来建立与PagerAdapter的联系。这个联系是双向的,一方面,,ViewPager会拥有PagerAdapter对象,从而可调用PagerAdapter的方法,另一方面,ViewPager会在setAdapter()中调用PagerAdapter的registerDataSetObserver()方法,注册一个自己生成的PagerObserver 对象,从而在 PagerAdapter 有所需要时(如 notifyDataSetChanged() 或 notifyDataSetInvalidated() 时),可以调用 Observer 的 onChanged() 或 onInvalidated() 方法,从而实现 PagerAdapter 向 ViewPager 方向发送信息。
    2. dataSetChanged():在 PagerObserver.onChanged(),以及 PagerObserver.onInvalide() 中被调用。因此当 PagerAdapter.notifyDataSetChanged() 被触发时,ViewPager.dataSetChanged() 也可以被触发。该函数将使用 getItemPosition() 的返回值来进行判断,如果为 POSITION_UNCHANGED,则什么都不做;如果为 POSITION_NONE,则调用 PagerAdapter.destroyItem() 来去掉该对象,并设置为需要刷新 (needPopulate = true) 以便触发 PagerAdapter.instantiateItem() 来生成新的对象。

    二、PagerAdapter

        PagerAdapter是ViewPager的支持者,ViewPager将调用它来取得所需展示的页面,而PagerAdapter也会在数据变化时通知ViewPager。这个类也是FragmentPagerAdapter以及FragmentStatePagerAdapter的基类。如果继承此类,至少需要实现instantiateItem(),destroyItem(),getCount()以及isViewFromObject()四个方法。

    • instantiateItem():在每次ViewPager需要一个用以显示Object的时候,该方法都会被ViewPager.addNewItem()方法调用。该方法内部一定要把指定位置要显示的视图添加到ViewPager中。
    • destroyItem():从当前的ViewPager容器中删除指定位置的View对象。
    • getCount():返回要滑动的View的个数。
    • isViewFromObject():该方法用来判断instantiateItem(ViewGroup , int)函数所返回来的key与一个页面视图是否是代表的同一个视图(即他俩是否是对应的,对应的表示同一个view) 返回值:如果对应的是同一个View,返回ture,否则返回false

    三、ViewPager添加标题

    1. 给ViewPager添加一个子标签
    <android.support.v4.view.PagerTitleStrip
    android:id="@+id/pagerTitle"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="top"/>

    2.覆写PagerAdapter的getPageTitle(position)方法,该方法返回每个界面的title

    1. @Override
    2. public CharSequence getPageTitle(int position){}

    注意:还有一种标题是android.support.v4.view.PagerTabStrip用法与上面的完全一样,唯一区别就是每个标题下面添加了一个下划线,并且标题也可点击

    四、给ViewPager添加监听器OnPageChageListner

    pager.setOnPageChangeListener(new OnPageChangeListener() {
                /**
                 * 当滑动结束后,新的pager完全显示到屏幕后会调用该方法
                 *  参数:新的pager的index
                 */
                @Override
                public void onPageSelected(int arg0) {
                    // Log.e(TAG, "onPageSelected..." + arg0);
                }
    
                /**
                 * 当正在滑动时,会一直回调该方法 
                 * arg0 :当前页面,及你点击滑动的页面
                 * arg1:当前页面偏移的百分比
                 * arg2:当前页面偏移的像素位置
                 */
                @Override
                public void onPageScrolled(int arg0, float arg1, int arg2) {
    //                Log.e(TAG, "onPageScrolled..." + arg0 + "  " + arg1 + "   " + arg2);
                }
                /**
                 * 当状态方法改变的时候调用
                 * 参数:页面的状态
                 * 共有三种状态:    
                 * SCROLL_STATE_IDLE   页面静止的时候的状态
                   SCROLL_STATE_DRAGGING  正在滑动的状态
                   SCROLL_STATE_SETTLING  到了要滑动的最终位置的状态
                 */
                @Override
                public void onPageScrollStateChanged(int arg0) {
    //                
                    String msg = "";
                    switch (arg0) {
                    case ViewPager.SCROLL_STATE_DRAGGING:
                        msg = "正在滑动";
                        break;
                    case ViewPager.SCROLL_STATE_IDLE:
                        msg = "静止了";
                        break;
                    case ViewPager.SCROLL_STATE_SETTLING:
                        msg = "到了";
                        break;
    
                    default:
                        break;
                    }
                    Log.e(TAG, "onPageScrollStateChanged..." + msg);
                }
            });
  • 相关阅读:
    Wireshark——工具
    Wireshark——网络协议
    Wireshark——过滤器
    Wireshark——数据包、着色规则和提示
    Floyd最小环
    有向环覆盖问题
    KM算法
    归并排序
    树状数组
    构造强连通图
  • 原文地址:https://www.cnblogs.com/chhom/p/4704944.html
Copyright © 2020-2023  润新知