Android(安卓)NotePad的简单实现
MyNotePad
1.0 Version
运行环境
AndroidStudio 3.3.1
JDK 1.8
minSdkVersion 21 TargetSdkVersion 28
模拟机API 23
时间戳
代码分析
(1)在布局文件中增加一个TextView来显示时间戳
(2)数据库中已有文本创建时间和修改时间连个字段,在NodeEditor.java中,找到updateNode()这个函数,选取修改时间这一字段,并将其格式化存入数据库
Date nowTime = new Date(System.currentTimeMillis());SimpleDateFormat sdFormatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");String retStrFormatNowDate = sdFormatter.format(nowTime);
values.put(NotePad.Notes.COLUMN_NAME_MODIFICATION_DATE, retStrFormatNowDate);
(3)在NoteList.java的PROJECTION数组中增加该字段的描述,并在SimpleCursorAdapter中的参数viewsIDs和dataColumns增加子段描述,以达到将其读出和显示的目的
//The columns needed by the cursor adapterprivate static final String[] PROJECTION = new String[] { NotePad.Notes._ID, // 0 NotePad.Notes.COLUMN_NAME_TITLE, // 1 NotePad.Notes.COLUMN_NAME_MODIFICATION_DATE, };// The names of the cursor columns to display in the view, initialized to the title columnprivate String[] dataColumns = { NotePad.Notes.COLUMN_NAME_TITLE ,NotePad.Notes.COLUMN_NAME_MODIFICATION_DATE} ;// The view IDs that will display the cursor columns, initialized to the TextView inprivate int[] viewIDs = { R.id.text1,R.id.text2 };
效果
搜索笔记以title为关键字段
代码分析
(1)在NodeList.java的布局文件中新增SearchView控件(备注:引用的是support.v7的包,如未找到,需在build.gradle(Module.app)中添加依赖
(2)在NodeList.java中创建一个函数专门来配置SeachView,实现查询的基本思想是新创建一个Cursor来通过SeacrhView搜索的字段在数据库中进行模糊搜索从而装配数据,并在ListView中即时显示,无需点击搜索按键,最后在onCreate()中调用
private void SearchView(){ searchView=findViewById(R.id.sv); // a display model searchView.onActionViewExpanded(); //default display searchView.setQueryHint("搜索笔记"); //display submit button searchView.setSubmitButtonEnabled(true); //implement SearchView TextListener searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() { // when text has been submitted @Override public boolean onQueryTextSubmit(String s) { return false; } // when text is changing @Override public boolean onQueryTextChange(String s) { if(!s.equals("")){ String selection=NotePad.Notes.COLUMN_NAME_TITLE+" GLOB '*"+s+"*'";//query selection condition updatecursor = getContentResolver().query( getIntent().getData(), // Use the default content URI for the provider. PROJECTION, // Return the note ID and title for each note. selection, // No where clause, return all records. null, // No where clause, therefore no where column values. NotePad.Notes.DEFAULT_SORT_ORDER // Use the default sort order. ); } else { updatecursor = getContentResolver().query( getIntent().getData(), // Use the default content URI for the provider. PROJECTION, // Return the note ID and title for each note. null, // No where clause, return all records. null, // No where clause, therefore no where column values. NotePad.Notes.DEFAULT_SORT_ORDER // Use the default sort order. ); } // change adapter from SimpleCursorAdapter cursor adapter.swapCursor(updatecursor); // adapter.notifyDataSetChanged(); return false; } }); }
效果
修改背景色及字体颜色
代码分析
(1)在OptionMenu中添加修改颜色这一选项,并划分为两类,修改背景颜色和修改字体颜色
-
(2)采用AlertDialog来实现界面交互效果,并采用自定义布局定义UI界面,因为两种修改颜色调用的是同一个函数showColor(),因此定义一个boolean变量flag来判断
private void showColor(){ AlertDialog alertDialog=new AlertDialog.Builder(this).setTitle("请选择颜色"). setIcon(R.mipmap.ic_launcher).setView(R.layout.color_layout) .setPositiveButton("确定", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); } }).create(); alertDialog.show(); }
AlertDialog布局文件
布局文件按钮的点击事件
public void onClick(View v) { switch (v.getId()){ case R.id.orange: if(isFlag){ mText.setBackgroundColor(Color.parseColor("#FF8C00")); colorBack="#FF8C00"; }else{ mText.setTextColor(Color.parseColor("#FF8C00")); colorText="#FF8C00"; } break; case R.id.chocolate: if(isFlag){ mText.setBackgroundColor(Color.parseColor("#D2691E")); colorBack="#D2691E"; }else{ mText.setTextColor(Color.parseColor("#D2691E")); colorText="#D2691E"; } break; case R.id.aqua: if(isFlag){ mText.setBackgroundColor(Color.parseColor("#00FFFF")); colorBack="#00FFFF"; }else{ mText.setTextColor(Color.parseColor("#00FFFF")); colorText="#00FFFF"; } break; case R.id.gray: if(isFlag){ mText.setBackgroundColor(Color.parseColor("#696969")); colorBack="#696969"; }else{ mText.setTextColor(Color.parseColor("#696969")); colorText="#696969"; } break; case R.id.pink: if(isFlag){ mText.setBackgroundColor(Color.parseColor("#D81B60")); colorBack="#D81B60"; }else{ mText.setTextColor(Color.parseColor("#D81B60")); colorText="#D81B60"; } break; case R.id.green: if(isFlag){ mText.setBackgroundColor(Color.parseColor("#00FF7F")); colorBack="#00FF7F"; }else{ mText.setTextColor(Color.parseColor("#00FF7F")); colorText="#00FF7F"; } break; } }
(3)在onOptionItemSelected()中添加点击事件
case R.id.background_color: isFlag=true; showColor(); break;case R.id.text_color: isFlag=false; showColor(); break;
(4)点击保存之后将颜色的16进制保存到数据库,也是通过updateNote()函数,具体实现与时间戳功能类似,这里说明一下数据库新增字段的实现
1.在NotePad.java中的内部类Notes添加契约
/** NoteEditor.java EditText setBackGroundColor* */public static final String COLUMN_BACKGROUND_COLOR="bColor";/* * NoteEditor.java EditText setTextColor * */public static final String COLUMN_TEXT_COLOR="tColor";
2.在NotePadProvider.java的静态构造块往sNotesProjectionMap添加相应的键值对
//Maps "bColor" to "bColor"sNotesProjectionMap.put( NotePad.Notes.COLUMN_BACKGROUND_COLOR, NotePad.Notes.COLUMN_BACKGROUND_COLOR);//Maps "tColor" to "tColor"sNotesProjectionMap.put( NotePad.Notes.COLUMN_TEXT_COLOR, NotePad.Notes.COLUMN_TEXT_COLOR);
备注:如果已经在手机中安装app之后要新增或删减字段,需要在onUpgrade()中更换版本号,或者卸载重装。
效果
添加标签以将文本分类
代码分析
(1)在数据库中添加标签字段,与修改颜色功能类似,不作相应代码显示
(2)在NodeEditor.java布局文件中添加Button控件,点击弹出AlertDialog显示类别选项,设置按钮文本
//tag selection definedprivate final String items[]={"Default","Travel","Work","Study","Life"};
public void tagSelect(View v){ AlertDialog.Builder tagbuilder; AlertDialog alertDialog; tagbuilder=new AlertDialog.Builder(this); tagbuilder.setTitle("Tag Selection:"); tagbuilder.setIcon(R.mipmap.ic_launcher); tagbuilder.setSingleChoiceItems(items, checkItem, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { checkItem=which; button.setText(items[checkItem]); } }); tagbuilder.setPositiveButton("确定", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); } }); alertDialog=tagbuilder.create(); alertDialog.show(); }
(3)点击保存之后会将按钮文本保存到数据库,与时间戳功能类似,不再作具体演示
(4)在NoteList.java添加抽屉布局,以显示侧滑菜单栏的效果
1.在AndroidManifest.xml中更改主题,禁用ActionBar,查看引用,在styles.xml中进行修改
android:theme="@style/AppTheme"
style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar"
2.在布局文件中使用DrawerLayout布局和toolbar,还有NavigationView,与之前同理,导入的分别是v4和v7的包,如果没有,需要添加依赖(注:CoordinatorLayout是之前想做一个浮动布局加的,但后来放弃了,所以这个布局是可以删除的,只要修改号里面控件的width和height就可以)
3.NavigationView的菜单编写
<?xml version="1.0" encoding="utf-8"?>
(3)编写toolbar的监听事件和NavigationView的监听事件
1.toolbar
setSupportActionBar(toolbar); //set support toolbar in this Activitytoolbar.setNavigationOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { drawerLayout.openDrawer(Gravity.START);// set from left open NavigationView Menu }});
2.NavigationView
navigationView.setNavigationItemSelectedListener(new NavigationView.OnNavigationItemSelectedListener() { @Override public boolean onNavigationItemSelected(@NonNull MenuItem menuItem) { switch (menuItem.getItemId()){ case R.id.notepad: tagCursor = getContentResolver().query( getIntent().getData(), // Use the default content URI for the provider. PROJECTION, // Return the note ID and title for each note. null, // No where clause, return all records. null, // No where clause, therefore no where column values. NotePad.Notes.DEFAULT_SORT_ORDER // Use the default sort order. ); adapter.swapCursor(tagCursor);//swap cursor from SimpleCursorAdapter toolbar.setTitle("MyNotePad"); drawerLayout.closeDrawer(navigationView);//close NavigationView menu break; case R.id.travel: tagSelection=NotePad.Notes.COLUMN_TAG_SELECTION_INDEX+" = 'Travel'"; tagCursor = getContentResolver().query( getIntent().getData(), // Use the default content URI for the provider. PROJECTION, // Return the note ID and title for each note. tagSelection, // No where clause, return all records. null, // No where clause, therefore no where column values. NotePad.Notes.DEFAULT_SORT_ORDER // Use the default sort order. ); adapter.swapCursor(tagCursor);//swap cursor from SimpleCursorAdapter toolbar.setTitle("Travel"); drawerLayout.closeDrawer(navigationView);//close NavigationView menu break; case R.id.work: tagSelection=NotePad.Notes.COLUMN_TAG_SELECTION_INDEX+" = 'Work'"; tagCursor = getContentResolver().query( getIntent().getData(), // Use the default content URI for the provider. PROJECTION, // Return the note ID and title for each note. tagSelection, // No where clause, return all records. null, // No where clause, therefore no where column values. NotePad.Notes.DEFAULT_SORT_ORDER // Use the default sort order. ); adapter.swapCursor(tagCursor);//swap cursor from SimpleCursorAdapter toolbar.setTitle("Work"); drawerLayout.closeDrawer(navigationView);//close NavigationView menu break; case R.id.study: tagSelection=NotePad.Notes.COLUMN_TAG_SELECTION_INDEX+" = 'Study'"; tagCursor = getContentResolver().query( getIntent().getData(), // Use the default content URI for the provider. PROJECTION, // Return the note ID and title for each note. tagSelection, // No where clause, return all records. null, // No where clause, therefore no where column values. NotePad.Notes.DEFAULT_SORT_ORDER // Use the default sort order. ); adapter.swapCursor(tagCursor);//swap cursor from SimpleCursorAdapter toolbar.setTitle("Study"); drawerLayout.closeDrawer(navigationView);//close NavigationView menu break; case R.id.life: tagSelection=NotePad.Notes.COLUMN_TAG_SELECTION_INDEX+" = 'Life'"; tagCursor = getContentResolver().query( getIntent().getData(), // Use the default content URI for the provider. PROJECTION, // Return the note ID and title for each note. tagSelection, // No where clause, return all records. null, // No where clause, therefore no where column values. NotePad.Notes.DEFAULT_SORT_ORDER // Use the default sort order. ); adapter.swapCursor(tagCursor);//swap cursor from SimpleCursorAdapter toolbar.setTitle("Life"); drawerLayout.closeDrawer(navigationView);//close NavigationView menu break; case R.id.def: tagSelection=NotePad.Notes.COLUMN_TAG_SELECTION_INDEX+" = 'Default'"; tagCursor = getContentResolver().query( getIntent().getData(), // Use the default content URI for the provider. PROJECTION, // Return the note ID and title for each note. tagSelection, // No where clause, return all records. null, // No where clause, therefore no where column values. NotePad.Notes.DEFAULT_SORT_ORDER // Use the default sort order. ); adapter.swapCursor(tagCursor);//swap cursor from SimpleCursorAdapter toolbar.setTitle("Default"); drawerLayout.closeDrawer(navigationView);//close NavigationView menu break; } return true; } });
效果
在文本中插入图片
代码分析
(1)在NodeEditor.java的optionmenu中添加选项
-
-
-
(2)拍照要在AndroidManifest.xml中设置权限,API23以上还要在代码中设置动态权限,API28以上权限设置更为严格,本功能并不适用。而从图库中选取照片权限要求较低.
//从相册取图片public void getPhoto() { Intent intent = new Intent(); intent.setType("image/*"); intent.setAction(Intent.ACTION_OPEN_DOCUMENT); startActivityForResult(intent, PHOTO_FROM_GALLERY);}//拍照取照片public void takeCamera() { if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions( this, new String[] { Manifest.permission.CAMERA }, PHOTO_FROM_CAMERA); } else { File file=new File(Environment.getExternalStorageDirectory(),System.currentTimeMillis()+".jpg"); try { if(file.exists()){ file.delete(); } file.createNewFile(); }catch (IOException e){ e.printStackTrace(); } imageUri=Uri.fromFile(file); Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); intent.putExtra(MediaStore.EXTRA_OUTPUT,imageUri); startActivityForResult(intent, PHOTO_FROM_CAMERA); }}
(3)编写onActivityResult定义返回动作
@Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { ContentResolver resolver = getContentResolver(); super.onActivityResult(requestCode, resultCode, data); //第一层switch switch (requestCode) { case PHOTO_FROM_GALLERY: //第二层switch switch (resultCode) { case RESULT_OK: if (data != null) { Uri uri = data.getData();//获取Intent uri Bitmap bitmap = null; //判断该路径是否存在 try { Bitmap originalBitmap = BitmapFactory.decodeStream(resolver.openInputStream(uri)); bitmap = resizeImage(originalBitmap, 200, 200);//图片缩放 } catch (FileNotFoundException e) { e.printStackTrace(); } if(bitmap != null){//如果图片存在 //将选择的图片追加到EditText中光标所在位置 int index = mText.getSelectionStart(); //获取光标所在位置 Editable edit_text = mText.getEditableText();//编辑文本框 if(index <0 || index >= edit_text.length()){ edit_text.append(uri.toString()); updateNoteText(mText.getText().toString());//更新到数据库 }else{ edit_text.insert(index,uri.toString()); updateNoteText(mText.getText().toString());//更新到数据库 } }else{ Toast.makeText(NoteEditor.this, "获取图片失败", Toast.LENGTH_SHORT).show(); } } break; case RESULT_CANCELED: break; } break; case PHOTO_FROM_CAMERA: if (resultCode == RESULT_OK) { Bitmap originalBitmap1=null; //判断图片是否存在 try{ originalBitmap1=BitmapFactory.decodeStream(getContentResolver().openInputStream(imageUri)); }catch (FileNotFoundException e){ e.printStackTrace(); } if(originalBitmap1 != null){//如果图片存在保存URI //将选择的图片追加到EditText中光标所在位置 int index = mText.getSelectionStart(); //获取光标所在位置 Editable edit_text = mText.getEditableText();//编辑文本框 if(index <0 || index >= edit_text.length()){ edit_text.append(imageUri.toString()); updateNoteText(mText.getText().toString());//更新到数据库 }else{ edit_text.insert(index, imageUri.toString()); updateNoteText(mText.getText().toString());//更新到数据库 } }else{ Toast.makeText(NoteEditor.this, "获取图片失败", Toast.LENGTH_SHORT).show(); } } else { Log.e("result", "is not ok" + resultCode); } break; default: break; } }
(4)定义两个正则表达式,提取文本的图片路径,用SpannableString进行图片文字替换,从而实现图片的插入
private static final String regex="content://com.android.providers.media.documents/" +"document/image%\\w{4}";private static final String reg="file:///storage/emulated/0/\\d+.jpg";
onResumn()和cancelNote()中编写
int colNoteIndex = mCursor.getColumnIndex(NotePad.Notes.COLUMN_NAME_NOTE);//提取数据库中的文本 String note = mCursor.getString(colNoteIndex); ArrayList contentList=new ArrayList<>();//存放图片路径 ArrayList startList=new ArrayList<>();//存放图片路径起点位置 ArrayList endList=new ArrayList<>();//存放图片路径终点位置 Pattern p=Pattern.compile(regex); Matcher m=p.matcher(note); //当匹配到图库相应资源地址,将对应信息存入List while(m.find()){ contentList.add(m.group()); startList.add(m.start()); endList.add(m.end()); flag=true; } p=Pattern.compile(reg); m=p.matcher(note); //当匹配到拍照的图片相应资源地址,将对应信息存入List while(m.find()){ contentList.add(m.group()); startList.add(m.start()); endList.add(m.end()); flag=true; } //判断文本中是否有图片资源地址 if(!flag){ mText.setText(note); }else{ pushPicture(note,contentList,startList,endList); }
图片处理
private void pushPicture(String note,ArrayList contentList,ArrayList startList,ArrayList endList) { //创建一个SpannableString对象,以便插入用ImageSpan对象封装的图像 SpannableString spannableString = new SpannableString(note); for(int i=0;i
效果
设置各文本的提醒时间
代码分析
(1)在NoteEditor.java中添加控件,我是选用Button,比较方便通过监听来处理事件,比较简单,就不作代码演示
(2)在NoteEditor.java中的OptionMenu中添加相应的选项,和选择事件的触发,也比较简单和上述功能类似,也不做代码演示
(3)创建DatePickerDialog和TimePickerDialog来进行时间与日期的选择
1.DatePickerDialog
private void createDateDialog(){ final Calendar calendar=Calendar.getInstance(); DatePickerDialog dialog = new DatePickerDialog(this, AlertDialog.THEME_HOLO_DARK, new DatePickerDialog.OnDateSetListener() { @Override public void onDateSet(DatePicker view, int year, int month, int dayOfMonth) { date=year+"-"+(month+1)+"-"+dayOfMonth; if(time!=null){ dateButton.setText(date+time); }else{ String text=calendar.get(Calendar.HOUR_OF_DAY)+":"+(calendar.get(Calendar.MINUTE)+5); time=" "+text;//如果时间未指定,则默认为当前时间的5分钟后提醒 dateButton.setText(date+" "+text); } dateButton.setVisibility(View.VISIBLE); } }, calendar.get(Calendar.YEAR), calendar.get(Calendar.MONTH), calendar.get(Calendar.DAY_OF_MONTH)); dialog.getDatePicker().setMinDate(System.currentTimeMillis()-1000);//选择以当前时间开始,避免无效时间的选择 dialog.setTitle("选择日期:"); dialog.show(); }
2.TimePickerDialog
private void createTimeDialog(){ final Calendar calendar=Calendar.getInstance(); TimePickerDialog dialog=new TimePickerDialog(this, AlertDialog.THEME_HOLO_DARK,new TimePickerDialog.OnTimeSetListener() { @Override public void onTimeSet(TimePicker view, int hourOfDay, int minute) { String text=calendar.get(Calendar.YEAR)+"-"+(calendar.get(Calendar.MONTH)+1)+"-"+calendar.get(Calendar.DAY_OF_MONTH); /* * 判断时间的选择是否为无效时间,在当前的时间之前 * */ if(text.equals(date)||date==null){ if(hourOfDay<=calendar.get(Calendar.HOUR_OF_DAY)) if(minute-5<=calendar.get(Calendar.MINUTE)){ //为无效时间的话就将时间设为五分钟之后 time=" "+calendar.get(Calendar.HOUR_OF_DAY)+":"+(calendar.get(Calendar.MINUTE)+5); } else{ time=" "+calendar.get(Calendar.HOUR_OF_DAY)+":"+minute; } else{ time=" "+hourOfDay+":"+minute; } }else{ time=" "+hourOfDay+":"+minute; } if(date!=null){ dateButton.setText(date+time); }else{ date=text; dateButton.setText(text+time); } dateButton.setVisibility(View.VISIBLE); } }, calendar.get(Calendar.HOUR_OF_DAY), calendar.get(Calendar.MINUTE), true); dialog.setTitle("选择时间:"); dialog.show(); }
(4)当保存笔记之后,将需要提醒的信息封装进PendingIntent,以广播的形式发送出去
private void notifyMessage(){ if(time!=null&&date!=null){ SimpleDateFormat simpleDateFormat=new SimpleDateFormat("yyyy-MM-dd HH:mm"); long t=System.currentTimeMillis()+5000; try { t=simpleDateFormat.parse(date+time).getTime(); } catch (Exception e) { // TODO: handle exception e.printStackTrace(); } //是Intent跳转到指定的广播处理 Intent intent=new Intent(NoteEditor.this, RemindActionBroadcast.class); /* * 把文本的内容和标题存入Intent * */ intent.putExtra("title",mCursor.getString(mCursor.getColumnIndex(NotePad.Notes.COLUMN_NAME_TITLE))); intent.putExtra("context",mCursor.getString(mCursor.getColumnIndex(NotePad.Notes.COLUMN_NAME_NOTE))); //将requestCode设为每个文本的ID以实现能够发送不同的信息,不会被覆盖 PendingIntent pendingIntent=PendingIntent.getBroadcast(NoteEditor.this,mCursor.getInt(mCursor.getColumnIndex(NotePad.Notes._ID)),intent,PendingIntent.FLAG_UPDATE_CURRENT); Calendar calendar=Calendar.getInstance(); calendar.setTimeInMillis(System.currentTimeMillis()); calendar.add(Calendar.SECOND,(int)((t-System.currentTimeMillis())/1000)); //使用AlarmManager实现定时功能 AlarmManager alarmManager=(AlarmManager)getSystemService(ALARM_SERVICE); alarmManager.set(AlarmManager.RTC_WAKEUP,calendar.getTimeInMillis(),pendingIntent); } }
(5)在广播接受类中进行处理和通知
public class RemindActionBroadcast extends BroadcastReceiver { public static int id=0; @Override public void onReceive(Context context, Intent intent) { PendingIntent pendingIntent=PendingIntent.getActivity(context,0,intent,0); NotificationManager notificationManager=(NotificationManager)context.getSystemService(context.NOTIFICATION_SERVICE); Notification.Builder mbuilder=new Notification.Builder(context); mbuilder.setContentTitle(intent.getStringExtra("title"));//设置通知栏标题 mbuilder.setContentText(intent.getStringExtra("context"));//设置通知栏内容 mbuilder.setSmallIcon(R.mipmap.ic_launcher);//设置小图标 mbuilder.setLargeIcon(BitmapFactory.decodeResource(context.getResources(),R.mipmap.ic_launcher));//设置大图标 mbuilder.setContentIntent(pendingIntent);//设置点击跳转的Intent,因为没有设置uri,所以跳转为空 mbuilder.setAutoCancel(true);//点击之后消失 Notification notification=mbuilder.build(); notificationManager.notify(id++,notification);//能够传送多条消息 }}
(6)点击设置时间按钮取消设置通知时间
public void dateClick(View view){ AlertDialog.Builder builder=new AlertDialog.Builder(this); builder.setMessage("请确认是否删除提醒时间:").setPositiveButton("确认", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { date=null; time=null; dateButton.setVisibility(View.GONE); dialog.dismiss(); } }).setNegativeButton("取消", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); } }); builder.create().show(); }
效果
最后
附上源码地址:https://github.com/smartflowers/MyNotePad
记得点个Star哟
更多相关文章
- Android(安卓)GridView的使用
- 学习Android闹钟源代码(三)-AlarmClock类分析(part2)
- Android(安卓)Base64编码解码
- Android基础篇之AutoCompleteTextView
- Android——为图片增加水印,并且保存到图库刷新
- Android中Toast之间快速切换(连续弹吐司)
- android tabHost使用
- Android之智能问答机器人
- Android(安卓)欢迎页面