相信大家对这个列表字母索引已经不陌生了,在很多app中也随处可见,像没团的城市地址选择,微信联系人列表,手机通讯录…等等。既然是个这么nb这么实用的功能我们怎么能不Get到来呢,下面就让我们一起造一个出来吧

一:我们可以大致将他分成3小块,右边的字母列表、中央的当前字母提示、ListView列表。ok分析好了那我们就一步步来编码实现吧

二:首先来实现右边的字母列表

1. 在画这个字母列表之前,先画张图来大致计算一下字母的坐标,如下图:

    /*绘制的列表导航字母*/private String words[] = {"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N","O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "#"};/*字母画笔*/private Paint wordsPaint;/*字母背景画笔*/private Paint bgPaint;/*每一个字母的宽度*/private int itemWidth;/*每一个字母的高度*/private int itemHeight;/*手指按下的字母索引*/private int touchIndex = 0;/*手指按下的字母改变接口*/private onWordsChangeListener listener;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
//得到画布的宽度和每一个字母所占的高度@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {super.onMeasure(widthMeasureSpec, heightMeasureSpec);itemWidth = getMeasuredWidth();//使得边距好看一些int height = getMeasuredHeight() - 10;itemHeight = height / 27;}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

2. 开始绘制A~Z~#的字符,先绘制字母背景,在绘制文字

@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);for (int i = 0; i < words.length; i++) {//判断是不是我们按下的当前字母if (touchIndex == i) {//绘制文字圆形背景canvas.drawCircle(itemWidth / 2, itemHeight / 2 + i * itemHeight, 23, bgPaint);wordsPaint.setColor(Color.WHITE);} else {wordsPaint.setColor(Color.GRAY);}//获取文字的宽高Rect rect = new Rect();wordsPaint.getTextBounds(words[i], 0, 1, rect);int wordWidth = rect.width();//绘制字母float wordX = itemWidth / 2 - wordWidth / 2;float wordY = itemWidth / 2 + i * itemHeight;canvas.drawText(words[i], wordX, wordY, wordsPaint);}}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

3.现在效果就是这个样子了

4. 现在来实现手指滑动或者点击字母列表的时候来改变当前选中的字母和在屏幕中央进行显示。这里怎么实现呢?很容易就想到这里肯定是在onTouchEvent中做处理,在使用接口回调来在屏幕中央显示当前字母

    /*** 当手指触摸按下的时候改变字母背景颜色*/@Overridepublic boolean onTouchEvent(MotionEvent event) {switch (event.getAction()) {case MotionEvent.ACTION_DOWN:case MotionEvent.ACTION_MOVE:float y = event.getY();//关键点===获得我们按下的是那个索引(字母)int index = (int) (y / itemHeight);if (index != touchIndex)touchIndex = index;//防止数组越界if (listener != null && 0 <= touchIndex && touchIndex <= words.length - 1) {//回调按下的字母listener.wordsChange(words[touchIndex]);}invalidate();break;case MotionEvent.ACTION_UP://手指抬起,不做任何操作break;}return true;}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

    /*手指按下了哪个字母的回调接口*/public interface onWordsChangeListener {void wordsChange(String words);}/*设置手指按下字母改变监听*/public void setOnWordsChangeListener(onWordsChangeListener listener) {this.listener = listener;}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

5.接口都写好了就可以在主界面中来显示了,activity_main的布局

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"><ListView
        android:id="@+id/list"android:layout_width="match_parent"android:layout_height="match_parent"android:scrollbars="none" /><!--字母导航--><com.zsy.words.view.WordsNavigation
        android:id="@+id/words"android:layout_width="30dp"android:layout_height="match_parent"android:layout_alignParentRight="true" /><!--这个就用来显示我们当前按下的字母--><TextView
        android:id="@+id/tv"android:layout_width="80dp"android:layout_height="80dp"android:layout_centerHorizontal="true"android:layout_centerVertical="true"android:background="@drawable/tvstyle"android:gravity="center"android:textSize="40sp"android:visibility="gone" />
</RelativeLayout
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

6.MainActivity中设置字母改变监听

tv = (TextView) findViewById(R.id.tv);
word = (WordsNavigation) findViewById(R.id.words);
word.setOnWordsChangeListener(this);//手指按下字母改变监听回调@Overridepublic void wordsChange(String words) {updateWord(words);}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
    /*** 更新中央的字母提示** @param words 首字母*/private void updateWord(String words) {tv.setText(words);tv.setVisibility(View.VISIBLE);//清空之前的所有消息handler.removeCallbacksAndMessages(null);//500ms后让tv隐藏handler.postDelayed(new Runnable() {@Overridepublic void run() {tv.setVisibility(View.GONE);}}, 500);}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

三:现在就只剩下ListView列表,和滑动列表来改变字母的背景了同时滑动列表来改变我们listView的数据显示

1.为ListView数据添加一个实体类Person,这里我们需要对我们的姓名转化成拼音,我这里使用的是pinyin4j-2.5.0.jar链接文章末尾给出

/** 文件名:     Person* 创建者:     阿钟* 创建时间:   2016/11/17 19:07* 描述:       封装联系人列表信息*/
public class Person {//姓名private String name;//拼音private String pinyin;//拼音首字母private String headerWord;public Person(String name) {this.name = name;this.pinyin = PinYinUtils.getPinyin(name);headerWord = pinyin.substring(0, 1);}public String getPinyin() {return pinyin;}public String getName() {return name;}public void setName(String name) {this.name = name;}public String getHeaderWord() {return headerWord;}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36

2.PinYinUtils将汉子转拼音

/** 文件名:   PinYinUtils* 创建者:   ZSY* 创建时间: 2016/11/17 17:51* 描述:     得到指定汉字的拼音*/
public class PinYinUtils {/*** 将hanzi转成拼音** @param hanzi 汉字或字母* @return 拼音*/public static String getPinyin(String hanzi) {StringBuilder sb = new StringBuilder();HanyuPinyinOutputFormat format = new HanyuPinyinOutputFormat();format.setCaseType(HanyuPinyinCaseType.UPPERCASE);format.setToneType(HanyuPinyinToneType.WITHOUT_TONE);//由于不能直接对多个汉子转换,只能对单个汉子转换char[] arr = hanzi.toCharArray();for (int i = 0; i < arr.length; i++) {if (Character.isWhitespace(arr[i])) {continue;}try {String[] pinyinArr = PinyinHelper.toHanyuPinyinStringArray(arr[i], format);if (pinyinArr != null) {sb.append(pinyinArr[0]);} else {sb.append(arr[i]);}} catch (Exception e) {e.printStackTrace();//不是正确的汉字sb.append(arr[i]);}}return sb.toString();}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41

3.现在我们就来撸ListView的Item布局了list_item.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:orientation="vertical"><!--显示字母--><TextView
        android:id="@+id/tv_word"android:layout_width="match_parent"android:layout_height="20dp"android:background="#ebebeb"android:gravity="center_vertical"android:paddingLeft="?android:attr/listPreferredItemPaddingLeft" /><!--显示联系人信息--><TextView
        android:id="@+id/tv_name"android:layout_width="match_parent"android:layout_height="wrap_content"android:gravity="center_vertical"android:minHeight="?android:attr/listPreferredItemHeightSmall"android:paddingLeft="?android:attr/listPreferredItemPaddingLeft"android:paddingRight="?android:attr/listPreferredItemPaddingRight"android:textAppearance="?android:attr/textAppearanceListItemSmall" />
</LinearLayout>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

5.为我们的列表添加测试数据,并对数据进行排序

    /*** 初始化联系人列表信息*/private void initData() {list = new ArrayList<>();list.add(new Person("Dave"));list.add(new Person("阿钟"));//省略一些....list.add(new Person("胡继群"));list.add(new Person("隔壁老王"));list.add(new Person("姜宇航"));//对集合排序Collections.sort(list, new Comparator<Person>() {@Overridepublic int compare(Person lhs, Person rhs) {//根据拼音进行排序return lhs.getPinyin().compareTo(rhs.getPinyin());}});}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

6.撸ListView的列表适配器

public class MyAdapter extends BaseAdapter {private List<Person> list;private LayoutInflater inflater;public MyAdapter(Context context, List<Person> list) {inflater = LayoutInflater.from(context);this.list = list;}@Overridepublic int getCount() {return list.size();}@Overridepublic Object getItem(int position) {return list.get(position);}@Overridepublic long getItemId(int position) {return position;}@Overridepublic View getView(int position, View convertView, ViewGroup parent) {ViewHolder holder;if (convertView == null) {holder = new ViewHolder();convertView = inflater.inflate(R.layout.list_item, null);holder.tv_word = (TextView) convertView.findViewById(R.id.tv_word);holder.tv_name = (TextView) convertView.findViewById(R.id.tv_name);convertView.setTag(holder);} else {holder = (ViewHolder) convertView.getTag();}String word = list.get(position).getHeaderWord();holder.tv_word.setText(word);holder.tv_name.setText(list.get(position).getName());//将相同字母开头的合并在一起if (position == 0) {//第一个是一定显示的holder.tv_word.setVisibility(View.VISIBLE);} else {//后一个与前一个对比,判断首字母是否相同,相同则隐藏String headerWord = list.get(position - 1).getHeaderWord();if (word.equals(headerWord)) {holder.tv_word.setVisibility(View.GONE);} else {holder.tv_word.setVisibility(View.VISIBLE);}}return convertView;}private class ViewHolder {private TextView tv_word;private TextView tv_name;}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60

7.现在我们需要来实现,当滑动列表的时候需要更新ListView的显示,在wordsChange函数中进行出理,同时滑动列表我们也需要更新右侧字母列表的状态,ok逻辑理清了就好办事了。

1.在wordsChange调用此函数updateListView改变ListView的显示

    /*** @param words 首字母*/private void updateListView(String words) {for (int i = 0; i < list.size(); i++) {String headerWord = list.get(i).getHeaderWord();//将手指按下的字母与列表中相同字母开头的项找出来if (words.equals(headerWord)) {//将列表选中哪一个listView.setSelection(i);//找到开头的一个即可return;}}}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

2.为ListView设置滑动监听,来改变右侧字母列表的状态

@Overridepublic void onScrollStateChanged(AbsListView view, int scrollState) {}@Overridepublic void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {//当滑动列表的时候,更新右侧字母列表的选中状态word.setTouchIndex(list.get(firstVisibleItem).getHeaderWord());}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

3.自定义字母列表View中的函数,这样当我们滑动列表的时候字母状态也就可以时时同步了

    /*设置当前按下的是那个字母*/public void setTouchIndex(String word) {for (int i = 0; i < words.length; i++) {if (words[i].equals(word)) {touchIndex = i;invalidate();return;}}}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

ok大功告成,一个简单的联系字母索引列表就实现了。Demo下载地址,汉子转拼音jar包下载如果有什么疑问可以联系我哦

实现联系人列表字母索引相关推荐

  1. Android 通过ListView实现联系人列表字母索引

    转载:Android自定义View--实现联系人列表字母索引_Code-Porter的博客-CSDN博客_android 字母索引 学习大神的思路和逻辑,很清晰. 这篇博文也不错:安卓仿手机联系人右侧 ...

  2. Android自定义View——实现联系人列表字母索引

    相信大家对这个列表字母索引已经不陌生了,在很多app中也随处可见,像没团的城市地址选择,微信联系人列表,手机通讯录-等等.既然是个这么nb这么实用的功能我们怎么能不Get到来呢,下面就让我们一起造一个 ...

  3. 联系人列表字母排序索引(三)

    也是忙忙碌碌好几天,今天又有时间了,继续这个文章的编写. 阅读这篇文章之前,请先阅读 联系人列表字母排序索引(一) 联系人列表字母排序索引(二) 今天是这篇文章的最后一部分,主要内容包括以下几点: 1 ...

  4. 联系人列表字母排序索引(一)

    很久没有写博客了,对自己又放松了很多.这篇博客本来早就要写了,迟迟拖到现在.今天这里要说的是,联系人列表,分组带索引,即联系人按字母顺序排列并分组,右边还有索引条.先看下效果: 就是这么一个效果,想必 ...

  5. 浅谈android中手机联系人字母索引表的实现

    实际上字母索引表的效果,可以说在现在的众多APP中使用的非常流行,比如支付宝,微信中的联系人,还有购物,买票的APP中选择全国城市,切换城市的时候,这时候的城市也就是按照一个字母索引的顺序来显示,看起 ...

  6. 字母索引定位,仿联系人列表功能,实现字母A-Z排序

    常见的联系人列表 A-Z排序功能,获取数据首字母,仿照联系人实现A-Z字母排序,实现字母索引定位功能:监听字母滑动,使recycleview滑动到指定位置: 先上效果图: 下面介绍实现逻辑: 自定义 ...

  7. Windows Phone 7 LongListSelector控件实现分类列表和字母索引

    在wp7手机里面的联系人列表和程序里面里面我们可以看到一个根据字母索引来定位联系人或者应用程序的控件,那么这个控件就是LongListSelector控件了. LongListSelector是一种比 ...

  8. android 仿微信联系人 首字母分组快速索引

    总结是一种习惯,不能停,一停人就懒了,都快一个月没有写了!该提提神了! 进入正题:android 仿微信联系人 首字母快速索引,先用下美团的索引效果图: 1.自定义View字母索引栏(右边那一列): ...

  9. 微信小程序点击--实现带字母索引的城市列表

    哈哈 先上图看看是不是这种效果   因为比较穷 所以录制的gif格式图片有水印 之前在网上搜索了好多这种格式的,感觉代码都好多  如果只是点击然后跳转到相应的位置是比较简单的 主要用到的知识就是 sc ...

最新文章

  1. codeblocks断点不停,无效问题终极解决
  2. 【Qt】打开现有 Qt 项目 ( 打开已存在的项目 | 运行打开的项目 )
  3. mysql第一二章笔记_MYSQL必知必会读书笔记 第一章(基础)
  4. java 生成正弦波声音_如何生成一个正弦波声音曲线?
  5. zen cart 操作-修改
  6. 光伏等新能源信用风险事件频繁爆发
  7. 服务器ios文件,ios 文件到服务器
  8. java删除表操作,JDBC删除表实例
  9. jquery操作checkbox最佳方法
  10. 测试培训大纲第一课时,软件测试基础(培训待续中....)
  11. 删除顽固node_modules
  12. Laravel的核心概念
  13. form data怎么接收_VUE发送Formdata数据,NodeJS接收
  14. Minimum edit distance(levenshtein distance)(最小编辑距离)初探
  15. 转科普CPU Cache line
  16. 干货!仓储规划设计方法论
  17. map和object对象互转
  18. 【转】让ubuntu自带词典可以本地查…
  19. python把英语句子成分字母_英语句子成分
  20. easyexcel 检查表头是否匹配_利用easyexcel生成excel文件-自定义表头与数据栏对应的处理方式...

热门文章

  1. C# 10进制转16进制 赋值给byte数组
  2. 密码学之公钥密码体系(3):ElGamal算法
  3. 做好人员管理,项目管理就成功了一半
  4. eNSP-VRRP虚拟路由器冗余技术
  5. FEA和CFD仿真和分析软件的全球与中国市场2022-2028年:技术、参与者、趋势、市场规模及占有率研究报告
  6. 【论文阅读】CVPR2018-深度材料感知跨光谱立体匹配
  7. 牛客2021年多校训练营<2>
  8. c语言字符串抽出,C语言程序设计 从键盘输入一个字符串,将其中所有的数字字符抽出,按原顺序组成一个新串并输出。...
  9. java-对两个数进行加减乘除操作案例
  10. 硬盘和固态硬盘的区别及监控